6bdac70508
Четыре блока палочками в верхнем борте, каждый своим цветом. Цвета взяты из 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 = число палочек.
103 lines
7.9 KiB
C
103 lines
7.9 KiB
C
/*
|
||
* 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
|