4e43890fce
Пройденная игра доходила до таблицы рекордов и умирала: программа
исчезала, машина следом вставала намертво (di;halt на 0x0000) либо уходила
в reset. Одинаково из Flex Navigator и из голого DSS.
КОРЕНЬ. pop_ui.h объявлял группу pop_text_*_mapped БЕЗ __banked. Пока
pop_hof.c лежал в банке 9 рядом с pop_ui.c, прямой call был верен; после
переноса pop_hof/pop_config/pop_pal в банк 10 тот же call стал уходить в
пустой хвост чужого банка. Процессор полз по 0xFF до 0x0000, где ловушка
DSS ставит B=0x27 и сворачивает процесс — подмена страниц W1/W2/W3,
которую было видно на трупе, оказалась уборкой, а не причиной.
Точную инструкцию (call $E503 = _pop_text_map банка 9) дала трассировка
MAME на узком участке: trace включалась брейкпоинтом на входе в
pop_hof_show и выключалась на процедуре завершения процесса DSS (0x1E56).
ЧТО СДЕЛАНО
* pop_ui.h/.c — группа text_*_mapped помечена __banked.
* toolchain/check_bank_calls.py — две проверки банкового кода:
1) прямой call в чужой банк (доказательна, ВАЛИТ сборку — проверено
намеренной поломкой);
2) указатель на данные своего банка, отданный в чужой (эвристика по
форме кода, только предупреждает).
Встроена в app.mk, запускается сразу после линковки.
* pop_hof.c — курсор ввода строится на стеке: литерал "_" лежал в _BANK10
и после пометки __banked уезжал из-под ног чужому банку, заливая экран
знаками вопроса.
* libc: kbd_raw_keypad_as_ext() — kbd_raw_sync переносит голые коды
нумпада в EXT-половину карты. Лечит залипание стрелок (потерянный
префикс E0 сажал make в PLAIN как код нумпада, и снять его было нечем),
заодно нумпад стал управлением: 7/8/9, 4/6, 2 и 5 = вниз.
* pop_pace.c — цикл ожидания луча зовёт тот же idle-хук, что и
gfx_wait_vsync: без этого F10 в геймплее не работал вовсе.
* pop_hof.c — Esc в таблице рекордов отменяет запись (расхождение с
оригиналом записано в docs/impl_diff.md).
* Экран версии показывается только через Menu/Settings/About: стартовый
показ и Ctrl+V убраны, мёртвый код снят.
* sprpop_cold.c — pop_start_level зовёт pop_hp_invalidate: после Ctrl+A с
выросшим за уровень максимумом полоса HP моргала между страницами.
Разбор всех четырёх багов — в applications/PoP/roomtest/BUGS_CLOSED.md
(FINAL-BANKCALL, FINAL-HOF-GARBAGE, KBD-ARROW-PHANTOM, F10-GAMEPLAY),
правило про банки — в applications/SprPoP/CLAUDE.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
142 lines
8.8 KiB
C
142 lines
8.8 KiB
C
/*
|
||
* pop_state.h — состояние, общее для банков и W1/W2. См. pop_state.c.
|
||
*
|
||
* Правило: если переменную читают/пишут по обе стороны банковой границы —
|
||
* её место здесь, а НЕ в банковом модуле (там она уедет в страницу банка).
|
||
*/
|
||
#ifndef POP_STATE_H
|
||
#define POP_STATE_H
|
||
|
||
#include <stdint.h>
|
||
|
||
/* Есть ли хоть один ЗАНЯТЫЙ слот падающего куска (active или дочистка
|
||
* clean). Гейт холостого хода: пока кусков нет, ни обход 14 слотов в
|
||
* pop_loose_mob_tick, ни поиск куска над головой Кида делать не нужно
|
||
* (замер 11/15: 12 090 + 5 868 тактов на комнату, где не падает ничего).
|
||
*
|
||
* Живёт в резиденте, а не в pop_room.c, потому что читает его pop_map
|
||
* (банк 3), а писучие статики банкового модуля наружу не видны. Ставит
|
||
* отрыв куска, снимает сам обход по факту пустой таблицы. */
|
||
/* F10 нажата — выйти из программы; см. pop_state.c. */
|
||
extern uint8_t pop_quit_req;
|
||
|
||
/* Общий idle-хук: вычерпать клавиатурный FIFO и проверить F10. Живёт в
|
||
* sprpop.c (резидент), висит на gfx_wait_vsync и вызывается из цикла
|
||
* ожидания луча — обе точки ожидания обязаны звать ОДНО И ТО ЖЕ, иначе
|
||
* выход работает не везде. */
|
||
void pop_idle(void);
|
||
extern uint8_t pop_mob_busy;
|
||
|
||
/* Кусок loose приземлился (loose_land, seg007:11E8): 0 = нет, иначе
|
||
* tilepos+1 тайла, на который он лёг. Ставит отрисовка mob (банк),
|
||
* разбирает логика loose-полов (pop_map, W1/W2). */
|
||
extern uint8_t pop_loose_landed;
|
||
|
||
/* Кусок loose ушёл ниже поля своей комнаты: 0 = нет, иначе col+1; комната
|
||
* отправления — pop_loose_exit_room. Ставит отрисовка mob (банк 7),
|
||
* разбирает главный цикл (посадка в комнате снизу). */
|
||
extern uint8_t pop_loose_exit;
|
||
extern uint8_t pop_loose_exit_room;
|
||
|
||
/* Тайл СОСЕДНЕЙ (не отрисованной) комнаты изменился — перечитать кромки.
|
||
* Подробности — в pop_state.c. */
|
||
extern uint8_t pop_neigh_dirty;
|
||
|
||
/* Бесшовный переход между уровнями (seamless): подробности в pop_state.c.
|
||
* Ставится главным циклом при попадании в комнату-выход, гасится стартом
|
||
* уровня — он же единственный, кто на флаг смотрит. */
|
||
extern uint8_t pop_seamless;
|
||
|
||
/* Держатель бесшовного перехода после ЧИТ-телепорта (см. pop_state.c).
|
||
* Ставит pop_dbg_roomnav, снимает первая смена комнаты обычным ходом. */
|
||
extern uint8_t pop_nav_hold;
|
||
|
||
/* Чит «бессмертие» (I): Кида бьют, но HP не убавляется. Читают пути урона
|
||
* (боёвка в банке, пики в pop_map), переключает главный цикл. */
|
||
/* ЧИТ БЕССМЕРТИЯ, три состояния (клавиша I по кругу):
|
||
* 0 — выключено;
|
||
* 1 — только БОЙ: удары мечом не отнимают HP (pop_take_hp тут ни при
|
||
* чём, ветка живёт в hurt_by_sword, guards.c);
|
||
* 2 — бой И мелкий урон: не проходят «минус одно деление» от падения с
|
||
* двух этажей и от падающей плиты (pop_take_hp гасит count < 100).
|
||
* МГНОВЕННАЯ СМЕРТЬ остаётся на любом уровне: пики, чомпер, падение с
|
||
* трёх этажей, удар мечом вне боевой стойки — все они приходят с
|
||
* count = 100. Чит отменяет потерю HP, а не смерть. */
|
||
extern uint8_t pop_immortal;
|
||
|
||
/* Габариты ПОСЛЕДНЕЙ нарисованной двери уровня — порт глобалов
|
||
* leveldoor_right / leveldoor_ybottom (seg008 draw_leveldoor). Их ставит
|
||
* отрисовка (банк pop_bg), а читает clip_char (pop_map, W1/W2): кадры
|
||
* подъёма по лестнице (224..228) обрезаются по правому косяку проёма и по
|
||
* низу поднятой створки. Дверь рисуется ПОД персонажем, поэтому перекрыть
|
||
* его не может — оригинал именно режет спрайт.
|
||
* right — экранный X правой кромки; ybottom — room-space Y (без POP_YOFF).
|
||
* 0 = двери в отрисованной комнате нет. */
|
||
extern int pop_leveldoor_right;
|
||
extern int pop_leveldoor_ybottom;
|
||
|
||
/* droppedout (seg006:1140): Кида СПИХНУЛИ с уступа в бою — он падает из
|
||
* боевой стойки. Ставит start_fall (pop_map, банк 3), читает ИИ стража
|
||
* (guards.c, банк 1): по этому флагу страж решает, идти ли за упавшим вниз
|
||
* (guard_follows_kid_down, seg002:09F8). Отсюда и место в общем модуле —
|
||
* банки друг другу данные не отдают. */
|
||
extern uint8_t pop_droppedout;
|
||
|
||
/* leveldoor_open (seg007:456): 1 — дверь уровня открыта полностью. Пишет
|
||
* анимация двери (банк 6), читает подъём скелета (банк 1). Word, как в
|
||
* оригинале: на уровне 8 в него же СЧИТАЕТСЯ задержка до мыши (см.
|
||
* pop_state.c и pop_check_mouse в guards.c). */
|
||
extern uint16_t pop_leveldoor_open;
|
||
|
||
/* Отладочная «пустышка» для брейкпоинтов из банков — см. pop_state.c.
|
||
* Звать из отлаживаемого места под нужным `if`, брейкпоинт ставить на
|
||
* _pop_dbg_trap (резидент W1, адрес однозначен). */
|
||
extern uint8_t pop_dbg_hits;
|
||
void pop_dbg_trap(void);
|
||
|
||
#endif
|
||
|
||
/* Зеркало уровня 4 (jumped_through_mirror, seg003). −1 — Кид ТОЛЬКО ЧТО
|
||
* пролетел сквозь зеркало (ставит is_obstacle в pop_map, банк 3), и
|
||
* следующий pop_check_mirror родит тень; 0 — обычное состояние.
|
||
* Межбанковый: пишет pop_map, читает pop_map же, но через главный цикл. */
|
||
extern int8_t pop_jumped_mirror;
|
||
|
||
|
||
|
||
/* ВРЕМЕННО: маркеры фаз кадра (замер). */
|
||
void pop_dbg_m1(void); void pop_dbg_m2(void);
|
||
void pop_dbg_m3(void); void pop_dbg_m4(void);
|
||
|
||
/* ВРЕМЕННО: состав перерисовок в самом тяжёлом кадре (замер). */
|
||
extern uint8_t pop_dbg_rdmax[10];
|
||
extern uint8_t pop_dbg_rdmax_tot;
|
||
|
||
/* ВРЕМЕННО: маркеры внутренних замеров. */
|
||
void pop_dbg_m5(void); void pop_dbg_m6(void);
|
||
void pop_dbg_m7(void); void pop_dbg_m8(void);
|
||
void pop_dbg_m9(void); void pop_dbg_m10(void);
|
||
void pop_dbg_m11(void); void pop_dbg_m12(void);
|
||
void pop_dbg_m13(void); void pop_dbg_m14(void);
|
||
void pop_dbg_m15(void);
|
||
void pop_dbg_kind(uint8_t k); void pop_dbg_m16(void);
|
||
void pop_dbg_b1(void); void pop_dbg_b2(void); void pop_dbg_b3(void);
|
||
void pop_dbg_b4(void); void pop_dbg_b5(void); void pop_dbg_b6(void);
|
||
/* ВРЕМЕННО (разбор pop_phys_tick 2026-08-19, позиция P2a): физика Кида —
|
||
* 61 266 тактов на НЕПОДВИЖНОМ персонаже. По одному зонду на звено
|
||
* цепочки kid_phys. */
|
||
void pop_dbg_p1(void); void pop_dbg_p2(void); void pop_dbg_p3(void);
|
||
void pop_dbg_p4(void); void pop_dbg_p5(void); void pop_dbg_p6(void);
|
||
void pop_dbg_p7(void); void pop_dbg_p8(void);
|
||
void pop_dbg_wh(uint16_t wh);
|
||
|
||
/* ВРЕМЕННО: трасса kidobj (см. pop_state.c). Порядок байт:
|
||
* 0 obj_tilepos, 1 frame, 2 action, 3 curr_col, 4 char_col_left,
|
||
* 5 char_col_right, 6 char_top_row, 7 char_bottom_row, 8 комната. */
|
||
extern int8_t pop_dbg_obj[9];
|
||
/* ВРЕМЕННО: разбор оверлея падающего куска (см. pop_state.c). */
|
||
extern uint8_t pop_dbg_pass[14];
|
||
extern uint8_t pop_dbg_ovl[14 * 4];
|
||
extern uint8_t pop_dbg_tiles[14];
|
||
void pop_dbg_kidobj(void) __banked;
|