From 5e9c6a2e9e521fe5f5a79a60f1498530f2668406 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 19 Aug 2026 23:21:33 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D0=B9=D1=81=D0=B8=D0=BD=D0=B3:=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D1=8B=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=80=D0=BE=D0=B2=20=D0=B8=20=D0=B3?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BB=D0=B8=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- applications/PoP/docs/frame_pacing_plan.md | 96 ++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/applications/PoP/docs/frame_pacing_plan.md b/applications/PoP/docs/frame_pacing_plan.md index 249cefc..38a1669 100644 --- a/applications/PoP/docs/frame_pacing_plan.md +++ b/applications/PoP/docs/frame_pacing_plan.md @@ -505,3 +505,99 @@ flip_page(); 4. Совпадает ли момент возврата `wait_edge()` с нынешним возвратом `gfx_wait_vsync()` с точностью до микросекунд (иначе поедет момент свопа и появятся разрывы картинки). + + +--- + +# 10. РЕЗУЛЬТАТ (2026-08-19, реализовано и проверено в MAME) + +Реализовано в приложении (`roomtest/pop_pace.c/.h`), в libbgi пока НИЧЕГО не +переносили — по решению пользователя: сначала обкатать у себя. + +## 10.1. Что сделано + +- `pop_beam_sample()` — выборка бита 5 порта `0xFE`, 10 инструкций, + быстрый путь 46 T + вызов. Модуль НЕ банковый, поэтому из банков + зовётся прямым `call` (проверено: банки так зовут `_pop_cd_hit_slot`). +- `pop_wait_edge()` — ожидание одного фронта; внутри тот же + `kbd_raw_poll()`, что раньше висел idle-хуком. +- `pop_pace_end(n)` — добрать до n фронтов от якоря; якорь ставится ПО + ФАКТУ. Главный цикл: `pop_wait_edge()` → строб вспышки → + `pop_pace_end(n)` → своп страниц. +- `pop_pace_arm()` — взводит `cbl_mode` через `gfx_wait_vsync()` и + ПРОВЕРЯЕТ, что фронты идут; если нет — `pace_ok = 0` и всё молча + откатывается на прежние `gfx_wait_vsync`. +- Режимы FASTEST/FAST/NORMAL, клавиша **P** по кругу, дефолт FASTEST. + Условие боя — `Kid.sword == SWORD_2_DRAWN`, буквально как у оригинала. + +## 10.2. Где стоят выборки и как они выбраны + +Точки ставились **не на глаз, а по замеру**: детектор зазора между +выборками (порог 64 512) останавливает машину, адрес возврата со стека +называет виновника. Пять итераций «замерил → закрыл дыру → перемерил»: + +| итерация | найденная дыра | тактов | +|---|---|---:| +| 1 | `kid_tick` + `pop_phys_tick` без выборок | 68 340 | +| 1 | весь циан, когда оба персонажа «тихие» | 100 044 | +| 2 | отрисовка персонажей идёт мимо `pop_blit_b` | 82 242 | +| 2 | полоса HP: `pop_kid_img_blit` в цикле | 86 586 | +| 3 | между двумя `pop_blit_b` — работа `pop_bg` | 75 000 | +| 4 | сам блит и сам heal (выборка была только НА ВХОДЕ) | 71 900 | +| 5 | `pop_loose_tick` | 73 990 | + +Итог: выборки в `pop_blit_b` (вход и перед каждым `gfx_w0_unmap`), +`pop_heal_fast` (вход и выход), после каждого `gfx_set_bank(SPRITE)` в +`pop_cdraw/pop_kdraw/pop_room`, в 16 потайловых функциях `pop_bg`, после +зондов `pop_dbg_p1..p8` в физике и в 21 точке главного цикла. + +## 10.3. Замеры + +Метод точности счётчика — **атомарный снимок одной командой отладчика**: +`printf "%d %d", totalcycles, b@<адрес pop_frame_tick>`. Раздельные +`lmem` и `print totalcycles` НЕ ГОДЯТСЯ: между двумя обращениями к мосту +проходят десятки кадров, и «недосчёт» получается на ровном месте (на этом +я сначала и обжёгся). + +| проверка | результат | +|---|---| +| счётчик, 11/15 покой, 5 окон | недосчёт **0** (270 растровых кадров) | +| счётчик, 11/15 тяжёлая позиция Кида | недосчёт **0** | +| счётчик, 13/23 | недосчёт **0** | +| период кадра, 11/15 | **ровно 3 растра**: ни длиннее 3,1, ни короче 2,9 на 302 логических кадрах | +| период кадра, 13/23 | **ровно 3 растра** на 308 логических (эталон был 3/4/5) | +| режим NORMAL | ровно 4 растра | +| NORMAL + `Kid.sword = 2` | ровно 5 растров | +| клавиша P | 0 → 1 → 2 → 0 | +| клавиатура | Кид отвечает на удержание и отпускание | + +**Цена выборок** — A/B прямо в памяти (заглушить `pop_beam_sample` +байтом `C9` и снять `pace_ok`, чтобы игра не зависла в ожидании фронта): +работа за кадр 543 860 с выборками против 539 832 без — **≈4 000 тактов, +0,9 %**. На фоне бюджета, который вырос втрое, это ничто. + +## 10.4. Грабли, стоившие времени + +1. **Литералы в отладчике MAME шестнадцатеричные.** Порог «700000» на + деле проверял 0x700000 = 17 растров и не срабатывал никогда. +2. **Счёт попаданий брейкпоинтом на этом драйвере недостоверен** (WAIT- + линия, инструкция пересчитывается): наблюдались «попаданий больше, чем + растровых кадров». Достоверны только сравнения ВРЕМЁН. +3. **Раздельные чтения через мост не атомарны** (см. 10.3). +4. **Мёртвый Кид перезапускает уровень** раз в `RESPAWN_DELAY` тиков, а + рестарт уровня — это 3-5 растров без единой выборки. Полдня я гонялся + за «дырой в статике», которой не было: Кид успел убежать в соседнюю + комнату и погибнуть, пока я мерил. **Проверяй, что на экране, прежде + чем объяснять числа.** +5. **`make LEVEL=13` без `make clean` не пересобирает** — флаги в + зависимостях не участвуют, на диск уезжает старый уровень. + +## 10.5. Что осталось + +- Перенос примитива в libbgi — по решению пользователя ПОСЛЕ обкатки. + Там же уместнее взводить `_cbl_port_ref` напрямую, без обходного + `gfx_wait_vsync()` в `pop_pace_arm`. +- Загрузка уровня/комнаты остаётся без выборок (ESTEX-вызовы) — счётчик + там недосчитывает. Это безобидно: якорь переставляется по факту, и + ошибка живёт один кадр. Отдельного «resync» не потребовалось. +- Проверить на реальном железе, что `pace_ok` взводится (в MAME — да).