Files
Sprinter-SDCC/applications/PoP/roomtest/pop_pace.h
T
snark13 6bdac70508 Отладочная метка: уровень, комната, режим скорости, бессмертие; дефолт NORMAL
Четыре блока палочками в верхнем борте, каждый своим цветом.  Цвета взяты
из 0x3A..0x3F — единственного диапазона, который не перезаписывают ни
зелья (0x40), ни env/wall тайлсета (0x50/0x60), ни страж (0x90).  Прежняя
метка комнаты рисовалась цветом 0x57, то есть из env-диапазона: белой она
была только в подземелье, во дворце брала цвет тайлсета.

Записи палитры — BGR (BIOS $A4), не RGB; читать kid.pal «как привычно»
нельзя, цвета выйдут переставленными.

Режимы перенумерованы: NORMAL=0, FAST=1, FASTEST=2.  Тогда дефолт (crt0
зануляет _DATA) — NORMAL, обход инкрементом даёт NORMAL->FAST->FASTEST, а
номер режима + 1 = число палочек.
2026-08-20 09:40:57 +03:00

103 lines
7.9 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.h — фиксированный логический кадр (frame pacing) по ЛУЧУ.
*
* Задача: период логического кадра = max(n, ceil(W)) вместо нынешнего
* ceil(W) + 2, где W — работа в растровых кадрах. Сегодня главный цикл
* ждёт три gfx_wait_vsync ПОСЛЕ отрисовки, поэтому реальный бюджет кадра
* — один растр (430 080 тактов), и превышение его на такт стоит целого
* лишнего растра. При n=3 бюджет становится 1 290 240.
*
* ПОЧЕМУ НЕ СЧЁТЧИК КАДРОВЫХ ПРЕРЫВАНИЙ (замеры 2026-08-19, MAME, полный
* разбор в docs/frame_pacing_plan.md). Импульс запроса кадрового
* прерывания живёт 32 такта неразогнанного клока = 9,14 мкс, а ядра
* акселератора держат di на весь чанк блита (~0,29 мс). Импульс,
* попавший в такое окно, теряется насовсем — ядро z80 уровневое, защёлки
* нет (то же и на настоящем Spectrum). Замерено: в сцене 11/15 теряется
* от 0 до 2,8 % прерываний в зависимости от ФАЗЫ рендера относительно
* луча, а на полной перерисовке комнаты — три подряд. Сейчас фаза
* плавает (период то 3 растра, то 4) и потери размазаны; при жёстком
* пейсинге фаза застынет, и сцена может залипнуть в плохой — это ровные
* −25 % скорости, невидимые в профиле тактов.
*
* ЧТО ВМЕСТО. Бит 5 порта 0xFE — это ЧТЕНИЕ ПОЛОЖЕНИЯ ЛУЧА (1, когда
* vpos >= 272; MAME sprinter.cpp kbd_fe_r), а не событие: ни импульса,
* ни очереди, ни защёлки. Такой сигнал невозможно потерять — можно
* только не посмотреть. Считаем фронты 1→0 (это vpos = 0, ровно тот же
* момент, которого ждёт gfx_wait_vsync) программно, выборкой из точек,
* которые код и так проходит.
*
* УСЛОВИЕ ТОЧНОСТИ — ОДНО: между соседними выборками должно проходить
* меньше 64 512 тактов (длина окна «бит 5 = 1»: 48 строк из 320). Тогда
* в каждый бланк попадает хотя бы одна выборка, а в промежуток между
* бланками (365 568 тактов) — тем более; значит каждый фронт засчитан
* ровно один раз. Двойной счёт невозможен: инкремент только на переходе,
* и beam_prev тут же обновляется. Условие не про нагрузку, не про di,
* не про прерывания и не про звук — только про зазор.
*
* Запас: самый длинный неделимый кусок без выборки — одно di-окно, то
* есть один вызов _bgi_blit_rows_raw, а он по контракту режется на чанки
* ≤16 строк. Даже если считать грубо — одна выборка на целый блит, а
* самый дорогой замеренный блит 32 073 такта — зазор вдвое меньше окна.
*
* ВАЖНО: бит 5 читается осмысленно ТОЛЬКО при включённом cbl_mode (в
* MAME `data &= ~0xa0` стоит внутри `if (cbl_mode())`, иначе бит выставлен
* в 1 намертво). Его лениво взводит сам gfx_wait_vsync через
* _cbl_port_ref, поэтому pop_pace_arm обязан один раз позвать
* gfx_wait_vsync и ПРОВЕРИТЬ, что фронты реально идут.
*/
#ifndef POP_PACE_H
#define POP_PACE_H
#include <stdint.h>
/* Режимы скорости. Растровый кадр Sprinter — 20,48 мс (48,83 Гц).
* Оригинал (SDLPoP/src/seg003.c:363): base_speed 5 тиков при 60 Гц =
* 83,3 мс, fight_speed 6 = 100 мс. Условие боя у оригинала одно и
* буквальное — «у Кида ВЫНУТ МЕЧ» (Kid.sword == sword_2_drawn), не
* «идёт бой» и не «рядом страж»; проверяется в верху главного цикла. */
/* Нумерация НЕ произвольная: 0 обязан быть дефолтом (crt0 зануляет _DATA),
* а обход по кругу инкрементом даёт ровно NORMAL -> FAST -> FASTEST.
* Заодно номер режима = число палочек в отладочной метке минус один. */
#define POP_SPEED_NORMAL 0 /* 4/5 — 81,9 / 102,4 мс; повтор оригинала */
#define POP_SPEED_FAST 1 /* 3/4 — 61,4 / 81,9 мс; бой как в оригинале */
#define POP_SPEED_FASTEST 2 /* 3/3 — 61,4 мс; не успели — подтормаживаем */
#define POP_SPEED_MODES 3
extern uint8_t pop_speed_mode; /* POP_SPEED_*; дефолт — NORMAL */
/* Клавиша смены режима по кругу (PS/2 set 2). P — свободна: заняты
* K/I/S/L/U/[/] (читы, pop_cheat.h) и +/- (обход комнат). */
#define KBD_SPEED_MODE 0x4D /* P */
/* Делитель для текущего режима. fight = «у Кида вынут меч». */
uint8_t pop_pace_n(uint8_t fight);
/* Счётчик кадров. volatile: его правит pop_beam_sample, а читают циклы
* ожидания — перечитывать обязаны каждый оборот. */
extern volatile uint8_t pop_frame_tick;
/* ВЫБОРКА ЛУЧА. Дёшево (~57 T с вызовом), не трогает bc/de/ix.
* Звать отовсюду, где иначе получился бы зазор длиннее 64 512 тактов:
* из обёрток рисования и с границ фаз. Модуль НЕ банковый, поэтому из
* банков зовётся прямым call, без трамплина. */
void pop_beam_sample(void) __preserves_regs(b, c, d, e);
/* Однократная подготовка: взвести cbl_mode, снять первое состояние бита
* и убедиться, что фронты идут. 0 — луч не работает (тогда пейсинг
* молча откатывается на прежнее поведение: n ожиданий gfx_wait_vsync). */
uint8_t pop_pace_arm(void);
/* Ждать ОДИН фронт луча. Внутри — тот же плотный опрос клавиатуры, что
* сейчас висит idle-хуком на gfx_wait_vsync (KBD-1: без опроса раз в
* ~0,5 мс теряются нажатия и залипают клавиши). */
void pop_wait_edge(void);
/* Добрать до n фронтов ОТ ЯКОРЯ и переставить якорь ПО ФАКТУ.
* Вызывающий обязан перед этим сделать хотя бы один pop_wait_edge() —
* на нём же и происходит своп страниц (tear-free). Якорь ставится
* по факту, а не anchor += n: догонять пропущенное время нельзя, иначе
* после тяжёлого кадра игра рванёт вперёд. */
void pop_pace_end(uint8_t n);
#endif