CLIP-1: heal Кида и стража — линейным ядром, когда клип не нужен

kid_heal/pop_guard_heal звали gfx_heal всегда, хотя рисуют ровно тот
прямоугольник, который блит в большинстве кадров кладёт noclip-ядром.
Все три места heal (+ heal_off фона) сведены к общему pop_heal_fast.

Замер в MAME, счётчики totalcycles на входах kid_heal и kid_tick (вся
группа heal за кадр), комната 1, Кид стоит:
  клипающее ядро  26 200 тактов/кадр (149 кадров)
  noclip          15 848 тактов/кадр (239 кадров)
−10 352 такта, −39.5 %.  A/B в одном прогоне: вторая половина снята с
пропатченным в памяти условием (jr nz → jr), то есть на той же геометрии.

Размер СУММАРНО −362 Б: _CODE +17, BANK2 −116 (свободно 2708 — это тесный
банк из рисков levels_plan §5), BANK3 −263, BANK4 без изменений.

Грабли по дороге: первым заходом хелпер был static inline в pop_bg.h —
SDCC 4.5 И встраивает тело (181 Б) в каждый вызов, И оставляет копию в
каждом TU, который видит заголовок.  pop_guard_heal раздулся с ~60 до
663 Б, итого +1091 Б в _CODE и +636 Б в банке стража.  Отсюда pop_draw.c:
обычная функция в резиденте W1, из банков это прямой call без трамплина.

pop_room_clip_borders оставлен клипающим осознанно (320 не лезет в 8 бит,
гейт border_dirty редкий) — причина записана в коде.

