5d31ed086f5994a0f7a3824abd341163544d75d8
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5d31ed086f |
Резидент: pop_redraw + холодная половина pop_level в банки (902 -> 3525 Б кучи)
MEM-COLD3, шаг 1-2 из плана разгрузки резидента: * pop_redraw.c целиком -> банк 7 (к pop_room.c: он и есть единственный потребитель разбора пометок). Два модуля в одном банке линкуются в один сегмент — проверено на .map (BANK7 7928 -> 8556). * pop_level.c расколот по частоте вызова: горячая половина (чтение тайлов, связи, дверные таблицы, живые стражи — зовут все банки, местами на каждый тайл) осталась в резиденте, холодная (чтение файла уровня, разбор комнаты, рестарт, потабличные различия) уехала в pop_level_cold.c -> банк 8. Общее состояние объявлено в _pop_level.h: данные банковых модулей всё равно линкуются в общий _DATA, в банк уехал только КОД. Побочно найден баг кодогена SDCC: `return 1;` из ветки не кладёт 1 в A — до __banked он маскировался случайным ненулевым остатком в A. Репро и разбор — docs/bugs/sdcc-z80-ret-const-lost/, стаб t_char починен одним выходом через переменную. Аудит всех .asm roomtest: других мест нет. _CODE 25628 -> 23005, куча 902 -> 3525 Б. tests-host 6/6. |
||
|
|
361c9d9f78 |
Мышь уровня 8 + холодная половина главного цикла в банк 8
Спецсобытие уровня 8 (SDLPoP seg003:0545/0ABA, seg002:07EB): когда дверь уровня открыта, а Кид остаётся в комнате 16, через 150 кадров приходит мышь, пробегает справа налево по верхнему ряду, наступает на кнопку (0,7) — решётка (0,3) открывается — и убегает. Ассетов не потребовалось: кадры 186..188 идут по таблице Кида, их спрайты (images 130..132) уже лежат в kid16.atl, отрисовка подхватывает мышь сама. - guards.c: pop_check_mouse, autocontrol_mouse, ветки мыши в autocontrol_opponent / play_guard / check_can_guard_see_kid; - pop_leveldoor_open стал word, как в оригинале (в него же считается задержка — байт переполнился бы через 255 кадров); - leave_guard больше не записывает тень и мышь в данные уровня (seg002:02F5); - полосы HP у мыши нет (seg000:1159); - tests-host/t_mouse: 17 проверок, единственный набор с guards.c. Разгрузка резидента (куча в huge упала до 879 Б): pop_start_level, find_start_level_door, чит-навигация по комнатам и лейбл номера уехали в roomtest_cold.c (--bank 8, n_banks = 8). _CODE 25653 -> 24825, куча -> 1707 Б; кадровый путь не тронут. Проверено: tests-host 6/6, make size-check OK, живьём в MAME (мышь появилась, нажала кнопку, решётка поехала вверх, мышь ушла). 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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
3fe083331f |
tests-host: покадровый харнесс сценариев Кида (физика + зацеп)
Проверять физику Кида глазами в MAME дорого и ненадёжно: ошибка почти всегда не в одной функции, а в РАСХОЖДЕНИИ ТРАЕКТОРИИ через несколько кадров. Харнесс гоняет тот же кадр, что и главный цикл (pop_ctrl_tick -> kid_tick -> pop_phys_tick -> pop_loose_tick), и сравнивает трассу состояния с эталоном. - scene.c/.h — раннер: комната + стартовая поза + скрипт ввода -> трасса; sc_kid_at_x задаёт точный X (исход часто зависит от фазы внутри тайла). - stubs.c/.h — libc/libbgi/соседние модули; read() реально отдаёт kid_data.bin (иначе kdat_ok=0 и play_seq молчит — трасса замирает). - t_phys.c — 9 характеризующих сценариев, 1723 сверки (golden/). - t_grab.c — окно зацепа: существует, достижимо коротким шагом, не зависит от рисунка нажатий. - record_golden.py — снятие эталона по одному сценарию за прогон. - testkit/host-tests.mk — CODE_LOC настраиваемый, EXTRA_INC/EXTRA_CFLAGS. Именно харнесс дал доказательство, что физика зацепа у нас верна, и тем самым перевёл поиск BUG-GRAB-1 на клавиатуру. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |