Files
Sprinter-SDCC/applications/SprPoP/src/pop_pace.c
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

106 lines
4.2 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_pace.c — фиксированный логический кадр по лучу. Зачем именно так,
* а не по счётчику кадровых прерываний — в шапке pop_pace.h (там же
* условие точности и замеры).
*
* Модуль НЕ банковый (EXTRA_SRCS): pop_beam_sample зовётся из банков
* рисования, а базовый код в W1 доступен им прямым call — трамплин на
* каждую выборку съел бы весь смысл.
*/
#include <stdint.h>
#include <gfx.h>
#include <kbd_raw.h>
#include "pop_state.h" /* pop_idle — общий idle-хук */
#include "pop_pace.h"
uint8_t pop_speed_mode; /* crt0 зануляет _DATA → NORMAL */
volatile uint8_t pop_frame_tick;
/* Бит 5 порта 0xFE на прошлой выборке: 0 или 0x20. На виду у asm-тела. */
uint8_t pop_beam_prev;
static uint8_t pace_anchor; /* тик на начало текущего логического кадра */
static uint8_t pace_ok; /* 0 — луч не работает, откат на gfx_wait_vsync */
/* ---- выборка луча --------------------------------------------------- *
* in a,(0xFE) кладёт A на старший байт адреса (выбор ряда клавиатуры) —
* биты 5/7 от него не зависят, так же читает и gfx_wait_vsync.
* Быстрый путь (состояние не изменилось) — 40 T + вызов. */
void pop_beam_sample(void) __preserves_regs(b, c, d, e) __naked
{
__asm
in a, (#0xFE)
and a, #0x20
ld hl, #_pop_beam_prev
cp a, (hl)
ret Z ; состояние то же выходим сразу
ld (hl), a
or a, a
ret NZ ; 01: вошли в бланк, это не граница
ld hl, #_pop_frame_tick
inc (hl) ; 10: vpos = 0, начало кадра
ret
__endasm;
}
uint8_t pop_pace_arm(void)
{
uint16_t tries;
uint8_t t0;
/* Взводит cbl_mode (_cbl_port_ref) и заодно ставит нас на фронт. */
gfx_wait_vsync();
/* Затравка beam_prev без ложного фронта: что бы луч ни показал сейчас,
* первая выборка либо совпадёт (ранний выход), либо запишет 0x20 и
* уйдёт по ветке «вошли в бланк». */
pop_beam_prev = 0;
pop_beam_sample();
t0 = pop_frame_tick;
for (tries = 0; tries < 20000; tries++) {
pop_beam_sample();
if ((uint8_t)(pop_frame_tick - t0) >= 2) {
pace_anchor = pop_frame_tick;
pace_ok = 1;
return 1;
}
}
pace_ok = 0; /* бит не шевелится — прежнее поведение */
return 0;
}
void pop_wait_edge(void)
{
uint8_t t;
if (!pace_ok) { gfx_wait_vsync(); return; }
t = pop_frame_tick;
do {
pop_beam_sample();
/* ТОТ ЖЕ ХУК, что висит на gfx_wait_vsync, а не голый kbd_raw_poll:
* кроме плотного опроса FIFO (KBD-1) он проверяет F10, и без этого
* выход из игры в геймплее не работал — пейсинг по лучу через
* gfx_wait_vsync не ходит. */
pop_idle();
} while (pop_frame_tick == t);
}
void pop_pace_end(uint8_t n)
{
if (!pace_ok) { /* откат: как было — добрать ожиданиями */
while (--n) gfx_wait_vsync();
return;
}
while ((uint8_t)(pop_frame_tick - pace_anchor) < n)
pop_wait_edge();
pace_anchor = pop_frame_tick; /* якорь ПО ФАКТУ — фаза не копится */
}
uint8_t pop_pace_n(uint8_t fight)
{
if (pop_speed_mode == POP_SPEED_NORMAL) return fight ? 5 : 4;
if (pop_speed_mode == POP_SPEED_FAST) return fight ? 4 : 3;
return 3; /* FASTEST: и вне боя, и в бою */
}