Files
Sprinter-SDCC/applications/SprPoP/src/pop_state.h
T
snark13 4e43890fce SprPoP: финал больше не убивает программу — прямой вызов в чужой банк
Пройденная игра доходила до таблицы рекордов и умирала: программа
исчезала, машина следом вставала намертво (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>
2026-08-27 22:11:01 +03:00

142 lines
8.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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;