Проверено визуально: ходьба, прыжок, спуск, позиция за решёткой шва
(straddle — там работает клипающий фолбэк) — артефактов нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Александр Петров
2026-08-01 21:53:30 +03:00
parent d552cbaca9
commit 1146c57544
11 changed files with 277 additions and 139 deletions
+82 -44
View File
@@ -6,11 +6,11 @@
а то, **что берём в работу сейчас и в каком порядке**. Каждая запись: что
сделать, почему именно сейчас, чем подтверждать результат.
**Открытые баги** (все Medium/косметика, Critical'ов нет):
[BUG-CEIL-1](bug_list.md#bug-ceil-1) · [BUG-CEIL-3](bug_list.md#bug-ceil-3) ·
[BUG-OCCL-1](bug_list.md#bug-occl-1) ·
[BUG-CEIL-2](bug_list.md#bug-ceil-2) (отложен) ·
оптимизации [T-1](bug_list.md#t-1), [T-2](bug_list.md#t-2).
**Открытых багов нет** (ревизия 2026-08-01): в [`bug_list.md`](bug_list.md)
остались только незакрытые оптимизации [T-1](bug_list.md#t-1),
[T-2](bug_list.md#t-2) и незаконченная
[таблица обхода 24 комнат](bug_list.md#обход-всех-24-комнат-уровня-1).
Следующая порция багов придёт оттуда и из L1-PASS.
Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга;
диагноз платформы подтверждать артефактом (брейкпоинт/дамп/.asm), а не
@@ -20,7 +20,8 @@
## P0 — делаем сейчас
*(KBD-1 закрыт до финальной полировки — см. ниже; следующая в работе — CLIP-1.)*
*(KBD-1 закрыт до финальной полировки, CLIP-1 сделан — оба ниже. P0 пуст;
следующая в работе — P1, начиная с L1-START/L1-EXIT.)*
### KBD-1. Shift + стрелки: нажатия теряются — **ПРИЧИНА НАЙДЕНА, ФИКС ПРОВЕРЕН**
@@ -284,7 +285,43 @@
---
### CLIP-1. Аудит блитов: где клип не нужен
### CLIP-1. Аудит блитов: где клип не нужен — **СДЕЛАНО 2026-08-01**
> **Итог.** Heal Кида и стража переведены на выбор ядра по тому же тесту,
> что давно стоит у блитов. Замер в MAME (счётчики `totalcycles` на входах
> `kid_heal` и `kid_tick`, то есть вся группа heal за кадр; комната 1, Кид
> стоит, стража нет):
>
> | путь | тактов на кадр | кадров в выборке |
> |------|----------------|------------------|
> | клипающее ядро (как было) | **26 200** | 149 |
> | noclip (стало) | **15 848** | 239 |
>
> **−10 352 такта на кадр, то есть −39.5 % с группы heal** (≈0.49 мс при
> ~21 МГц). A/B честный: оба замера сняты в ОДНОМ прогоне, вторая половина —
> с пропатченным в памяти условием (`jr nz` → `jr` в `pop_heal_fast`), то
> есть на той же геометрии и в той же сцене.
>
> **Размер: −362 Б суммарно** (не плюс!): `_CODE` 25 289 → 25 306 (+17),
> BANK2 13 792 → **13 676** (116, свободно стало 2708 Б — это тот самый
> тесный банк из рисков `levels_plan.md` §5), BANK3 6512 → 6249 (263),
> BANK4 без изменений.
>
> **Грабли, стоившие двух пересборок** (вынесено в память
> `sdcc-static-inline-double-cost`): первым заходом хелпер был `static
> inline` в `pop_bg.h` — и SDCC 4.5 И встроил его тело (181 Б) в каждое
> место вызова, И оставил отдельную копию в КАЖДОМ TU, который видит
> заголовок. `pop_guard_heal` раздулся с ~60 до 663 Б, итого +1091 Б в
> `_CODE` и +636 Б в банке стража. Лечится обычной функцией в одном
> резидентном модуле (`pop_draw.c`, W1 — из банков это прямой `call` без
> трамплина, как у `pop_sword_draw`).
>
> **Проверено визуально:** обычная ходьба, прыжок, спуск и позиция «за
> решёткой шва» (straddle — там как раз работает клипающий фолбэк) —
> артефактов и следов нет.
**Ниже — исходная постановка задачи (что и почему смотрели).**
**Зачем сейчас.** Кадр занят на ~86 %; подготовка клипающего варианта
стоит ~5.6 К тактов на вызов, а общее ядро против линейного — 13 288 против
@@ -296,30 +333,32 @@
`gfx_blit_cols_part_noclip`, иначе в клипающий вариант
(`pop_kid.c:86,591,676`, `pop_gdraw.c:105`).
**Что чинить:**
**Что чинили:**
- `kid_heal()` (`pop_kid.c:613,615`) и `pop_guard_heal()`
(`pop_gdraw.c:65,66`) зовут `gfx_heal`**всегда с клипом**, хотя
`gfx_heal_noclip` существует и `pop_bg.c:147` им уже пользуется по тому же
тесту. Это heal 2–4 прямоугольников КАЖДЫЙ кадр; замер общего ядра —
11 658 тактов на heal 22×22. Тест onscreen у нас уже посчитан рядом.
- `pop_room_clip_borders()` (`pop_bg.c:1193,1194`) — `gfx_heal(0,0,320,…)`:
noclip требует w,h ≤ 255, значит либо два куска по 160, либо оставить как
есть (зовётся по гейту, только в кадрах падения — проверить, что гейт
действительно редкий, прежде чем трогать).
- `kid_heal()` и `pop_guard_heal()` звали `gfx_heal` — **всегда с
клипом**, хотя `gfx_heal_noclip` существует и `heal_off` в `pop_bg.c` им
уже пользовался. Это heal 2–4 прямоугольников КАЖДЫЙ кадр; замер общего
ядра — 11 658 тактов на heal 22×22. Теперь все три идут через общий
`pop_heal_fast` (`pop_draw.c` + `_pop_draw.h`).
- `pop_room_clip_borders()` (`pop_bg.c`) — `gfx_heal(0,0,320,…)`:
**оставлен клипающим осознанно**. Полоса шириной 320 не лезет в 8-битный
параметр noclip-ядра, а бить её на два куска по 160 нет смысла: гейт
`border_dirty` пускает туда только в кадрах падения, и выигрыш подготовки
тонет в цене самих 320×28 пикселей. Причина записана прямо в коде, чтобы
не «оптимизировать» повторно.
**Что обязано остаться с клипом** (зафиксировать в комментарии, чтобы потом
не «оптимизировать» повторно):
- кромочные тайлы фона `pop_bg.c:132` — тайл у края экрана режется по
построению;
**Что обязано остаться с клипом** (зафиксировано комментариями в коде):
- кромочные тайлы фона (`blit_b` в `pop_bg.c`) — тайл у края экрана режется
по построению;
- спрайты при straddle (`kid_render_dx = ∓140`, комната Кида ≠ отрисованной)
и при падении ниже поля — фолбэки `pop_kid.c:594,681`, `pop_gdraw.c:107`.
и при падении ниже поля — фолбэки в `kid_draw`/`pop_guard_draw`;
- борта поля (`pop_room_clip_borders`) — см. выше.
**Порядок работы:** (1) выписать полный список вызовов
`gfx_blit*`/`gfx_heal*` в roomtest с ответом «кто гарантирует on-screen»;
(2) перевести то, что можно, на noclip по существующему тесту; (3) замерить
кадр полосами бордюра ДО/ПОСЛЕ (`PROF()` уже в `roomtest.c`) и брейкпоинтом
на конкретной функции — числом, а не «стало плавнее»; (4) `make size-check`.
**Что уже было правильно** (шаблон, который и распространили): блиты Кида,
стража, клинка и брызг спрашивают `pop_onscreen_cols()` и уходят в
`gfx_blit_cols_part_noclip`. Отдельный случай — `pop_kid_img_blit`: noclip
БЕЗ проверки, потому что единственный вызывающий (полоса HP) рисует по
фиксированным координатам; это тоже помечено в коде.
---
@@ -351,13 +390,17 @@ BUG-1 (провал на row 1 при переходе через открыты
Список разделён на [`bug_list.md`](bug_list.md) (открытое) и
[`bug_closed.md`](bug_closed.md) (закрытое + разбор корней).
**Косметика окклюзии тоже закрыта** — BUG-CEIL-1/2/3 и BUG-OCCL-1 были
починены кодом ещё в июле, а записи никто не снял: `ceil_over_kid_tile`
(`pop_bg.c:1275`), `pop_ceil_modif` + `pop_ceil_shake_draw`/`_bake_empty`
(`:547,815,827`), `bar` с `POP_YOFF+3` в `pop_room_redraw_seam_left` (`:793`),
разделение слоёв по `add_backtable` vs `ptr_add_table` в `overlay_mid_tile`
(`:1367`). Разбор — в [`bug_closed.md`](bug_closed.md).
**Осталось:** закрыть таблицу обхода 24 комнат
([`bug_list.md`](bug_list.md#обход-всех-24-комнат-уровня-1)) — заполнена на
5 строк из 24, инструмент (`ROOMNAV`) готов. Открытая косметика окклюзии
([BUG-CEIL-1](bug_list.md#bug-ceil-1),
[BUG-CEIL-3](bug_list.md#bug-ceil-3),
[BUG-OCCL-1](bug_list.md#bug-occl-1)) остаётся Medium — трогать после
L1-PASS, обход комнат может добавить к ней новые случаи.
5 строк из 24, инструмент (`ROOMNAV`) готов. Делать вместе с L1-PASS: это
единственный оставшийся источник новых багов уровня 1.
### L1-SPEED. Игра идёт быстрее оригинала (найдено 2026-08-01)
Сверка таймингов: оригинал — `BASE_FPS = 60` при `base_speed = 5` тиков на
@@ -365,21 +408,19 @@ L1-PASS, обход комнат может добавить к ней новы
`fight_speed = 6` = **100 мс**. У нас `roomtest.c` ждёт **три** `gfx_wait_vsync()`
= 60 мс, и отдельной скорости боя нет — то есть примерно **+39 % к скорости
эталона**. Соответствие: 4 ожидания (80 мс) обычно, 5 (100 мс) в бою.
**Делать ПОСЛЕ CLIP-1**: замедление кадра спрячет проблемы бюджета вместо
того, чтобы их показать. Проверка — секундомером по одинаковому отрезку
рядом с живым SDLPoP, не «на глаз».
Условие «делать ПОСЛЕ CLIP-1» **снято** — CLIP-1 закрыт (см. P0). Проверка —
секундомером по одинаковому отрезку рядом с живым SDLPoP, не «на глаз».
### L1-PASS. Сквозное прохождение уровня 1
От старта до двери уровня одним заходом: подбор меча, страж, кнопки/ворота,
пики, loose-полы, зелье, падения. Это приёмка этапа 1 и одновременно
регресс-база для уровня 2.
Заодно проверить на живом проходе открытую косметику окклюзии —
[BUG-CEIL-1](bug_list.md#bug-ceil-1),
[BUG-CEIL-3](bug_list.md#bug-ceil-3),
[BUG-OCCL-1](bug_list.md#bug-occl-1): именно здесь станет видно, мешают ли
они играть или это придирки на стоп-кадре. И подъём на тайл-кнопку — см.
оговорку к BUG-3 в [`bug_closed.md`](bug_closed.md).
Совмещать с [таблицей обхода 24 комнат](bug_list.md#обход-всех-24-комнат-уровня-1):
известные баги закрыты, значит новые придут только отсюда. Точки, где стоит
смотреть внимательно, — уже закрытая косметика окклюзии (потолок при прыжке
вверх, шов при анимации решётки, грани дальней колонны) и подъём на
тайл-кнопку: см. оговорку к BUG-3 в [`bug_closed.md`](bug_closed.md).
---
@@ -389,9 +430,6 @@ L1-PASS, обход комнат может добавить к ней новы
- **Таймер уровня / HUD времени / меню / сохранения** — Фаза 6.
- **[T-1](bug_list.md#t-1)** (пики: перерисовка по причине) — отдаётся почти
бесплатно после [T-2](bug_list.md#t-2), отдельно не окупается.
- **[BUG-CEIL-2](bug_list.md#bug-ceil-2)** (loose-плита в потолке из комнаты
сверху) — требует персистентного per-room modifier соседей, это Фаза P0 из
`../docs/gates_spikes_plan.md`.
- **Отключение мыши на время игры** и **замена PRNG**
`../docs/ideas_backlog.md` (оба дают доли процента кадра).
- **OPT-1** (хирургический редрой шва) — решено НЕ делать, стоимость