d7d18aef771d1205d265a3fff32969b83107c31b
52 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d7d18aef77 |
Зелье медленного падения (перо) + цвет пузырьков по типу зелья
L7-FEATHER: тип 3 (уровень 7, комната 1) больше не пустой TODO. - pop_map.c: pop_feather — счётчик кадров эффекта (POP_FEATHER_FRAMES = 225, ванильный порог do_timers seg003:0517; привязку к звуку взять неоткуда). fall_accel — ускорение 1 / потолок 4 (seg006:057C) вместо 3 / 33. proc_get_object case 3: взвести эффект + зелёная вспышка на 3 кадра. - pop_kid.c: опкод JMP_IF_FEATHER (0xF7) больше не пропускает адрес безусловно — под пером прыгает по нему, то есть seqtbl уводит падение и удар в ветки stepfloat/bumpfloat (плавные кадры, без урона). - roomtest.c: pop_flash_red -> pop_flash_color (жёлтый/красный/ЗЕЛЁНЫЙ); сброс pop_feather в pop_start_level (seg003:189). - Цвет пузырька по ТИПУ зелья (seg008:652), чего у нас не было вовсе: 3/4 зелёный, 5/6 СИНИЙ, остальные красный. Mono-блиттера с параметром цвета в libbgi нет, поэтому цвет запекается при упаковке: pop_pack_bg.py кладёт те же 7 кадров ещё дважды (id 30..36 зелёные, 40..46 синие), pop_potion_draw выбирает набор. Атлас 23 -> 37 спрайтов (+423 Б). - Синее зелье «−HP» приведено к оригиналу (seg006:1892): своей вспышки не ставит (красный кадр даёт общий flash_if_hurt — иначе экран красился дважды), а на уровне зелий забирает ПОЛОВИНУ запаса HP. Такое зелье стоит уже на пройденном уровне 2 (комната 13, тайл (1,3)), а также ур.8 комн.2 и весь ур.15 — до сих пор оно было красным. Тесты: phys_feather_fall_is_slow_and_harmless (обычное падение с двух рядов разгоняется и стоит HP, под пером скорость <= 4 и HP целое); tests-host 5/5 (1727 в [phys]), size-check OK. Расхождения с ванилью — в docs/impl_diff.md (перо ловит только Кида; синее зелье без своей вспышки). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b5951c9b5c |
Зацеп за кромку нижнего ряда: do_fall пускает ряд до 3 + check_grab в midair
Уровень 7, комната 14: спуск с ряда 0 на ряд 2 через зацеп (повис на кромке кнопки (0,2), отпустил, взялся за (2,2)) не работал ни при какой фазе и задержке — Кид пролетал мимо и разгонялся до fall_y = 33, после чего окно зацепа закрыто уже по скорости. Корень: do_fall не давал curr_row выйти за 2, а check_grab целится в тайл ряда curr_row-1 — то есть ряд 2 своей комнаты становится целью только при curr_row == 3. В оригинале (seg005:0030) inc_curr_row безусловный, а get_tile для ряда 3 уходит по links.down (find_room_of_tile, seg006:005D). - pop_map.c do_fall: inc_curr_row теперь 2 -> 3; ветка «достиг y_land» гейтится по curr_row <= 2 (при ряде 3 ни in_wall, ни land звать нельзя — тайлов своей комнаты там нет, а до y_land[4] дело не доходит: комнату меняет check_leave_below на y >= 211). - pop_map.c check_action: восстановлена ветка ACT_MIDAIR (кадры 102..105, seg006:0619) — первые четыре кадра падения, где fall_y ещё не разогнан, зацеп не работал вовсе. Отсюда же «иногда цепляется, иногда нет». - tests-host/t_grab.c: регресс grab_below_room_edge_window_exists — сцена комнаты 14 + переход в 15 по pop_fell_out. До фикса ни одного зацепа, после — окно из 8 фаз X при задержках 0..5 кадров; проверяется и играбельность (зацеп при «отпустил и сразу зажал Shift»). - roomtest.c + pop_guard.h: чит-навигация ставит Кида на верхний ряд в комнате 14 уровня 7 (GRAB_DOWN_LEVEL/ROOM) — снизу этот спуск не проверить. Общее правило (снизу вверх) не тронуто: в верхнем ряду пола чаще нет и Кид проваливается сразу после телепорта. Проверено: tests-host 5/5 (55 в [grab]), size-check OK, живьём в MAME — Кид повис на кромке (2,2): frame=91 y=55 row=0 room=15. Доски: GRAB-BELOW-ROOM в BUGS_CLOSED, GRAB-KBD-TIMING в BUGS_OPEN (отложен пользователем до готовности всех уровней), план L7-FEATHER в TASKS_OPEN. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
abe4a83d38 |
Уровень 5: тень не дерётся, кнопка в шве, узор паласа после зелья
Три бага одной отладочной сессии, все проверены в MAME пользователем. 1. SHADOW-FIGHT-L5. Кид доставал меч, а тень вставала в боевую стойку и рисовалась спрайтами стража. В pop_check_can_guard_see_kid первым множителем стояло `Guard.charid != 0`, тогда как оригинал (seg003:702) пишет `Guard.charid != charid_1_shadow || current_level == 12`: тень боевая только на 12-м уровне. Оба симптома шли из одной ветки control_standing (seg005:352). Возвращён и пропущенный множитель `Guard.direction != dir_56_none`. 2. SEAM-BUTTON-STALE. Кнопка в шве не меняла вид ни при нажатии, ни при отжатии. Сигнатура редроя шва сравнивала room_modif, а у кнопки modif — это индекс LINKLOC, константа уровня: нажатие живёт в doorlinks2, и в сигнатуру не приходило никогда. seam_row_sig подмешивает бит нажатости. Оригинал шов в этом случае не перерисовывает вовсе — расхождение осознанное, записано как D-2 в docs/impl_diff.md. 3. BUG-POTION-STRIPE. Синяя лента паласа пропадала на месте выпитого зелья и не возвращалась даже после перезахода в комнату. У оригинала curr_room_tiles — сама таблица уровня, у нас fg живёт в двух местах, и страницу правил главный цикл уже после pop_process_trobs. В этот зазор trob зелья крутил фазу пузырька поверх обнулённого модификатора: bubble_next_frame(0) = 1, а для пола в паласе modif 1 означает «узор не рисовать» (seg008:499). do_pickup теперь правит страницу уровня сразу. Тем же лечится меч: animate_sword делал `--mod[tp]` из нуля. tests-host 5/5 (добавлена заглушка pop_level_set_tile), size-check чист. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
36a60f54ba |
Кнопка в ШВЕ срабатывала как чужая связь (нашёл пользователь на ур. 5)
Симптом: Кид встаёт на плиту, которая физически в СОСЕДНЕЙ комнате (стоя в шве, curr_col = −1), и вместо одних ворот открываются двое — на уровне 5 плита комнаты 11 открывала и нижние ворота комнаты 24, и верхние, хотя связана только с нижними. Причина. check_press читал тайл через get_tile (тот резолвит шов в соседнюю комнату), а комнату и tilepos для trigger_button брал из координат персонажа: (своя комната, row*10 + curr_col). При curr_col = −1 это tilepos 9 СВОЕЙ комнаты — там стена с bg = 0, то есть индекс цепочки LINKLOC 0. Цепочка от нуля в данных уровня 5 ведёт на ДВА тайла: нижние ворота (link[0], next=1) и верхние (link[1]) — ровно то, что наблюдалось. Настоящая кнопка имеет индекс 9 и одну цель. В оригинале этого нет по построению: get_tile зовёт find_room_of_tile (seg006:005D) и ПЕРЕСТАВЛЯЕТ curr_room/curr_tilepos, а trigger_button работает уже с ними. У нас резолв комнаты жил только внутри get_tile, а наружу не отдавался. Фикс: tile_room_of(col,row) — комната и tilepos клетки с учётом швов (по образцу gate_modif, который так делал давно), check_press зовёт trigger_button с резолвнутыми room/tilepos. Ветка loose там же оставлена на координатах персонажа: make_loose_fall работает с g_fg своей комнаты. Два других вызова trigger_button (зацеп за кромку, севшая на кнопку плита) правки не требуют — оба ограничены колонками 0..9 своей комнаты. Заведён GATE-FORE-KID (BUGS_OPEN.md): Кид, стоящий В ПРОЁМЕ ворот, виден поверх решётки — у нас портирован только шовный случай окклюзии, а draw_tile_fore (seg008:0D15) рисует решётку поверх персонажа и внутри комнаты. Чинить в fore-слое отдельно, он горячий. tests-host 5/5, make size-check OK. Банк 3: 10648 -> 10828 Б. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8dc53e6b57 |
L5-SHADOW: тень уровня 5 крадёт зелье (спецсобытие комнаты 24)
Порт seg002: check_shadow (0064), do_init_shad (0000), do_auto_moves (1089),
autocontrol_shadow_level5 (1157) + таблица shad_drink_move (data.h:866).
Механика. При входе в комнату 24 уровня 5, если зелье (кол 3, ряд 0) ещё
на месте, слот соперника занимает ТЕНЬ — и занимает его ВМЕСТО стража:
enter_guard в этой комнате оригинал не зовёт НИКОГДА (в данных страж есть,
guards_tile[23] = 8, но check_shadow уходит в return до него — в том числе
когда зелье уже выпито). Поэтому pop_check_shadow вернула 1 = «событие
обработано, стража не поднимать», и вызов стоит ПЕРЕД pop_guard_enter.
Тень стоит, пока не откроются ворота комнаты (openness >= 80), затем
проигрывает ЗАПИСЬ ходов: подойти, взять, выпить, развернуться, уйти за
левый край. do_auto_moves — тот же движок, что у демо-режима заставки;
тонкость выбора записи (при достигнутом времени индекс сдвигается, а ход
берётся из ещё не сдвинутой записи) портирована дословно.
Три вещи, найденные по дороге:
1. ГЕЙТ ВЫКЛЮЧЕННОГО ПЕРСОНАЖА. play_guard_frame (seg000:1248) начинается
с `if (Guard.direction != dir_56_none)`, и clear_char выключает
персонажа именно так — он не трогает charid. У нас гейт был только по
charid, поэтому ушедшая за край тень продолжала тикать: do_auto_moves за
концом таблицы отдаёт последнюю запись {0x31,1} = «вперёд», тень
разворачивалась, вбегала обратно, пробегала верхний ряд и падала в проём
(наблюдение пользователя в MAME). Гейт добавлен и в pop_guard_tick, и в
pop_guard_phys_tick.
2. ЭФФЕКТ ЗЕЛЬЯ — ТОЛЬКО КИДУ. proc_get_object (seg006:16CB) начинается с
`if (Char.charid != charid_0_kid || ...) return`: предмет забирает любой
персонаж, а эффект достаётся только Киду. На этом стоит всё спецсобытие
— тень пьёт, зелье исчезает и не достаётся никому. Без проверки
hitp_delta уходил бы Киду, то есть тень его ЛЕЧИЛА бы.
3. ВЕТКА ТЕНИ в check_guard_fallout (seg002:0241): исчезает, только если
реально падает (action == 4). Была помечена в коде как «появится с
L3-SKEL» — на самом деле она про тень.
Чит ROOMNAV: телепорт ставил Кида в первый попавшийся пол, то есть почти
всегда (0,0). Теперь ищет пол С МАКСИМАЛЬНЫМ Y (ряд 2 -> 1 -> 0, по
просьбе пользователя): в комнате 24 старая посадка была вплотную к тени, и
вместо сцены кражи начиналась схватка, которой там быть не должно.
Отладочный тумблер POP_DBG_SHADOW_NOWAIT (pop_tune.h, по умолчанию 0) —
посмотреть сцену, не проходя уровень до кнопки.
Проверено в MAME (уровень 5, комната 24): при закрытых воротах тень стоит
за левым краем и не видна; с тумблером — подошла, выпила (зелье исчезло),
развернулась, ушла за край, выключилась и НЕ вернулась. tests-host 5/5,
make size-check OK. Цена: резидент +102 Б (24637 -> 24739), банк 1
+368 Б, банк 3 +11 Б.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
8f0362f3d4 |
L4-MIRROR шаги 3 и 5 + левый клип колонок в libbgi
libbgi: новая gfx_blit_cols_part_wx — обрезка СЛЕВА (skipw) в дополнение к верхней (skip/rows) и правой (maxw). По подсказке пользователя сделано примитивом, а не обходным путём «нарисовать и вернуть фон поверх лишнего»: для column-major левая обрезка стоит ровно столько же, сколько правая — колонка это непрерывный кусок ОЗУ, меняется стартовая колонка источника и экранная X. Внутри это уже было (так клипается левый край экрана), наружу не было выведено. Полное тело блита колонками переехало туда, gfx_blit_cols_part_w стала обёрткой (skipw=0). size-check: OK, роста нет (62 программы, -22..-33 Б на пользователей блита). Шаг 5 — клип ТЕНИ (seg008:1699): на уровне зеркала она показывается только СПРАВА от него, obj_clip_left = 137 + (mirror_column-4)*32. Шаг 3 — ОТРАЖЕНИЕ (check_mirror, seg003:0798): пока Кид стоит на тайле зеркала, каждый кадр рисуется его зеркальная копия с клипом left = (curr_col<<5)+9 и top = y_clip[curr_row+1]. Отдельной функцией pop_mirror_draw, а НЕ третьим слотом Char — это структура самого оригинала: отражение идёт сокращённым путём load_frame_to_obj + add_objtable(4), без клинка, брызг, fore-прохода и пропуска кадра; гейтить всё это в общем теле значило бы добавить ветки в самый горячий путь. Свой heal (pop_mirror_heal) рядом с pop_char_heal, в skip-маске не участвует. tests-host: заглушки pop_mirror_draw/pop_mirror_heal (pop_map теперь на них ссылается), все 5 наборов прошли. Цена: _CODE 24501 -> 24629, банк 3 10551, банк 4 8267 -> 9590. НЕ ПРОВЕРЕНО В MAME. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
844fa6d767 |
L4-MIRROR шаг 4: прыжок сквозь зеркало и рождение тени
Порт seg003:0798..08A9 + seg004:0239 + seg002:081D/1131 + seg006:1945. - is_obstacle (pop_map.c): ветка зеркала — Кид, кадры бегового прыжка 39..43, направление ВЛЕВО -> modif = 0x56, pop_jumped_mirror = -1, препятствия нет (пролетает насквозь). - mirror_image / jump_through_mirror / pop_check_mirror (pop_map.c): отражённый Char уходит в слот Guard как CHARID_1_SHADOW, guardhp = hitp_max, у Кида hitp_curr = 1. savekid НЕ делается — как в оригинале, отражается только копия. Полосы HP перерисует pop_hp_draw сам. - pop_check_mirror() зовётся из главного цикла ПЕРЕД отрисовкой персонажей (в оригинале — первая строка draw_people, seg008:228A). - autocontrol_shadow + autocontrol_shadow_level4 + clear_char (guards.c): тень идёт СВОЕЙ веткой целиком, к стражьему ИИ не сводится — на уровне 4 она не дерётся, а бежит влево и при x < 80 исчезает. АТЛАС ТЕНИ — вскрылось при чтении seg006:0532. Тень вне боевых кадров 150..189 ходит по таблице КИДА, и image оттуда индексирует спрайты Кида, а не стража. Выбор атласа в pop_cdraw шёл по СЛОТУ, то есть тень рисовалась бы спрайтами стража. Условие вынесено в pop_frame_tbl_is_guard() (pop_kid.c) — его теперь читают и load_frame, и отрисовка, разъехаться не могут. Заодно из pop_load_fram_det_col выделен pop_load_frame() без determine_col: jump_through_mirror берёт ось отражения из curr_col, и пересчёт колонки по x там был бы вреден. Константы MIRROR_* и DIR_56_NONE переехали в pop_guard.h — нужны и постановке тайла (банк 6), и ИИ тени (банк 1). Звука sound_45_jump_through_mirror в порте нет, пропущен. Шаг 5 (клип тени слева от зеркала) НЕ сделан и оказался не однострочником: в pop_cdraw есть клип сверху/снизу/справа, левого нет вовсе — нужен новый примитив либо срез исходных колонок. Расписано в TASKS_OPEN. Цена: _CODE +18 Б, банк 1 2311 -> 2367, банк 3 10328 -> 10551, банк 4 8243 -> 8267. tests-host: все 5 наборов прошли. НЕ ПРОВЕРЕНО В MAME — сценарий проверки записан в TASKS_OPEN. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4718bff767 |
Уровень 4: skip по маске тайлов + порт loose_land, ворота 0xFF, пламя в фон
ОПТИМИЗАЦИЯ. Метка «фон трогали» была одним union-прямоугольником на страницу, и три факела комнаты склеивались в полосу x 40..280 на всю комнату — Кид, стоящий между крайними факелами, терял пропуск перерисовки и каждый кадр платил полным fore-проходом. Теперь это маска тайлов (uint16_t pop_cd_dmask[2][3]: бит = колонка, слово = ряд, набор = страница), проверка — три AND через резидентный pop_cd_hit. Гранулярность тайла — это гранулярность оригинала (redraw_frames_anim[tilepos], set_wipe; подтайловое уточнение там только по высоте, wipe_heights). Замер на (1,7): циан 233 515 -> 24 781, работа за кадр 517 609 -> 306 553, период 4 -> 3 растровых кадра. BUG-LOOSE-BUTTON-1. Упавшая плита не нажимала кнопку. Три слоя: порт loose_land не звал trigger_button вовсе; pop_room_col_landing считала площадкой только чистый пол, а у оригинала их семь (пол, пика, обе кнопки, зелье, оба факела); сигнал приходил в момент ОТРЫВА плиты, из-за чего ворота начинали открываться, пока она ещё в воздухе. Нажатие в оригинале ОДНО, но с button_type = tiles_14_debris — это «открыть НАСОВСЕМ» (modifier 0xFF), и кнопка съедается. Посадка в комнате снизу переехала на новый сигнал pop_loose_exit (взводит mob_tick_one, когда кусок ушёл ниже поля). BUG-GATE-FF-1. 0xFF был перегружен: сторожевое «тайла нет» в gate_modif и живое «открыто навсегда» из trigger_gate. gate_passable заворачивал Кида в воротах, нарисованных открытыми. Мёртвая ветка убрана. BUG-TORCH-CHOMP-1. Чомпер (0,7) комнаты 23 healит x 224..255 / y 30..93 и стирал пламя факела (0,6), которое рисуется в ячейке правого соседа. Фикс — запекать пламя (GFX_BANK_NORMAL): у факела все девять кадров на общем канвасе 16x18 и непрозрачны, протухнуть в ОЗУ-копии нечему, а heal чомпера сам возвращает огонь и кладёт челюсти поверх — z-порядок как в оригинале. Пузырёк зелья так нельзя (ползёт вверх, нужен heal) — остаётся в SPRITE. Заведено открытым: died_on_button (seg007:776) не портирован — нужен тайл tiles_5_stuck в атласе. Проверено пользователем в MAME (уровень 4: плита 16(1,1) -> кнопка 17(0,1) -> ворота 23(0,9); комната 23 с чомпером и факелом); make -C tests-host — все 5 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6a824dd7ab |
Закрыты BUG-GUARD-IX-1 и BUG-GATE-SEAM-ROW1 (уровень 4)
BUG-GUARD-IX-1 — «зависание» в бою со стражем. Не зависание: главный цикл крутился, а отладочная локаль frozen сама становилась ненулевой. Корень — у main затирался IX (0xBFFA -> 0xBF00), и все его локали адресовали живой стековый мусор. Затирал check_chomped_guard: coll_row() пишет по flags + scan_off, длину берёт из win_lo/win_hi, а scan_off выставляла только coll_scan_prepare() из пути Кида. Ряд стража писался по смещению Кида длиной стража и при Киде у правого края комнаты вылезал за flags[13] — прямо в сохранённый IX. Фикс: coll_scan_prepare() в начале get_row_collision_data(); заодно чинится расчёт (scan_left0 задаёт x колонок, чомпер-коллизия стража считалась по координатам Кида). На уровне 1 не проявлялось: бой идёт левее середины, запись оставалась внутри массива — данные были неверны молча. BUG-GATE-SEAM-ROW1 — решётка в шве не анимировалась. Плита комнаты 1 открывает ворота комнаты 8 в (1,9), видимые через левый шов, а change-driven редрой смотрел только m[9] (ряд 0) и перерисовывал жёстко draw_tile(0,0). На уровне 1 та же связка работала лишь потому, что решётка соседа стояла в (0,9). Фикс: сигнатура по всем трём рядам, маска изменившихся рядов в seam_rows (переживает оба кадра дабл-буфера), pop_room_redraw_seam_left(rows) перерисовывает только помеченные. Плюс ряд выше (changed | changed>>1): верх решётки (draw_tile_anim_topright, seg008:0568) рисует тайл над-справа от ворот, без этого чёрный треугольник над ними оставался статичным. Проверено пользователем в MAME (бой в комнате 18; анимация решётки в стартовой комнате), детекторы IX висели без починки и не сработали; make -C tests-host — все 5 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0941ef1d90 |
Шаг D: паласные ветки отрисовки + потерянный верх ворот
Порт семи мест seg008, расходящихся по tbl_level_type, плюс общая дыра порта, которая на дворце стала видна. Паласные ветки (все — «в подземелье этого нет»): - doortop_fram_top / doortop_fram_bot (seg008:413, 506): декоративная панель над воротами. У шва она и есть тот «ковёр», которого не хватало. - stripe_id соседа слева (seg008:486): орнаментная лента под окнами. Она непрерывная, потому что stripe_id = 145 у пола, кнопок, зелья, loose, чомпера и меча; без неё лента шла кусками (только blueline). - полоска на стене id 84 (seg008:510), при (modifier & 0x80) == 0. - blueline_fram3: условие `num == !!level_type` — в подземелье пропускается num==0, в паласе num==1 (seg008:501). - левая половина кнопки-opener без пола (id 148) — только подземелье (seg008:628). - склянка зелья: id += 2 во дворце (seg008:747). - remove_loose возвращает ТИП УРОВНЯ, и он ложится модификатором пустой клетки от упавшей плиты (seg007:846/1083). Потерянный вывод (НЕ паласное расхождение, просто заметили здесь): draw_tile_anim_topright (seg008:0568) не был портирован вовсе — верх ворот, который рисует тайл НАД ними: маска 68 (mono, чёрным) + door_fram_top [(modifier>>2) % 8] = 60..67. Ids 60..68 в атлас не паковались. Симптом — чёрный клин над воротами; нашёлся сравнением с эталоном SDLPoP (--screenshot) и трассой add_backtable. Флаг тайлсета pop_palace вынесен в резидент (pop_tile.c): по нему расходятся ветки в банке 7 (полная отрисовка), банке 2 (fore-проход) и банке 3 (модификатор пустой клетки) — читается напрямую, без трамплина. Известное расхождение: модификатора ряда СНИЗУ у нас нет (pop_t_below — только fg), поэтому паласная панель над воротами в комнате снизу не рисуется. Помечено в коде. tests-host: 5/5 (добавлен include-путь до pop_bg_atlas.h и стаб pop_palace). Проверено в MAME: уровень 4 совпадает с эталоном SDLPoP в рядах 0-1 попиксельно (кроме фазы пламени); уровень 1 не изменился. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b0524b9ad0 |
Оптимизация: логический кадр уложился в бюджет, цикл 4 растровых кадра -> 3
Главный цикл спейсится тремя gfx_wait_vsync, поэтому работа сверх 430 000 тактов стоит сразу целый лишний растровый кадр. Было 470 964, стало ~425 600 — игра быстрее на треть (16,7 логических кадров/с против 12,5). - pop_y_to_row: цепочка сравнений вместо (y+60)/63%4-1. ВАЖНО: медленный хвост вынесен в ОТДЕЛЬНУЮ функцию — SDCC видит одинаковое выражение в двух ветках и поднимает деление в вершину, быстрые возвраты не спасают. - col_from_x (pop_bg) и get_tile_div_mod (pop_map) — общие резидентные таблицы POP_TILE_DIV/POP_TILE_MOD в pop_tile.c (const банка из чужого банка не читается). - pop_fore_over_char: расширение окна считается арифметикой, а не перебором 10 колонок и 3 рядов (условие монотонно -> границы). Формулы сверены с прежним перебором перебором значений, расхождений нет. - pop_cd_touch: цикл по страницам развёрнут, x+w/y+h считаются один раз. Зовётся с каждого блита фона, стоил 6 846 тактов. - process_trobs: tp/10 и tp%10 у факелов — таблицей. - Пустой слот соперника (стража на сцене нет, на странице ничего не нарисовано) считается «тихим»: ни heal, ни вход в pop_char_draw, ни fore-проход. Приём для поиска делений: брейкпоинт на __divsint/__divuint/__divuchar с печатью адреса возврата (printf "%04X", w@(sp)). Профиль остатка — TASKS_OPEN.md#draw-cost. tests-host: 5/5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d0030922ff |
Оптимизация логики, шаг 2: пробеги вместо ветвления на колонку
- move_coll_to_prev -> memcpy (LDIR): цикл на C пересчитывал адрес назначения через слот кадра IX и обходился в 5 514 тактов на 14 байт. - Окно перебора режется на НЕПРЕРЫВНЫЕ пробеги (комната слева / своя / справа), по каждому идёт coll_scan с шагающим указателем. Прежний «быстрый путь для окна внутри комнаты» не срабатывал почти никогда: Кид в колонке 0 даёт окно с −1, и всегда шёл медленный сбор во временный буфер с тернарником на колонку (1 340 тактов на колонку). - Пролог ряда (координата грани, длина окна, смещение слота) вынесен из тела ряда на кадр; базы соседних рядов — те же ±10 без пересчёта. check_collisions 44 022 -> 38 334, физика Кида 67 518 -> 63 102, работа за логический кадр 470 964 -> 466 560 (бюджет растрового кадра 430 000). Замер итерации: пустая колонка 750 тактов, колонка-стена ~1 700. tests-host: все 5 наборов прошли. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8c4bc4f621 |
Оптимизация логики: окно коллизии как в оригинале + деление таблицей
Замерено брейкпоинтами в MAME (уровень 1 комната 1, Кид стоит у факела). Калибровка, без которой цифры не сходятся: такт totalcycles != номинальный T-такт Z80, wait-state'ы ОЗУ Sprinter дают ~2,4x (get_tile 574 против 1422). 1. Окно перебора коллизии — как у оригинала (left_checked_col..right_checked_col, seg004:0047), было: все 14 колонок каждый кадр. Признак годности слота у нас дешевле оригинального: не массив номеров комнат с очисткой, а границы окна, которые move_coll_to_prev переносит в prev вместе с флагами; бамп считается по пересечению двух окон. check_chomped_flags тоже ограничен окном, иначе протухшие слоты дают фантомный перемол. 2. get_tile_div_mod — таблицами tile_div_tbl/tile_mod_tbl (seg006:702), было /14 и %14. SDCC разворачивал это в __divsint + __modsint, а __modsint внутри зовёт __divsint ещё раз: 5 400 тактов на вызов, 13 вызовов за кадр = 16 % кадрового периода на «в какой колонке точка». 3. get_row_collision_data: ряд разрешается один раз на весь перебор (было — get_tile на каждую колонку, 1 422 такта), грань идёт шагом TILE_SIZEX как в оригинале, wall_type таблицей вместо switch. Итог: check_collisions 60 888 -> 42 750, физика Кида 100 578 -> 67 518, синяя полоса ~60 % -> ~30 % кадрового периода. Профиль остатка и следующие цели (отрисовка Кида 47 %, process_trobs 21 %) — в TASKS_OPEN.md#draw-cost. tests-host: все 5 наборов прошли. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9234c03020 |
BUG-GATE-PASS-1: история флагов коллизии переживает боковой переход
Оригинал (seg004:0004) индексирует флаги перекрытия колонкой ВНУТРИ разрешённой комнаты и хранит рядом её номер, поэтому решётка комнаты 8 остаётся в своём слоте и после перехода 8->6: переход флага 0->1 виден, bumped() срабатывает. У нас индекс — колонка отрисованной комнаты, тот же тайл менял слот, и enter_room вынужден был выбрасывать историю целиком — на кадре входа бампа не было, и Кид с разбега уходил сквозь закрытые ворота. Вариант B (сдвиг вместо тега комнаты): при БОКОВОМ переходе история не выбрасывается, а перенумеровывается на 10 слотов. check_leave двигает x ровно на ∓140 = 10 тайлов, координата грани едет на те же 140 вместе с габаритом Кида — сами флаги инвариантны, меняется только номер слота. Сдвигаются curr/above/below (prev на следующем кадре всё равно перезапишет move_coll_to_prev), освободившиеся слоты = 3 «уже перекрывал». Вверх/вниз и прочие входы в комнату — по-прежнему полная инвалидация. Дословный вариант A (10 слотов + массив номеров комнат) не взят: он тянет за собой сужение окна перебора колонок, то есть отказ от FIX_COLL_FLAGS. Заведён docs/impl_diff.md — список осознанных расхождений с SDLPoP; правило «фиксировать расхождение» в обоих CLAUDE.md теперь указывает туда. tests-host: все 5 наборов прошли. Приёмка в MAME впереди. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
db4106a55e |
L3-CHOMP: перед чомпером Кид разбегается сразу, без осторожного шага
forward_pressed (seg005:0577) исключает чомпер из правила «у стены шагаем, а не бежим»: `edge_type == EDGE_TYPE_WALL && curr_tile2 != tiles_18_chomper && distance < 8`. У нас исключения не было, а wall_type(18) = 3 — чомпер считается стеной, — поэтому из позиции вплотную к челюстям Кид сначала делал safe_step на 1-2 px и только потом бежал. Лишние кадры шага не дают проскочить между челюстями (поймано на приёмке, комната 3.22). Добавлен pop_edge_tile() — порт curr_tile2 после get_edge_distance. control_turning не трогаем: у нас он ванильный, а второе такое исключение в SDLPoP сидит под фиксом fix_turn_running_near_wall. |
||
|
|
dc0bd47368 |
L3-CHOMP: чомперы — анимация, отрисовка и смерть в челюстях
Порт SDLPoP: animate_chomper / start_chompers / start_anim_chomper / next_chomper_timing (seg007) -> pop_trob.c; draw_tile_anim + draw_tile_fore, ветка tiles_18_chomper (seg008) -> pop_room.c (низ/кровь/верх, backtable) и pop_bg.c (передний слой); check_chomped_kid / check_chomped_guard / chomped (seg004) -> pop_map.c. Состояние — в room_modif, как у пик и ворот: младшие 7 бит фаза 1..N, старший бит «перемололо кого-то» (кровь остаётся на тайле навсегда). Номер позы chomper_fram1 и передние куски лежат в РЕЗИДЕНТЕ (pop_tile.c): их читают обе половины слоя фона, а const-таблица банка из чужого банка не видна. start_chompers зовётся там же, где в оригинале: SEQ_UP/SEQ_DOWN в play_seq (pop_kid.c), start_fall и land (pop_map.c), вход в комнату (roomtest.c). Поэтому чомперы щёлкают только пока персонаж в ИХ ряду — так в оригинале. check_chomped_guard у оригинала отдельное тело (страж не проходит через check_collisions). У нас та же формула уже есть в get_row_collision_data, поэтому флаги ряда считаются во ВРЕМЕННЫЙ массив: coll_curr/above/below — это кадр Кида, из них move_coll_to_prev берёт прошлые флаги, затирание сломало бы ему бамп. Период смыкания — POP_CHOMPER_SPEED в pop_tune.h (15, как в оригинале). Заодно: устаревшая заглушка pop_fore_set_clip в tests-host была __banked, хотя функция давно переехала в резидент — всплыло при пересборке. Ассеты уже были упакованы (pop_pack_bg.py, 2026-08-07). Банк 6: 20.0 %, банк 7: 39.7 %, банк 3: 53.8 %. tests-host зелёные (5 наборов). Зубья в MAME рисуются; анимация и смерть — на ручной приёмке. |
||
|
|
bbf91d10ee |
DRAW-CHAR: отрисовка одна на всех Char; разгрузка банка 2 (90.4% -> 72.9%)
DRAW-CHAR. Отрисовка персонажа сведена к одному набору функций над Char — как физика после GUARD-PHYS. В оригинале add_kid_to_objtable (seg008:22F0) и add_guard_to_objtable (seg008:2324) имеют идентичное тело и различаются окном (loadkid/loadshad), набором спрайтов и типом объекта, а redraw_at_char/redraw_at_char2 гейтов по charid не имеют вовсе. pop_gdraw.c -> pop_cdraw.c: pop_char_draw/heal/fore(who), слот POP_CH_KID / POP_CH_OPP; состояние слотов pop_cd[] в _DATA — читается из любого банка без трамплина. Проход окклюзии тоже один (pop_fore_over_char), pop_fore_over_kid больше нет. Починилось само (расхождения, которые и были ценой дублирования): у соперника не было clip_char; у Кида не было клипа полем 192 и ветки брызг «мёртв/падение»; char_width_half СТРАЖА считался по спрайту КИДА. Замер: _CODE 24 881 -> 20 524 (куча 2023 -> 6333), BANK2 -265, итого -3.2 КБ. Проверено пользователем в MAME; циан-полоса профиля подросла — оптимизация заведена отдельной задачей DRAW-COST. MEM-BANK2, шаг 1: общие «листья» слоя фона в РЕЗИДЕНТ (pop_tile.c/.h). Ограничение платформы: писучие данные банка лежат в _DATA и видны всем, а const-таблицы — в странице банка, из другого банка их не прочитать; трамплин же выбирается объявлением, то есть __banked на листе бьёт и по горячим вызывающим (654 такта). W1 замаплено всегда — оттуда обе половины зовут листья прямым call и читают таблицы напрямую. MEM-BANK2, шаг 2: дедуп внутри банка. wall_pattern 808 -> 394 и wall_rnd 786 -> 654: четыре ветки по виду стены отличались только набором кусков и числами в одной серии prandom — сведены к таблицам WP_PARTS и WR_RULE, порядок вызовов prandom сохранён дословно. Заодно: kid_seq_off больше не static const в kid_data.h (230 Б мёртвой копии в каждом из 9 модулей) — генератор pop_extract_kid_data.py отдаёт макро-инициализатор, массив определяет один pop_kid.c. Итог: BANK2 14 815 -> 11 942 (72.9 %, свободно 4442 Б), _CODE 22 556, куча 4301 Б. tests-host зелёные (65/39/53/1723/1); в MAME комната 1 совпала с дорефакторным снимком попиксельно (0 из 227 520), комната 3 — та же раскладка кладки. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6673279cef |
PoP: скелет уровня 3, цвета стражей, окклюзия соперника; кэш соседних комнат
Скелет (L3-SKEL, ассеты + механика): - pop_pack_guard.py получил параметр набора (GUARD/SKEL): атлас скелета poc/res/skel/g0..g3.atl (28 кадров), палитра — из его res750.pal (на ур. 3 curr_guard_color = 0, оригинал палитру не подменяет); - pop_guard_load выбирает набор по tbl_guard_type и перезагружается ПРИ СМЕНЕ УРОВНЯ (load_lev_spr, seg000:1092) — без этого скелет рисовался атласом стража и был невидим; - load_frame: charid_4_skeleton идёт по таблице стража (seg006:529), тень — только в кадрах 150..189. Пока ветка была одна (charid_2_guard), скелет получал image из таблицы Кида (180 при 28 спрайтах) и не рисовался; - check_skel (seg002:1042), ветка charid_4 в enter_guard, возрождение в комнате 3 при падении (seg002:252), autocontrol_skeleton; - leveldoor_open (seg007:456) — новый флаг, сбрасывается стартом уровня. Цвета стражей (BUG-GUARD-COLOR-1, закрыт): - все 7 палитр res10.bin -> pop_guard_pal.h, заливка 16 слотов по guards_color комнаты перед отрисовкой (set_chtab_palette, seg003:257). Проверено в MAME: ур. 2 комн. 11 = цвет 1, комн. 7 = цвет 3, полоса HP меняется вместе со стражем. Грабля: gfx_pal_load отдаёт указатель в BIOS, а тот читает только #4000-#BFFF — таблицу из банка копируем в стек. Кэш соседних комнат (BUG-SWORD-GHOST-1, закрыт): - pop_map кэширует fg соседей слева/справа ЦЕЛИКОМ и резолвит col -10..19. Было -2..11, дальше мнимая стена: луч видимости упирался в неё (страж после follow_guard в col 12), Кид прятал меч посреди боя и не мог достать обратно. +48 байт W2. Окклюзия соперника: - pop_fore_over_char получил проход other_overlay_tile (порядок midtable, seg008:1B06) и расширение перебора объединённым прямоугольником «персонаж + клинок + брызги» — падающий скелет больше не рисуется поверх кладки и верхней грани пола; - клип полем 192 строк (reset_obj_clip, seg006:0507) для спрайта, клинка (общий pop_sword_draw) и брызг — спрайт не залезает на полосу HP; - ROOMNAV после смерти Кида делает честный pop_start_level: телепорт «оживлял» мёртвого мимо старта уровня, оставляя живого скелета рядом с вернувшейся кучей костей. Ассеты чомпера (под L3-CHOMP): весь набор кадров в атласе явным списком (101-105 низ, 111-113 верх, 106-110 фронт, 114-123 кровь mono-силуэтом) — render_room анимированные тайлы пропускает, и в атласе не было ни одного. Число EMM-страниц не изменилось. Тесты: tests-host все 5 наборов зелёные, t_char вырос до 65 проверок (резолв колонок за краем комнаты, возрождение скелета); в testkit добавлен гард «код наехал на данные» (DATA_LOC). Доски: TASKS.md разнесён на TASKS_OPEN/TASKS_CLOSED, закрытые баги с разбором корней — в bug_closed.md; заведены DRAW-CHAR (отрисовка одна на всех Char, как физика после GUARD-PHYS) и L3-COLOR (зелёная кладка уровня 3: level_var_palettes = ресурс 20, есть в MSDOS/PRINCE.DAT). В roomtest.c временно оставлен автостоп на падении соперника (отладка падений скелета) — помечен ВРЕМЕННО. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0cd6b2d737 |
L3-CHKP, зацеп в прыжке опцией, pop_tune.h; сборка 10 мин -> 1:48
L3-CHKP (чекпойнт уровня 3). Флаг взводится, когда Кид уходит ВЛЕВО ИЗ комнаты 7, а do_startpos по нему подменяет старт на комнату 2, тайл (0,6), лицом влево и снимает loose-плиту (7,0,4). Тонкость, на которой я сначала ошибся: level3_set_chkp (seg002:0665) вызван из leave_room ДО goto_other_room, поэтому `Char.room == 7` — это комната, ИЗ которой уходят, а не в которую входят. Поймал пользователь прогоном в SDLPoP: смерть В комнате 7 вернула его в стартовую 9, а плита осталась цела. Проверено в MAME: вход в 7 флаг не ставит, уход влево — ставит; респавн в комнате 2; после обычной смерти (без чекпойнта) плиты восстанавливаются как раньше. Зацеп ПРЯМО В ПРЫЖКЕ (check_grab_run_jump, seg006:1228) — портирован за выключателем POP_ENABLE_JUMP_GRAB. Это НЕ ваниль: в оригинале зацепиться можно только в начале падения (кадры 102..105), то есть Shift приходится жать уже в полёте; у SDLPoP это enable_jump_grab, и работает он лишь при включённых fixes-and-enhancements. Три точки вызова как у оригинала: check_action и обе ветки check_bumped (зацеп за верх стены вместо удара). pop_tune.h — настраиваемые константы в одном месте (аналог custom_options_type SDLPoP): чекпойнт, выключатель зацепа и отладочная крутилка POP_DBG_GATE_HOLD (сколько кадров решётка держится поднятой; оригинал 5, потолок 30 — таймер связи пятибитный, 31 = «заклинено»). Задача TUNE-1 в TASKS.md: читать это из ini рядом с exe. СБОРКА. --max-allocs-per-node снижен со 100000 (дефолт sprinter-cc) до 3000 (дефолт SDCC) через `make ALLOCS=...`, а pop_trob.c уехал в БАНК 6 — на 3000 резидент иначе не влезает (замер: конец _HOME 0xBC69 при стеке с 0xBB00). Итог: сборка с нуля 1:48 вместо >10 минут, куча 2751 Б вместо 2298. Релизная сборка — make ALLOCS=100000; сравнивать занятость банков можно только при одинаковом ALLOCS. Отдельно (вне git, SDLPoP в .gitignore): из референса вычищена вся наша отладка DBG-GRAB — трасса JMP, GRAB try/probe/fail/OK/skip, автоскриншоты seg003, печати DBG mob/mid/overlay/kidobj и счётчик dbg_shots. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dc8b2b7115 |
Приёмка ур. 2–3: сквозь стену, клин в шве, бар под плитой, глухой страж
Четыре разбора с прогонов пользователя; три бага закрыты, четвёртый (BUG-SPIKE-1, пики) заведён с замером и гипотезой. BUG-JUMPWALL-1 (Critical) — недолетевший прыжок проходил СКВОЗЬ стену. check_collisions держал флаги перекрытия ОДНОГО ряда, а оригинал (seg004:0004) — трёх, и move_coll_to_prev (seg004:00DF) берёт «прошлые» флаги из нужного. Наш prev=3 («уже перекрывал») подавлял бамп ровно на кадре смены ряда, а в падении ряд меняется почти каждый кадр — переход 0→1 на стене приходился как раз на него. Порт трёх рядов дословно. Воспроизведено и закрыто на харнессе (новый набор t_wall: свип по 20 стартовым X, 4 давали проход сквозь кладку); 1723 трассы t_phys НЕ изменились — правка поведение-сохраняющая. Живьём подтвердил пользователь. BUG-GUARD-DEAF-1 (Major) — is_guard_notice не взводился НИГДЕ, поэтому неактивный страж не оборачивался на Кида за спиной никогда. Портированы все пять мест оригинала: опкод SOUND в play_seq (звуки 0..2), bumped_sound, мягкое/среднее приземление, обрушенная плита, щелчок кнопки. Ждёт игровой проверки боем в комнате 11 уровня 2. BUG-SEAM-WEDGE-1 — клин кладки в пустом (2,0). Сосед угла снизу-слева лежит в комнате по диагонали (room_BL); мы безусловно считали его стеной, оригинал (load_rowbelow, seg008:368) — только когда такой комнаты нет. Ряд «снизу» стал 11-байтным: [10] = тайл (0,9) диагональной комнаты. BUG-LOOSE-3 — чёрный бар под упавшей плитой-потолком. pop_ceil_bake_empty стирал полосу и восстанавливал только два тайла ряда −1, а в полосу лезет графика соседа слева и верхушки ряда 0. Теперь перерисовываются ряды −1 и 0, колонки col−1..col+1. Проверено попиксельной сверкой с эталонной перерисовкой: 0 различий. Плюс карта связности комнат уровней 1–3 (TASKS.md): на ур. 2 недостижимых нет, на ур. 3 это 23 и 24 — те же односторонние ссылки, что дали 13/18/24 на уровне 1, только комнаты полностью пустые. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cfc3602375 |
L2: падение с мечом, разбег-прыжок через 3 тайла, стартовое состояние ворот
BUG-FALL-SWORD-1 — start_fall (seg006:1044) был портирован не целиком: не хватало трёх веток выбора последовательности и уборки меча в ножны. Из-за seq_7 с set_fall(1,15) (дрейф 1 px/кадр) Кид с мечом уезжал примерно на тайл вбок; в оригинале это seq_81_fightfall — падение строго вниз. Сверено по логу SDLPoP: кадры 102..105 дают x = 155,157,159,160. BUG-RJUMP-1 — run_jump (seg005:0AA8) не выравнивал Кида по кромке пола перед толчком, а был заглушкой «полировка K3». Суммарный dx seq_4 — 62 px при тайле 14, то есть провал ровно в три тайла берётся ТОЛЬКО с кромки: без выравнивания Кид не перепрыгивал его никогда. Порт — pop_run_jump_align() в pop_map (беззнаковое сравнение оригинала = «сдвиг не попал в [-8,-1]»). На харнессе: было — толчок с x=165, кадр 44 в колонке 3 (провал); стало — 5 кадров добега, толчок с x=149, кадр 44 даёт x=87 col=1 row=1. Остальные 8 сценариев не изменились. BUG-GATEMOD-1 — load_alter_mod (seg008:198E) был портирован только для зелий, поэтому ворота с bg=1 («Open» по спецификации DAT, табл. 8) стартовали закрытыми. Добавлены ветки gate (1 -> 188) и loose. Ветка wall намеренно НЕ портируется: связи стен наш pop_bg считает по типам соседей в момент отрисовки. На уровнях 1-3 таких ворот всего двое (ур. 1 комн. 5 (0,9); ур. 2 комн. 13 (1,5)) — у остальных bg=2, а 2 и 0 ведут себя одинаково. Убран временный трассировщик pop_dbg_trace/pop_dbg_draw; pop_dbg_trap() оставлен как многоразовый инструмент. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e67117219f |
BUG-CTRL-FRAME-1: геометрия Кида считалась по кадру стража
cur_frame — один глобал на всех персонажей (как в оригинале), владелец — тот, кто последним прошёл load_frame. Последним в кадре тикает страж, поэтому к моменту control() Кида там лежал кадр СТРАЖА, а через kid_cur_dx/dy/flags по нему считается вся геометрия управления: dx_weight -> determine_col -> distance_to_edge_weight -> get_edge_distance -> выбор ветки в check_jump_up. Оригинал зовёт load_fram_det_col() (seg006:0144) сразу после loadkid/loadshad и ДО control() — play_kid_frame (seg000:1211) и play_guard_frame (seg000:1248). У нас этого не было. Замерено брейкпоинтом на pop_jump_up_seq: Kid x=156 col=6 кадр 15 (dx=0 weight_x=3) при кадре стража image17 (dx=-1 weight_x=8) дал curr_col=7 и distance=2 вместо 6 и 10 — то есть jump_up_plain (вернулось A=28, пустой прыжок) вместо «шаг назад на x=160 + зацеп». Предсказание по кадру стража совпало с намеренным до единицы. Отсюда же плавающее поведение: кадр стража меняется каждый тик, distance Кида скакал через порог 6 — то прыжок, то попытка зацепа с неверной X. И «голова Кида поверх плиты (1,6)» — не баг отрисовки, а следствие позы, которой в оригинале в этом месте не бывает. Фикс: pop_load_fram_det_col() (pop_kid.c) + вызовы в pop_ctrl_tick и pop_guard_tick. determine_col — только на ветке Кида: у нас он существует лишь для него (pop_map работает с Kid, а не с Char). Разбор с числами — bug_closed.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e36828ae6e |
L2: переход между уровнями и игра уровня 2
Порт levels_plan.md §2 (шаг 1) — машинерия смены уровня целиком. - Номер уровня стал состоянием: pop_current_level (порт current_level), pop_next_level больше не флаг, а НОМЕР; главный цикл срабатывает по расхождению next != current (порт play_level_2, seg003:0386). - pop_level_load_num(n): LEVELS\res20NN.bin (fallback a:\), старая EMM-страница отпускается только после успешной загрузки новой. На диск кладутся все 15 уровней (34 КБ). - Потабличные различия (data.h:840..848) — таблицы по 16 в pop_level.c: tbl_entry_pose, tbl_guard_hp (HP стража ушло из хардкода 3), tbl_guard_type (−1 = стражей на уровне нет), tbl_level_type. - find_start_level_door (seg003:02E6): на уровне 2 стартовый тайл — правая половина двери уровня, без modif=43 + add_trob(...,3) Кид материализуется внутри глухой створки. Тип 3 = дверь захлопывается за спиной, как в оригинале. - HP через уровень: hitp_beg_lev (seg003) — рестарт уровня откатывает HP к нему, пройденный уровень подтягивает его к hitp_max. Заодно большая склянка add_life (тип зелья 2: +1 к потолку HP до 10) — на уровне 2 она есть, комната 20. - have_sword = level >= 2 (play_level, seg003:106). - Чит Shift+L — следующий уровень (seg000:698). Проверено в MAME: уровень 1 → Shift+L → уровень 2 (комната 5, новые для нас тайлы 8/9 «большая колонна» на месте, дверь захлопнута, HP 3) → влево в комнату 4 со стражем → Shift+L → уровень 3. Вход в стартовую дверь уровня 2 по Up не срабатывает — как и должно (start_room-гард). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b5d2a81ee3 |
L1: фиксы прогона уровня 1 — уровень мутабелен, стражи, loose-плиты, порядок
Одиннадцать наблюдений первого прогона свелись к шести корням, четыре
наблюдения второго — ещё к четырём. Разбор каждого — bug_closed.md.
Первая волна:
- BUG-LVLSTATE-1: уровень стал мутабельным (эталонная копия foretable для
рестарта, pop_level_set_tile вместо таблицы оверрайдов);
- BUG-RESPAWN-1: рестарт = load_level, тайлы возвращаются из эталона;
- BUG-DEATH-1: смерть от меча доигрывается (порт control_kid, seg006:0CD1);
- BUG-GATE-ANIM-1: ворота в отрисованной комнате перерисовываются
(POP_RD_GATE, порт draw_trob seg007:01E6);
- BUG-COLL-1: полный порт check_collisions/bumped (seg004) вместо поиска
стены только в колонке переднего края;
- BUG-STANDUP-1: убран лишний guard в bumped_floor — вставание у стены
роняло Кида сквозь пол.
Вторая волна:
- BUG-RESPAWN-2: рестарт возвращает и СТРАЖЕЙ (в оригинале play_level на
каждой итерации делает load_level + pos_guards);
- BUG-LOOSE-2: падающий кусок привязан к своей комнате и долетает после
ухода Кида (do_mobs крутит mobs[] независимо от drawn_room);
- BUG-DRAWORDER-1: порядок «Кид / страж» задаётся обходом тайлов
(redraw_needed_tiles: ряды 2,1,0, колонки 0..9), а не ролью персонажа.
По BUG-DRAWORDER-1 понадобилось три захода, и два первых были неполны:
1) сам порядок — но общее окно fore-клипа осталось стражьим, и Кид
нарисовался поверх передних столбов (kid_fore_clip_restore);
2) enter_guard брал curr_col из тайла, а leave_guard пишет туда
get_tilepos(0,row) — у запомненного ТРУПА колонка была 0 при
настоящей X. Теперь колонка выводится из X, как в оригинале;
3) ветка actions_1_run_jump в set_objtile_at_char оказалась не
«упрощаемой»: в беге тайл берётся из нижнего ряда и ЛЕВОЙ колонки
габарита, поэтому бегущий Кид уходит за объекты справа. Считается
для обоих персонажей — enter_guard ставит action=1 и стражу.
Проверено в MAME: зелья/меч/плиты переживают выход из комнаты и
восстанавливаются после смерти; кнопка room5 поднимает решётку; падение с
кнопки больше не роняет в комнату 6; убитый страж жив после respawn;
Кид проходит за телом стража. make size-check — роста нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ba37bd1133 |
BUG-DOOR-CLIP: обрезка силуэта правым косяком двери уровня
Симптом (нашёл пользователь сразу после L1-EXIT): при подъёме по лестнице за
дверью уровня силуэт Кида вылезал ПРАВЕЕ правого косяка проёма; по высоте
обрезка была корректна.
Причина — недопортированная половина clip_char (seg006:1231). Для кадров
двери оригинал ставит ДВА клипа, у нас был только первый:
obj_clip_top = leveldoor_ybottom + 1; // было
obj_clip_right = leveldoor_right; // не было
Отдельная ловушка: комментарий в SDLPoP говорит «frames 217..228», а КОД
проверяет >= frame_224_exit_stairs_8, то есть 224..228 — портировано по коду.
Fore-слоем это не лечится: створка и косяк уходят в оригинале целиком в
backtable (draw_leveldoor, все add_backtable), рисуются ПОД персонажем и
перекрыть его не могут. Единственный способ — срезать сам спрайт.
libbgi: gfx_blit_cols_part_w(..., uint8_t maxw) — обрезка СПРАВА у
колоночного блита. Для column-major это ровно уменьшение числа колонок, то
есть внутри ядра механизм уже был (так же клипается край экрана,
w = _bgi_maxx + 1 - x), наружу не выводился. Тело блита переехало туда,
gfx_blit_cols_part стал тонкой обёрткой (maxw=0) — тем же приёмом, каким
gfx_blit_cols уже обёрнут вокруг gfx_blit_cols_part. Работает и при flip:
первые maxw нарисованных колонок всегда ложатся в левую часть футпринта.
make size-check: роста нет.
PoP: pop_leveldoor_right / pop_leveldoor_ybottom (порт одноимённых глобалов)
пишет draw_leveldoor в pop_state — их читает clip_char из другого банка;
pop_clip_char_right() отдаёт границу, kid_draw превращает её в maxw и уводит
эти кадры с noclip-пути на общий. Прямоугольник heal (kid_lw) сужается тоже
— стираем ровно нарисованное.
Проверено в MAME: pop_leveldoor_right = 176, что есть ровно (draw_xh<<3)+48
для двери комнаты 9; pop_leveldoor_ybottom = 112 у закрытой створки и 69 у
поднятой — сходится с формулой оригинала. Отрисовку подтвердил пользователь
на живом подъёме.
Заодно: ROOMNAV остаётся включённым осознанно — это наш чит, которого в
оригинале не было, как и S/K/I; позже сведём в общий блок читов.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
e86f254b87 |
L1-START + L1-EXIT: старт по данным уровня и выход через дверь уровня
L1-START. Старт и оба рестарта (смерть, выпадение из уровня) сведены в pop_start_level() — порт start_level + do_startpos + set_start_pos (seg003). Комната/тайл/направление берутся из pop_level_start_*, направление инвертируется (~start_dir), поза входа — из tbl_entry_pose: у уровня 1 это падение внутрь (seq_7_fall) плюс нажатие кнопки room5(0,2), то самое, что захлопывает решётку за спиной. Жёсткие START_ROOM/COL/ROW убраны. Проверено в MAME: старт даёт room 1, col 0, падение на row 1 — как по данным. L1-EXIT. Ветка двери уровня из up_pressed + go_up_leveldoor (seg005:0482/ 0574): тайлы и геометрия — pop_leveldoor_enter() в pop_map, последовательность seq_70 — в pop_ctrl. Опкод 0xF1 END_LEVEL в play_seq инкрементит pop_next_level (порт next_level), главный цикл по нему перезапускает уровень — ровно та точка, куда levels_plan §2.2 подключит загрузку уровня 2. Открытость двери проверяется по modifier >= 42 (ветка fix_exit_door), а не по ванильному leveldoor_open: иначе можно войти в ещё ползущую створку. Отдельно стоило разбора: go_up_leveldoor сначала писал Char.x/Char.direction, и оба присваивания молча терялись — окно Char вокруг диспетчера возвращает назад только curr_seq и sword (pop_savekid_state). Направление оставалось «вправо», а все DX в seq_70 отрицательные, поэтому Кид уходил ИЗ проёма влево (поймано стоп-кадром). Геометрию персонажа в этом порте меняет pop_map, пишет в Kid — как pop_down_action и pop_jump_up_seq. Известный остаток — BUG-DOOR-CLIP в bug_list.md: нет обрезки силуэта правым косяком проёма (недопортирован obj_clip_right в clip_char); нужен вариант колоночного блита с ограничением ширины. ROOMNAV пока оставлен включённым — он нужен, чтобы попадать в комнату 9 для этой работы. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
75a51fb1db |
PoP roomtest: выпивание зелья (уровень 1 — склянка здоровья)
Каркас предметов уже был (check_get_item/do_pickup/proc_get_object), пустой оставалась только ветка зелий. Порт seg005 get_item + seg006 proc_get_object: - pop_get_item_action теперь отдаёт 3 = «пить» и делает do_pickup с ТИПОМ зелья, который лежит в старших битах модификатора тайла (modif >> 3); - pop_ctrl на код 3 запускает seq_78_drink; - эффекты: тип 1 (здоровье) — +1 HP через hitp_delta и красная вспышка, причём как в оригинале только если HP не полные; тип 5 («злое») — −1 HP. Типы 2/3/4/6 (жизнь, перо, переворот, открыть ворота) — свойства поздних уровней, портируем вместе с ними. Вспышка фона получила цвет: меч даёт ярко-жёлтую (было), зелье — красную (flash_color оригинала; двух значений достаточно, других в игре нет). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3983fa4513 |
PoP roomtest: HP-учёт, индикаторы HP, чит бессмертия; фикс кэша кадра
Боёвка (порт seg002/seg006): - check_sword_hurting / check_hurting / check_sword_hurt / hurt_by_sword / take_hp через дельты; do_delta_hp сводит их раз в кадр; - парирование (justblocked), refractimer после ранения стража; - смерть по seq_71_dying — через неё же теперь работает чит K: страж действительно погибает, а не замирает на месте; - парные окна Char/Opp: loadkid_and_opp / savekid_and_opp / saveshad_and_opp. Индикаторы HP (порт draw_kid_hp / draw_guard_hp): Кид слева, страж справа. Перерисовка ТОЛЬКО при изменении числа и тогда на ОБЕИХ страницах дабл-буфера (счётчик hp_todo, иначе на второй странице осталось бы старое значение и мерцало через кадр); pop_hp_invalidate при входе в комнату, где фон перерисован целиком. Чит I — бессмертие Кида (нашего изобретения, в оригинале его нет). Перекрывает и путь «безоружного закалывают насмерть»: тот идёт мимо HP, и без этого чит бесполезен ровно там, где нужен. ДВА НАЙДЕННЫХ БАГА: 1. Полосу HP блитил row-major примитивом, а атласы Кида и стража хранятся COLUMN-major (ради бесплатного флипа) — марки выходили транспонированными. Теперь колоночный блит, стрелки как в оригинале. 2. Кэш кадра для ОТРИСОВКИ заполняли pop_savekid/pop_saveshad. Любое окно Char БЕЗ play_seq — а это оба окна боёвки — записывало Киду кадр, который принадлежал СТРАЖУ, и kid_draw искал этот image в атласе Кида, рисуя произвольную позу. Владельцем кэша стал load_frame: он один знает, чей кадр загружен (по Char.charid). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
79ea473910 |
PoP roomtest: страж замечает Кида и достаёт меч (ИИ, шаг 1)
Первый самостоятельный кусок ИИ стража (план, пункт 2). В оригинале у соперника нет своего диспетчера: autocontrol_* (seg002) выставляет те же глобалы control_*, что и ввод игрока, а дальше исполняется общий control(). Инфраструктура под это встала прошлым коммитом, здесь — сама логика. Порт: - check_can_guard_see_kid (seg003:688) — луч видимости по ряду: стены и верхи дверей рвут его совсем, loose/чомпер/дыра/неподнятые ворота дают «вижу, но не пойду». В guards.c (банк 1); - Opp + loadshad_and_opp (seg006:841) и char_opp_dist (seg006:2135); - autocontrol_guard_inactive (seg002:710) + move_* (seg002:0706..); - ветки control(): control_guard_inactive (seg006:2123) и draw_sword (seg005:945) — соперник уходит сразу в seq_90 en garde; - pop_guard_tick перестроен по play_guard_frame (seg000:1246): окно Char/Opp вокруг ИИ, диспетчера и play_seq. По дороге: - Kid.alive не выставлялся (= 0 = «мёртв» в семантике оригинала), из-за чего луч видимости не мог сработать в принципе — ставим -1 в kid_init; - Kid.room не выставлялась вовсе; условие Kid.room == Guard.room всегда было ложным. Ставим в enter_room (полная модель Kid.room != drawn_room у шва по-прежнему впереди); - pop_tile_at — тайл текущей комнаты наружу из pop_map (луч видимости); - control_x/y/shift открыты в шину: ИИ заполняет оси как есть, без flip_control_x (его «вперёд» уже в системе персонажа). Проверено в MAME (комната 3, страж на tile 17): страж переходит из стойки 166 в 171 stand_with_sword, sword=2, can_guard_see_kid=2; ввод игрока не пострадал. Клинок отдельным спрайтом пока не рисуется — sword.atl содержит только кадры 229..237 (подъём меча Кидом), остальные строки sword_tbl приедут с боёвкой. Бюджет (225 кадров, комната 3): 384 168 – 384 636 тактов, 0.893–0.895 кадра, запас 45 364. Прошлый замер 382 584 – 383 064 — шаг стоил ~1 570. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8bbc6b4d07 |
PoP roomtest: интерпретатор последовательностей стал общим (Char) — стражи
Пункт 1 плана стражей. play_seq был прибит к Киду, поэтому страж стоял на захардкоженном кадре 166. Теперь как в оригинале: интерпретатор работает с АКТИВНЫМ персонажем Char, а вокруг стоят loadkid/savekid и loadshad/saveshad (порт seg006:809..825). Почему копия, а не указатель: так в оригинале, и на Z80 это быстрее — горячий цикл обращается к глобалу абсолютной адресацией, а 16-байтовое копирование платится один раз на переключение персонажа, тогда как указатель дал бы индексную адресацию в каждом обращении. Сопутствующее: - kid_t и pop_char_t слиты в один pop_char_t (pop_char.h): в оригинале char_type один на всех, и без этого общий интерпретатор невозможен. Kid получил поля room/charid/sword/alive — они и так нужны боёвке; - load_frame выбирает таблицу кадров по Char.charid (у стража своя, frame_tbl_guard с индексом frame + add_frame − 149, seg006:0293); - cur_frame разведён на два кэша: страж тикает ПОСЛЕ Кида, и без этого kid_draw брал бы кадр стража. savekid/saveshad раскладывают кадр по своему персонажу; - kid_set_seq пишет ИМЕННО Kid (его зовут pop_ctrl/pop_map вне окна Char, иначе loadkid затёр бы), для окна Char добавлен pop_char_set_seq — порт seqtbl_offset_char; - в pop_map 9 голых play_seq() заменены на pop_kid_play() (load+play+save); - страж входит в комнату через seq_77_guard_stand_inactive (seg002:0208), а не через прибитый кадр. Проверено в MAME: Kid бегает и упирается в стену как прежде, в комнате 12 плиты проваливаются со щебнем (правка задела 9 вызовов play_seq в физике), страж в комнате 3 рисуется в той же позе, но теперь curr_seq=0x19A9 и charid=2 — кадр получен прокруткой последовательности, а не константой. Бюджет (комната 3, 150 кадров): 380 292..381 282 тактов против 370 140..371 160 до правки, то есть +10 150 (+2.7 %). Основное — не копии Char, а то, что страж теперь реально крутит интерпретатор каждый кадр, а раньше стоял замороженным. 0.887 кадра, запас 48 718. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1e6c377edc |
PoP roomtest: pop_bg и pop_map в банки; --dataseg BANKn стал опцией
Резидента --w3 больше нет: отрисовка (pop_bg + pop_gdraw) уехала в БАНК 2, физика/коллизия (pop_map) — в БАНК 3. Куча W1/W2 1294 -> 6750 Б. Что это разблокировало. Резидент был тупиком: из банка он недостижим ни прямо, ни транзитивно, поэтому pop_map (самый крупный модуль, 5.8 КБ) в банк было не увести — он зовёт mob-отрисовку. Проверено, что банк->банк РАБОТАЕТ: ___sdcc_bcall_ehl читает страницу окна портом 0xE2 и кладёт её на СТЕК своего кадра (runtime/bank.s), поэтому вложенность корректна по построению. Подтверждено в MAME цепочкой W1 -> банк1 -> банк2 -> банк1: nested=124 after=8, ровно ожидаемое. Значит развязка mob'а (самое рисковое место, loose-полы) НЕ понадобилась — pop_map зовёт pop_bg трамплином. Правила вызовов проверены на сгенерированном asm и записаны в memory sdcc_banked_call_rules: трамплин выбирает ОБЪЯВЛЕНИЕ (__banked), а не раскладка — даже внутри одного .c между __banked функциями он есть. Внутрибанковые функции оставлены непомеченными и зовутся напрямую, в т.ч. через границу файла (pop_gdraw -> pop_fore_over_char). sprinter-cc: --dataseg BANKn БОЛЬШЕ НЕ ставится по умолчанию. Раньше вся писучая память банкового модуля уезжала в страницу банка и снаружи не читалась (проверено на .map: глобал лёг по 0x0001C000) — грабли на каждом переносе. Теперь данные банков по умолчанию в общем _DATA (W1/W2, замаплен всегда), а прежнее поведение — по явному --bank-data. Замеры (комната 3, Kid + страж; кадр Sprinter в турбо = 430 000 тактов): до переноса 338 508 (0.79 кадра) + pop_bg в банк 2 347 100 (+2.5 %) + pop_map в банк 3 371 100 (+9.6 % к исходному, 0.86 кадра) Плата — трамплины (~654 такта на вызов, ~30 вызовов за кадр). При пейсинге в 3 кадра это 29 % логического кадра, но запас до ОДНОГО кадра всего ~59 000 тактов — под звук его надо возвращать (следующий шаг: батчить кроссбанковые вызовы, начиная с pop_redraw_needed). Профилирование бордюром включено по умолчанию (make PROF=0 выключает) и переведено на реально работающие биты: бит 0 (красный) у бордюра Sprinter ИГНОРИРУЕТСЯ, поэтому различимых состояний четыре и значения обязаны быть чётными — 0 чёрный (ждём vsync), 2 синий (логика), 4 зелёный (фон), 6 циан (спрайты). Раньше нечётные номера сливались и полосы не читались. Проверено в MAME: комнаты 1/3 рисуются как прежде, бег и коллизия работают, в комнате 12 плиты проваливаются со щебнем — то есть цепочка loose банк3 -> банк2 живая. Полосы бордюра в комнате 3: логика 73 строки, фон 64, спрайты 128, свободно 23. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ecf5ecfc14 |
PoP roomtest (план v2, шаг 2): общая геометрия в pop_geom
Сведены дубли, разъехавшиеся по модулям: - x_bump[20] был в pop_kid (uint8_t!) и в pop_map (int16_t) — теперь одна таблица int16_t; - y_land[5] — две копии; - y_to_row_mod4 — в pop_bg и pop_map; - 32-битный LCG оригинала (prandom) — в pop_bg и pop_trob; функция теперь одна, а СИДЫ остались раздельными (у раскладки кладки и у фаз факелов свои последовательности, смешивать нельзя — иначе поедет рисунок стен). Экономия по коду скромная (_CODE 24462 -> 24421, W3 11643 -> 11632: часть выигрыша съели межмодульные вызовы). Главное здесь другое: pop_geom лежит в W1/W2 и не трогает графику, то есть это тот самый «чистый» слой, который сможет звать __banked-код стражей (docs/layout_plan_v2.md §4, §5.2). Проверено в MAME: комната 1 после пересборки отрисована ПОБАЙТОВО так же, как до правки (0 различающихся пикселей в области комнаты) — значит последовательности PRNG и геометрия не поехали; Kid бегает. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4cffbc9aa4 |
PoP roomtest (план v2, фаза 1b): pop_map переведён на пометки перерисовки
Loose-полы, плита-потолок и щебень от приземления больше не зовут pop_bg — ставят пометки (pop_set_redraw / pop_set_redraw_above), которые разбирает pop_redraw_needed из главного цикла. Удалены самодельные счётчики loose_bake/loose_rest/ceil_rest/ceil_bake/land_bake: их роль (вторая страница дабл-буфера) теперь у счётчика страниц в пометке. Осталось ОДНО исключение: падающий кусок (mob) — spawn/tick/pos. Это движущийся ОБЪЕКТ, а не перерисовка тайла, и в оригинале он живёт отдельно (mobs + draw_moving), поэтому разделение его на логику и отрисовку — следующая фаза. Из-за него pop_loose_tick остаётся единственной функцией pop_map, которую нельзя звать из __banked-кода; вся коллизия, физика, кромки и предметы — то, что понадобится стражам — чисты от графики. Замер: _CODE 24718 -> 24462, _DATA 4301 -> 4219 (ушли rest-массивы). Проверено в MAME: комната 12, осторожный шаг на плиту — тряска, падение плиты, Kid проваливается на ряд 1, дыра и щебень отрисованы; на замороженном кадре обе страницы дабл-буфера побайтово совпадают в области изменений (7 строк). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
fb495ace42 |
PoP roomtest: вспышка фона стробом + красная вспышка урона (+ урон падения)
1. Жёлтая вспышка подъёма меча была ОДНОЙ длинной заливкой. В оригинале (seg003:0AFC flash_if_hurt / remove_flash_if_hurt) цвет ставится и СНИМАЕТСЯ в том же кадре — пока идёт flash_time, видно быстрый строб «жёлтый/чёрный». У нас теперь так же: цвет ставится в кадре и снимается после ПЕРВОГО gfx_wait_vsync из трёх (≈20 мс жёлтого, 40 мс чёрного — близко к оригинальным 2 тикам таймера). 2. Красной вспышки при потере HP не было вовсе. Порт второй ветки flash_if_hurt: если flash_time не активен, но в этом кадре hitp_delta<0 — do_flash(color_12_brightred) ровно на кадр. У нас триггер — pop_kid_hurt (он же рисует «брызги»). 3. Чтобы вспышке было от чего срабатывать, портирован урон падения из land() (seg005), которого у нас не было: <22 — мягко, <33 — −1 HP и seq_20 (2 этажа), иначе take_hp(100) + seq_22_crushed (насмерть). ВНИМАНИЕ: это меняет геймплей — падение с 2 этажей теперь отнимает HP, с 3+ убивает (как в оригинале). Проверено в MAME: pop_flash_time тикает 5→3→1→0 (строб), падение с ряда 0 на ряд 2 даёт hitp_curr 3→2 и кадр 109 (medium land). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e1846369ad |
PoP roomtest: блеск лежащего меча, клинок в руке, вспышка фона
Три расхождения с оригиналом на сцене подъёма меча (комната 15): 1. Лежащий меч не блестел. Порт animate_sword (seg007:0425) + start_anim_sword (seg007:087C): при входе в комнату тайлу даётся случайная фаза (prandom & 0x1F), каждый кадр счётчик вниз, на 0 — новый период 0x28..0x67. Кадр блеска рисуется РОВНО на modif==1 ((modif==1)+10 в draw_tile), т.е. одиночная вспышка раз в 40..103 тика. Перерисовка тайла — по смене видимого кадра, схемой кнопки (текущая страница сразу, вторая через rest-цикл). 2. В кадрах «нашёл меч» клинка не было видно. Порт add_sword_to_objtable (seg006:1798): клинок — ОТДЕЛЬНЫЙ спрайт chtab_0 поверх Kid со смещением из sword_tbl. Смещения в ЭКРАННЫХ пикселях (оригинал применяет их после calc_screen_x_coord), поэтому берём уже масштабированный obj_x. Из таблицы взяты только строки 35..42 (кадры 229..236) — бой не портирован. Новый атлас sword.atl (8 спрайтов, 1.6 КБ) + палитра chtab_0 в слотах 0x80..0x8F. Прямоугольник heal расширяется объединением с клинком, иначе он оставлял след за габаритом Kid. 3. Не было вспышки фона. do_flash = set_bg_attr(0, color) — оригинал подменяет НУЛЕВУЮ запись палитры, вспыхивает всё чёрное поле экрана; proc_get_object ставит flash_color=14, flash_time=8. У нас gfx_pal_set на обе страницы. ВАЖНО: вызов gfx_pal_set(0,0,0,0,0) пятью литералами ломает SDCC 4.5 (эмитит невалидный `ld hl, a`) — обёрнуто в функцию с параметрами. Попутно: TROBS_MAX 24 -> 30 (как в оригинале) + при входе в комнату из списка выбрасываются «декоративные» trob ДРУГИХ комнат (факелы/зелья/ меч анимируются только в отрисованной). Без этого отладочный обход всех 24 комнат забивал список, и новые анимации молча не заводились. Проверено в MAME: блеск (брейк на редрое тайла срабатывает), клинок в руке виден, фон вспыхивает жёлтым. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1c91c5f92a |
PoP roomtest: меч — отрисовка на полу, подъём по Shift, статус have_sword
Комната 15, тайл (2,2): меч не рисовался вовсе — в draw_tile не было
ветки tiles_22_sword (seg008 draw_tile_anim):
add_midtable(chtab_1, (modifier == 1) + 10, draw_xh, 0, draw_main_y - 3, ...)
Спрайты уже лежали в нашем pot-атласе (chtab_1 пакуется целиком, id 1..23),
так что понадобился только вызов. У нас предмет рисуется статикой в фоне,
а не в midtable: пока меч лежит, он не анимируется, а Kid и так поверх фона.
Подъём — порт цепочки оригинала:
- check_get_item/get_item (seg005:061F/073E) -> pop_get_item_action() в
pop_map (тайлы): стоя НА предмете отступить на тайл назад, подровняться
по кромке и присесть; из приседа над мечом — поднять;
- do_pickup (seg006:1671): тайл -> пол, pop_item_taken = tilepos+1;
roomtest запекает пол на ОБЕИХ страницах (pop_floor_bake) и пишет
per-room override, чтобы меч не воскресал при возврате в комнату;
- триггер — Shift (control_shift2) в стойке и в приседе, как в
control_standing/control_crouched;
- seq_91 pickupsword: опкод SEQ_GET_ITEM с аргументом 1 в play_seq теперь
зовёт pop_proc_get_object (seg006:16CB) -> pop_have_sword = 1.
Статус: pop_have_sword. Боевой режим (стойка с мечом, бой) НЕ делаем и
он тут не нужен: seq_91 сам заканчивается убиранием меча в ножны
(кадры 230..240) и возвратом в обычную стойку — как в оригинале до
встречи со стражем. Питьё зелий не портировано: над зельем Kid только
приседает (get_item возвращает «не обработано»).
Проверено в MAME (комната 15): меч виден на полу, Shift над ним даёт
присед -> кадр 229 «нашёл меч» -> ножны -> стойка; меч с пола исчезает,
pop_have_sword = 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
6769b6a01b |
PoP roomtest: loose-плита впереди = КРОМКА в get_edge_distance
Осторожный шаг к проваливающемуся полу проваливал Kid с первого же шага:
в pop_edge_distance не было ветки оригинала (seg004:067C)
if (tiletype == tiles_11_loose) goto loc_59FB; // CLOSER + до кромки
— плита читалась как обычный пол (EDGE_FLOOR, 11), и весь механизм
«проверки ногой» пролетал мимо. С веткой safe_step (seg005) отрабатывает
три фазы, как в оригинале: (1) укороченный шаг РОВНО до кромки плиты
(seq 29..42 по distance), (2) seq_44 testfoot — щуп ногой, плита трясётся
по SEQ_KNOCK_DOWN, Kid остаётся на месте (Char.repeat), (3) только третье
нажатие — шаг на плиту и падение вместе с ней.
Заодно портированы соседние ветки той же функции: верх двери лицом вправо
(проверяется ДО wall_type) и closer/меч/зелье (кромка, пока есть зазор).
Проверено вручную в MAME: все три фазы.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
517d225091 |
PoP roomtest: полный порт bumped (seg004) — прижатие Y к полу + hardbump
Комната 5, прыжок в решётку: Kid оставался стоять на 6 пикселей выше
пола и без приземления-приседания. Шесть пикселей — это dy(-6) кадра
frame_25_standing_jump_10: удар обрывал standjump ровно между кадрами
25 и 26, парный dy(+6) не выполнялся.
От оригинального bumped() у нас был портирован только хвост (выровнять
X к грани + seq_47_bump). Добавлены недостающие ветки:
- bumped() — исход удара выбирается по тайлу, НА КОТОРОМ персонаж
оказался после отжатия (сквозь стену/верх двери это
соседняя клетка, для ворот/зеркала — сама клетка);
- bumped_floor — прижимает Y к y_land, ветка fall_y>=22 (только отжать
на 5) и выбор сиквенса по кадру: 24/25/40..42/102..106
-> seq_46_hardbump (отскок с приседанием), иначе 47;
- bumped_fall — удар выше пола на 15+ px (беззнаковое сравнение
оригинала) или не над полом: seq_45, в свободном
падении — только гашение fall_x.
Гард «уже в отскоке — не рестартить» (наш, от edge-триггера коллизий)
перенесён внутрь bumped_floor: выравнивание X/Y идёт всегда, рестарта
анимации нет. Проверено в MAME.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
e143ee010a |
PoP roomtest: нажатая кнопка-closer, таблица падающих кусков, фон-покой при запечке
Кнопка-closer (tiles_6) в нажатом состоянии рисуется как tiles_5_stuck
(get_tile_to_draw seg008:2FE), но её грани — env id 35 (правая) и 36
(нижняя) — не попадали в атлас: pop_pack_bg собирает набор спрайтов
прогоном render_room по статике уровней, а тайла 5 статически в уровнях
нет. Итог — чёрный провал на месте нижней грани. Добавлены явным
набором STUCK_ENV_IDS (как loose/пики/ворота). Заодно OPENER_NOFLOOR_ENV_IDS
= {148}: левая половина кнопки-opener, когда слева пусто (draw_tile_base
seg008:0A8E) — ветка была не реализована ни в pop_bg, ни в render_room.
bake_rest: при ЗАПЕЧКЕ статического фона (запись идёт в ОЗУ-копию
акселератора, из неё восстанавливает heal) get_loose_frame возвращает
кадр ПОКОЯ. Иначе транзиентный кадр тряски соседней плиты консервируется
в копии, а так как запечка идёт двумя кадрами (по одному на страницу
дабл-буфера) — на страницах застывают РАЗНЫЕ кадры, вечное мерцание.
mobs[MOB_MAX] вместо одиночных статиков: две плиты, упавшие подряд,
теряли первый кусок (застывал в воздухе) и оставляли щебень только от
одной. Плюс отложенная допечка прошлого приземления ПЕРЕД разбором
нового и ограничение чёрного бара в pop_loose_bake_empty низом тайла.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
b106aba59b |
PoP roomtest: подъём/спуск на ОТКРЫТЫХ воротах (потерянная проверка openness)
Kid не мог залезть на тайл поднятой решётки: срывался обратно. can_climb_up (seg005:09DF) выбирает seq_73 («влез под решётку и съехал») только для ЗАКРЫТЫХ ворот — условие включает curr_room_modif[curr_tilepos] >> 2 < 6, а у нас его не было, и seq_73 играл при любой решётке над головой при взгляде влево. Та же дыра нашлась в спуске: down_pressed (seg005:482) запрещает climb-down с тайла ворот лицом влево тоже ТОЛЬКО пока они закрыты (|| curr_room_modif[curr_tilepos] >> 2 >= 6). Без этого Kid приседал вместо спуска даже под поднятой решёткой. Openness обеих проверок берётся общим хелпером gate_modif(col,row) (вынесен из gate_passable — умеет свою комнату и соседнюю через шов). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5e09bd9d5a |
PoP roomtest: непрозрачные нижние грани + edge-триггер бампа + 2 колонки шва
Три фикса, найденные разбором в MAME. 1. Просвечивание Кида сквозь торец кнопки (подъём/спуск у кромки). draw_tile_bottom (seg008:570) рисует нижнюю грань блиттером blitters_0_no_transp: пиксель индекса 0 заливается ЧЁРНЫМ, а не пропускается. У граней кнопок (env 96/149) и нижних кадров loose (73/74) последняя строка целиком нулевая — наш упаковщик переводил 0 в 0xFF для ВСЕХ спрайтов, и она становилась дырой, сквозь которую был виден Kid за тайлом (на запечённом фоне не видно — там и так чёрное). pop_pack_bg.py: NO_TRANSP_ENV_IDS пакуется с сохранением индекса 0 как ЦВЕТА (0x50 = чёрный в палитре env). Ноль тактов в рантайме. 2. Разворот в проёме опущенной решётки выкидывал Кида на другую её сторону. check_bumped проверял столкновение УРОВНЕМ («край зашёл за грань»), поэтому разворот на месте внутри тайла-стены менял, какую грань мы меряем, и это читалось как новое столкновение → Kid.x = char_dx_forward(d) швырял его сквозь решётку. Оригинал (check_collisions + get_row_collision_data, seg004:0004) считает флаги перекрытия от ГАБАРИТА персонажа, БЕЗ направления, и зовёт bumped() только на переходе 0 -> 1 (вправо смотрит на 0x0F, влево на 0xF0). Порт этого edge-триггера: флаги от габарита + память прошлого кадра по колонке, сброс при смене комнаты (в оригинале это несовпадение prev_coll_room/curr_row_coll_room). Проверено: теперь разворот даёт уход в комнату 8 (leave_room, взгляд влево, char_x_left<=54) — то есть туда, где Kid физически и стоит. 3. Снимок шва расширен до ДВУХ колонок с каждой стороны (lcol_fg[6]/ rcol_fg[6]: col9+col8 и col0+col1). Kid, стоящий В шве (curr_col=-1), смотрит вперёд на колонку -2, где раньше была мнимая стена (get_tile -> TILE_WALL). Оригинал резолвит любую колонку через find_room_of_tile (seg006:005D). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6582154381 |
PoP roomtest: clip_char (верхняя обрезка спрайта) + подстановка нажатой кнопки
Спуск Кида с кнопки (room8, кромка (0,6)) рисовался неверно: Кид просвечивал в щель между кнопкой и ближним столбом, а ближняя рука была срезана до одного оторванного пикселя. Две независимые причины. 1. Не был портирован clip_char() (seg006:1749) — оригинал перед add_objtable обрезает спрайт персонажа сверху по y_clip[curr_row+1], когда тайл над головой стена или пол. Порт: pop_clip_char_top() (pop_map.c, метрики по set_char_collision) + новый примитив gfx_blit_cols_part() в libbgi (блит column-major с пропуском skip верхних строк; обрезка сверху бесплатна — колонка непрерывна в ОЗУ, сдвигается только старт). gfx_blit_cols стал тонкой обёрткой над ним. kid_heal чистит уже ОБРЕЗАННЫЙ прямоугольник, иначе стирается кромка пола над срезом. 2. climb_overlay_tile (порт draw_floor_overlay, seg008:1E3A) выбирал ветку по СЫРОМУ коду тайла, а get_tile_to_draw (seg008:240) подменяет нажатую кнопку на floor/stuck. Тайл-кнопка не проходил тест floor → уходил в draw_other_overlay, который кладёт поверх Кида ВЕСЬ тайл вместо узкой кромки floor_left_overlay[frame-137]. Фикс — tile_code_drawn(): одна подстановка на все слои (fore_only_tile/overlay_mid_tile перестали её дублировать, W3 −293 Б). Проверено покадрово в MAME (шаг gv + снимок): кадры спуска 148..138 чистые, после приземления на кнопке мусора нет. Заодно: gfx_heal_noclip() (пара к gfx_blit_noclip; heal 11658 -> 8982 тактов) и rest_pending в pop_trob — холостой проход по 30 тайлам только когда есть отложенные редрои пик/кнопок (pop_process_trobs 89-111К -> 70-92К тактов). START_ROOM временно = 8 (отладка спуска с кнопки), вернуть на 6/старт уровня. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
63a3b60524 |
PoP roomtest: плита-потолок, порядок слоёв по SDLPoP, фиксы окклюзии и Y
Фича — плита-ПОТОЛОК (loose ряда 2 комнаты сверху, комн.5 (2,5) над комн.6): - check_press (seg006:1683) портирован целиком: кадры виса 87..99 и подъёма 135..140 «нажимают» тайл, за который Kid держится; кадр 79 — удар снизу (с проверкой action, как в оригинале); стояние — тайл под ним; - ряд −1 в pop_map.get_tile = ряд 2 комнаты сверху (порт find_room_of_tile), состояние тряски/падения — pop_ceil_modif[10]; do_knock(-1) трясёт потолок; - падение: mob переведён на координаты оригинала (y_loose_land/y_something), спавнится в ряду −1, по move_loose находит пол → loose_land: debris + do_knock; heal коридора — по прошлой позиции ДЛЯ КАЖДОЙ страницы; - урон: check_loose_fall_on_kid + fell_on_your_head (seq_52/seq_22, −1 HP) и «брызги» draw_hurt_splash (kid image 218); - колодец наверх: leave_room «вверх» проверяется ПЕРВЫМ (иначе y≈248 при подтягивании ловил check_leave_below и ронял Kid из уровня); - персистентность: общий tile-override (щебень внизу, пустота в комнате сверху) вместо debris-only. Окклюзия — приведена к оригиналу (найдено трассировкой собранного SDLPoP): - draw_tile_right/anim_right/draw_loose кладут спрайты в add_backtable НАПРЯМУЮ, минуя ptr_add_table → правая грань соседа («шахматка» столба 93), blueline и грани loose/пик соседа ВСЕГДА под персонажем. draw_other_overlay поверх Kid даёт только: 42 при левом соседе-поле, base, свои пики, bottom (overlay_mid_tile); - порядок объектов: тайл Kid (set_objtile_at_char) и порядок обхода (ряды 2→0, колонки 0→9), внутри тайла — сортировка по y (sort_curr_objs); то же правило для падающей плиты (pop_loose_mob_draw_over); - fore-слой рисуется ПОСЛЕ оверлеев (foretable после midtable), иначе тёмный скос floor_left_overlay ложился поверх колонны; - fore_tile больше не рисует bottom_id (в оригинале он в backtable) — минус 2..4 блита за кадр; - полоса потолка поверх Kid = draw_tile_bottom(1)+draw_loose(1)+draw_tile_fore (arg_0=1 добавляет в foretable), остальное — под ним; - следы оверлея восстанавливаются на ОБЕИХ страницах (pop_fore_heal). Прочее: - control(): при action bumped/in_freefall управление игнорируется целиком (seg005) — иначе прерванный присед терял dy(1)+dy(1) из medland и Kid навсегда оставался на 2px выше пола; - check_bumped: стена ищется по колонке переднего края (а не curr_col) + гард разворота — Kid больше не проходит сквозь опущенную решётку шва; - pop_floor_bake для смены floor→debris (bake_empty стирал верх стены ряда ниже — чёрный прямоугольник); - в pop_room_redraw_seam_left восстанавливается полоса потолка над решёткой. Проверено в MAME (bridge); эталон — собранный SDLPoP из applications/PoP/SDLPoP. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
86d7615841 |
PoP: roomtest — объекты/обломки/переходы комнат + арт
Порт Prince of Persia (applications/PoP/roomtest): развитие уровня, объекты (loose-полы/обломки), переходы между комнатами, фон-упаковка; планы (room_model/size_optimization), bug_list, pop_trob. Арт third_party/16x16-RPG-characters. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d1bc97c589 |
roomtest: обломки упавшего loose в комнате снизу (debris)
Loose-пол room1(2,6) при падении даёт кусок, который в оригинале улетает в комнату снизу (links.down) и приземляется на первый floor, превращая его в debris (порт move_loose/loose_land seg007). Для room1(2,6): room2(0,6)=empty пролёт → room2(1,6)=floor приземление → debris. Минимальная реализация (без анимации полёта куска, только персистентный результат — мини-версия P0 из docs/gates_spikes_plan.md): - pop_map: сигнал pop_loose_fell (tilepos+1 упавшего loose). - pop_level: pop_room_col_landing(room,col) — ряд первого floor сверху вниз (пустые ряды пролетаются), иначе -1. - roomtest: при pop_loose_fell вычисляет debris-приземление в комнате снизу → персистентный override (dbr_room/dbr_pos, живёт между переходами) → enter_room применяет поверх загруженных тайлов (floor→debris 0x0E). Проверено в MAME: провал loose room1(2,6) → debris в room2(1,6), сохраняется при повторных входах. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
bf7ddfd4da |
roomtest: L3 переходы вбок (право+лево) + план объектов (кнопки/гейты/пики)
Переходы в соседние комнаты по горизонтали (порт SDLPoP leave_room/ goto_other_room/find_room_of_tile), проверено в MAME (room2↔room3↔room9, room1↔room5 и т.д.): - pop_level: pop_room_load теперь извлекает и rightcol (col0 правого соседа), как leftcol — для коллизии/рендера правого шва. - pop_map: pop_map_set_edges(l,r,u,d, lcol_fg, rcol_fg) — связи + кромки швов. get_tile(col=-1)=lcol (col9 левого), get_tile(col=10)=rcol (col0 правого), с гейтом по связи (нет соседа → стена) — порт find_room_of_tile. check_leave (порт leave_room): передний край char_x_left<=54 / char_x_right>=201 (и обратные <=57 / >=198) → x∓140, сигнал pop_leave_dir (1=left,2=right). - roomtest: rcol-массивы; enter_room задаёт edges; обработчик pop_leave_dir переключает комнату по pop_room_link. - Фикс двойного перехода: без коллизии ЛЕВОГО шва Kid, войдя справа в соседа (curr_col=-1), стоял «в стене» → in_wall выбрасывал его на второй переход. Левый шов (get_tile(-1)=lcol) это чинит. docs/gates_spikes_plan.md — ПОДРОБНЫЙ самодостаточный план на след. сессию: интерактивные объекты (RAISE/DROP-кнопки, гейты, пики) + HP/смерть. Контекст текущего roomtest, форматы уровня (LINKLOC/LINKMAP @1440/1696), данные room6, декод связи кнопка(0,2)→гейт room8(0,9), триггер пик (check_spike_below), точные ссылки SDLPoP, фазы P0(персистентное per-room состояние+trob)/ S(пики+HP/смерть)/B(кнопки+гейты). NB: L3-вверх (climb-up в комнату сверху) ещё не сделан. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
7f3e32ddd6 |
roomtest: L2 — переход в комнату снизу (провал/спуск) + фиксы окклюзии
При пересечении нижней границы комнаты Kid переходит в комнату links.down, продолжая падать/спускаться (порт SDLPoP goto_other_room/leave_room). - pop_map check_leave_below (порт leave_room, seg002): триггер по Kid.y>=211 (не curr_row) — единый для провала И спуска-зацепа/climbdown; репроекция координат в кадр комнаты снизу (y-=189, curr_row=y_to_row_mod4) + сигнал pop_fell_out. Убран прежний преждевременный триггер y>=189 из do_fall (из-за него climbdown с curr_row=3 ждал y_land[4]=244 → пролёт через комнату). - roomtest: enter_room(room) — загрузка комнаты в рабочие массивы + отрисовка фона в обе страницы; обработчик pop_fell_out переключает комнату по pop_room_link(down) или респавнит (нет комнаты снизу). cur_room-трекинг. - pop_loose_reset (pop_map) + pop_loose_mob_reset (pop_bg): сброс loose-состояния (индексировано позицией тайла) при смене комнаты — иначе течёт в новую. Фиксы окклюзии при висе/спуске (сверено с SDLPoP): - other_overlay_tile: пустой тайл НЕ окклюдирует — draw_tile для него рисует лишь фоновую сетку (BLUELINE «силуэт кладки»), которая в оригинале уходит в y-сортируемый midtable ПОЗАДИ Kid; у нас клалась поверх. Пропускаем. - pop_fore_over_kid: порт set_char_collision (seg006:0723) — char_bottom_row = y_to_row_mod4(obj_y), обёртка -1 → ряд 3 (Kid НИЖЕ комнаты при спуске), а не ряд 0. Прежний кламп в 0 давал rT>rB → цикл окклюзии не выполнялся, нога рисовалась поверх пола. single-room: ряд 3 клампится в 2 (передняя грань пола). Проверено в MAME: провал вниз, спуск-зацеп/climbdown с кромки, окклюзия ноги. Известно (позже): осколки упавшего loose в комнате снизу (перенос mob), per-room modified-tile tracking (re-entry восстанавливает исходные тайлы), HP. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8b30dc20c8 |
roomtest: loose-полы — тряска (knock), падение с окклюзией, фикс дабл-буфера
Проваливающиеся полы в roomtest (порт SDLPoP seg007/seg008), проверено в MAME: - Падающий кусок (mob): правый край env-42 (w=26, доходит до mob_x+57) теперь полностью покрыт heal-коридором (MOB_W 56->64) — убран тёмный хвост-тень на полу 2,7 ПОСЛЕ падения. - Окклюзия правого куска во ВРЕМЯ падения: после отрисовки плиты перерисовывается сосед-пол draw_tile(row,col+1) в том же кадре — край прячется за полом 2,7 (порт redraw_at_cur_mob: set_redraw_full+1). - Тряска от сотрясения (knock): seqtbl-команды KNOCK_DOWN/UP в play_seq -> флаг knock -> do_knock(ряд) трясёт loose ряда из покоя (modif=0x80). KNOCK_DOWN в land-seq (приземление) и runcyc (footstep). - Фикс «бесконечной тряски» (дабл-буфер): при завершении тряски (0x84->0) перерисовать покойный кадр плиты на ОБЕИХ страницах (loose_rest, 2 кадра) — иначе на одной странице застревает дрожащий кадр правой грани (живёт в тайлах col и col+1) -> мерцание через флип. Инфраструктура/документация: - app.mk: цель `make hdd` (упаковка в D: для MCP-моста MAME). - docs: POP-DAT-FormatSpecifications.pdf/.txt как каноническая спецификация форматов ресурсов; ссылки в README/MSDOS_RESOURCE_FORMAT. - README.md/CLAUDE.md для applications/PoP и roomtest (правило «SDLPoP — источник истины», порядок слоёв, режим отладки freeze 1/2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
36a5a5e194 |
roomtest: фикс провала сквозь пол при беге с кромки (порт start_fall/in_wall)
Тап-бег с кромки [1,3] вправо -> Kid проваливался в стену col2/3 -> респавн, вместо посадки на [2,4]. Сверено с SDLPoP seg006: - start_fall: frame 9 -> seq_7_fall (был ошибочно seq_19 как у 13); frame 13 -> seq_19 (лишний dx(1)). + хвост seg006:1099: тайл ПЕРЕД персонажем — стена -> Char.x = char_dx_forward(-1); - in_wall переписан аутентично (seg006:1292): выталкивает ВПЕРЁД из стены (delta+4) на соседний тайл, а не назад вглубь (наш старый через dist_from_wall_forward давал отрицательный сдвиг -> Kid уходил в стену col2 -> проваливался). distance_to_edge_weight + 6-delta/+4. Проверено в MAME (пользователь): бег с кромки [1,3] -> посадка на [2,4]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
419d5e4eff |
applications/PoP/roomtest: фикс ухода Kid в стену при прыжке с кромки
Баг (из динамики): стоя на кромке [1,3]/[1,4] лицом вправо + Up, Kid телепортировался/застревал в стене col8. КОРЕНЬ (трасса MCP): jumpup(seq_14) на кромке → приземление над ямой → падение; на кадрах падения action=3 (midair) check_bumped был НЕ заглушён (guard покрывал только freefall/hang/climb-кадры). Kid дрейфовал к стене, get_tile вне рядов давал WALL, dist_from_wall_forward/x_bump[] — мусор с большим отрицательным сдвигом → Kid.x=char_dx_forward((int8_t)d) → underflow uint8 X (0→255) → долёт до col8 и застревание в стене. Фикс check_bumped: заглушить и в action MIDAIR (кадры падения), и при curr_row вне [0..2] (падение мимо пола — тайлы вне комнаты = WALL, bump-мусор; fell_out ловит do_fall). Проверено в MAME: на кромке Kid больше НЕ уходит в стену — чисто падает (в тесте сброс на старт). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |