Files
Sprinter-SDCC/applications/PoP/roomtest/pop_state.c
T
snark13 f89b7dd0d2 Ускорение холостого хода: указательный обход вместо arr[i] в горячих циклах
Замер зелёного блока (комната 23 уровня 13, MAME, такты эмулятора) показал,
что 86% его стоимости в ПОКОЕ — это pop_loose_tick, который не делает ничего.

Причина — кодоген SDCC, подтверждена чтением .asm.  С `int pos` и записью
pop_loose_modif[pos] компилятор держал счётчик в IX-фрейме, каждую итерацию
заново складывал 16-битный адрес элемента, клал его в локал и тут же
вычитывал обратно парами `pop bc / pop hl / push hl / push bc`.  40 холостых
итераций (30 тайлов + 10 потолков) стоили 27 936 тактов.  То же в
pop_loose_mob_tick: запись mobs[i] заставляла умножать i на sizeof(mob_t)=15
заново под КАЖДОЕ поле (.active/.clean/.x/.y), 14 пустых слотов — 35 790.

Правка — обход указателем, счётчик uint8_t, пустые слоты отсеиваются в
вызывающем цикле (а не гардом внутри mob_tick_one, до которого надо ещё
дойти).  Холостая итерация стала `ld a,(de) / or a,a / jp Z` — три
инструкции вместо дюжины с обращениями к памяти.

Результат (такты MAME, холостой кадр):
  циклы по тайлам      27 936 -> 9 852   (2,8x)
  pop_loose_mob_tick   35 790 -> 7 416   (4,8x)
  pop_loose_tick       70 866 -> 24 384  (2,9x)
  ЗЕЛЁНЫЙ БЛОК         82 242 -> 35 760  (2,3x)
Банки ужались: BANK3 -90 Б, BANK7 -74 Б.

Все 8 наборов tests-host проходят; комната 23 в MAME рисуется корректно.

Плюс ВРЕМЕННАЯ оснастка замера (маркеры m9..m16, pop_dbg_kind) — она же
показала, что пик зелёного блока сидит НЕ в тряске плит, а в запекании
тайлов; разбор продолжается, оснастку снять перед закрытием темы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 23:08:05 +03:00

149 lines
10 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.c — данные, которые читают ОБЕ стороны банковой границы.
*
* Зачем отдельный модуль: sprinter-cc собирает банковый модуль с
* --dataseg BANKn, поэтому ВСЯ его писучая память (глобалы, file-static,
* статики внутри функций) линкуется В СТРАНИЦУ БАНКА и снаружи не читается
* — проверено на .map (2026-07-30: глобал банкового модуля лёг по
* 0x0001C000, а не в _DATA). Значит любую переменную, к которой нужен
* доступ и из банка, и снаружи, надо держать в НЕбанковом модуле — здесь.
*
* Модуль обязан оставаться в W1/W2: его читают и главный цикл, и банки.
*/
#include "pop_state.h"
#include "pop_ctrl.h" /* объявления шины control_* (живёт здесь) */
uint8_t pop_loose_landed;
/* Кусок loose УШЁЛ ВНИЗ из комнаты (порт хвоста move_loose, seg007:1126:
* mob_down_a_row переносит его в комнату снизу; у нас симуляция там не
* продолжается, и приземление разбирает главный цикл). 0 = нет, иначе
* col+1; комната, ИЗ которой он ушёл, — в pop_loose_exit_room.
* Сигнал отдельный от pop_loose_fell: тот взводится в момент ОТРЫВА плиты
* (там верен только remove_loose), и по нему кнопка внизу нажималась,
* пока кусок ещё летел. */
uint8_t pop_loose_exit;
uint8_t pop_loose_exit_room;
uint8_t pop_droppedout;
/* leveldoor_open (seg007:456): дверь уровня доехала до верха. Ставит
* анимация двери (pop_trob, банк 6), читает спецсобытие подъёма скелета
* (guards.c, банк 1) — снова случай «данные через границу банков».
* Сбрасывается стартом уровня (seg003:97).
*
* ШИРИНА — word, как в оригинале (data.h:361), а не byte: на уровне 8
* do_timers СЧИТАЕТ этой же переменной время до появления мыши
* (`++leveldoor_open` каждый кадр, пока Кид в комнате 16), и байт
* переполнился бы через 255 кадров, обнулив признак «дверь открыта». */
uint16_t pop_leveldoor_open;
/* ---- Шина управления (seg006 globals) ------------------------------ *
* Объявлены в pop_ctrl.h, а ЛЕЖАТ здесь: к ним обращаются ТРИ банка —
* pop_ctrl (банк 5) пишет с клавиатуры, guards.c (банк 1) подаёт
* синтетический ввод ИИ (autocontrol_*, seg002), pop_map (банк 3) читает
* через pop_ctrl_shift_held. Сегодня оно уцелело бы и в pop_ctrl.c
* (собираем без --bank-data, данные банков остаются в _DATA), но это
* флаг сборки: включат его — и шина уедет в страницу банка 5.
*
* control_x/y/shift — «сырое» состояние осей ЭТОГО кадра (абсолютно:
* X LEFT/-1..RIGHT/+1); *_forward/backward/up/down/shift2 — конечный
* автомат held/released/ignore (авто-повтор). */
int8_t control_x, control_y, control_shift;
int8_t control_forward, control_backward, control_up, control_down, control_shift2;
/* Чит «бессмертие» (клавиша I). Отладочный: Кид получает анимацию удара,
* но HP не теряет — иначе проверять боёвку приходится с трёх попаданий. */
uint8_t pop_immortal;
/* Габариты последней нарисованной двери уровня (порт leveldoor_right /
* leveldoor_ybottom): пишет draw_leveldoor из банка pop_bg, читает
* clip_char из pop_map. См. pop_state.h. */
int pop_leveldoor_right;
int pop_leveldoor_ybottom;
/* ---- Отладочная «пустышка» для брейкпоинтов из БАНКОВ ---------------- *
* Зачем. PC-брейкпоинт видит только логический адрес, а 0xC000+ — это
* окно, куда мапятся ВСЕ банки: точка на адресе банковой функции ловит
* заодно чужой код, случайно легший по тому же смещению (проверено:
* точка на start_fall из банка 3 срабатывала на каждом кадре — попадала
* в pop_bg из банка 2). Условные брейкпоинты этот отладчик MAME не
* поддерживает («error in assignment expression» на `==`).
*
* Приём (идея пользователя, 2026-08-04): позвать ЭТУ функцию ровно из
* того места банкового кода, которое отлаживаем, и поставить брейкпоинт
* на неё — она резидентна в W1, её адрес однозначен. По возврату из неё
* читаем что нужно. Условие «когда именно ловить» пишется обычным `if`
* в C — это гибче любых выражений отладчика.
*
* Счётчик нужен, чтобы вызов не выкинул оптимизатор; заодно видно, сколько
* раз точка прошла, если ловим не останавливаясь. */
uint8_t pop_dbg_hits;
void pop_dbg_trap(void) { pop_dbg_hits++; }
/* Зеркало уровня 4 — см. pop_state.h. */
int8_t pop_jumped_mirror;
/* Тайл СОСЕДНЕЙ комнаты изменился, пока она не отрисована (плита, которую
* Кид расшатал и бросил, провалилась заочно — animate_loose в pop_trob).
* Кромочные колонки и у отрисовки (lcol_fg), и у коллизии (g_lcol/g_rcol) —
* СНИМКИ на момент входа в комнату, поэтому их надо перечитать. Ставит
* банк 6, разбирает главный цикл. */
uint8_t pop_neigh_dirty;
/* seamless (seg000:900 + seg003:158): переход 12 → 13 идёт БЕЗ двери
* уровня — уровень кончается фактом попадания в комнату 23, и следующий
* начинается в комнате с тем же номером и тем же рядом. Флаг говорит
* старту уровня «не сбрасывай HP» (и, когда появятся заставки, «не
* показывай „LEVEL 13“»): Кид продолжает бежать, не заметив границы.
* Ставит главный цикл, гасит pop_start_level. */
uint8_t pop_seamless;
/* «Кид попал в комнату ЧИТОМ навигации» — держит бесшовный переход уровня.
* Отладочная добавка, у оригинала телепорта между комнатами нет вовсе; см.
* pop_dbg_roomnav и ../docs/impl_diff.md. Снимается первой же СМЕНОЙ
* КОМНАТЫ ОБЫЧНЫМ ХОДОМ и стартом уровня. */
uint8_t pop_nav_hold;
/* ВРЕМЕННО (замер фаз кадра 2026-08-13): четыре маркера-пустышки в
* РЕЗИДЕНТЕ. Брейкпоинт на каждом + printf totalcycles даёт длительность
* блоков: m1..m2 — логика (синий), m2..m3 — фон (зелёный), m3..m4 —
* спрайты (циан). В банк брейкпоинт ставить нельзя: 0xC000 общее окно. */
void pop_dbg_m1(void) { }
void pop_dbg_m2(void) { }
void pop_dbg_m3(void) { }
void pop_dbg_m4(void) { }
/* ВРЕМЕННО (разбор зелёного блока 2026-08-13): состав перерисовок в САМОМ
* тяжёлом кадре. pop_redraw_needed считает вызовы по видам, и если кадр
* оказался тяжелее прежнего максимума — сохраняет расклад сюда.
* Индексы 0..7 — POP_RD_*, 8..9 — POP_RDA_* (полоса у потолка). */
uint8_t pop_dbg_rdmax[10];
uint8_t pop_dbg_rdmax_tot;
/* ВРЕМЕННО (замер внутренностей 2026-08-13): m5..m8 — границы внутри
* pop_ceil_shake_draw (heal / draw_tile) и mob_render (heal / блиты). */
void pop_dbg_m5(void) { }
void pop_dbg_m6(void) { }
void pop_dbg_m7(void) { }
void pop_dbg_m8(void) { }
/* ВРЕМЕННО (разбор pop_loose_tick 2026-08-13): m9..m12 — границы ВНУТРИ
* самого pop_loose_tick. Он банковый, но зовёт резидент W1 напрямую,
* так что брейкпоинты по этим адресам однозначны. */
void pop_dbg_m9(void) { }
void pop_dbg_m10(void) { }
void pop_dbg_m11(void) { }
void pop_dbg_m12(void) { }
/* ВРЕМЕННО (разбор pop_ceil_shake_draw 2026-08-13): heal / draw_tile. */
void pop_dbg_m13(void) { }
void pop_dbg_m14(void) { }
void pop_dbg_m15(void) { }
/* ВРЕМЕННО (разбор pop_redraw_needed по ВИДАМ 2026-08-13): вид приезжает в
* A (__sdcccall(1), uint8 arg1), брейкпоинт на первой инструкции логирует
* его вместе с тактами — так видно цену КАЖДОГО вида, а не число пометок. */
void pop_dbg_kind(uint8_t k) { (void)k; }
void pop_dbg_m16(void) { }