Files
Sprinter-SDCC/applications/PoP/docs/midtable_analysis.md
T
snark13 9e1a55bdc3 Падающие плиты: спрайты падающего набора, замеры слоёв, анализ midtable
Спрайты: у падающего куска obj_id = 10 (add_mob_to_objtable, seg007:1170),
и три части берутся из таблиц по этому индексу — 70/74/72, а не 41/43/42.
Мы рисовали плиту В ПОКОЕ, отсюда «цельная ровная» вместо двух половин со
сдвигом правой на пиксель.

Подпорка «перерисовать соседний тайл поверх куска» убрана: она тащила на
плиту чужой узор и окно, да ещё стоила по одной полной отрисовке тайла на
кусок за кадр.  Клип объекта (clip.right = 40) НЕ портирован — единицы поля
не выяснены, буквальные 40 пикселей срезают задний угол плиты.  Цена отказа
— правая грань куска и передние части чужих тайлов его не перекрывают
(MOB-CLIP-RIGHT, BUGS_OPEN.md).

Коридор heal сужен по высоте 32 -> 24: реальный след спрайта 16 px.
Ширина оставлена 64 — из неё используются 62, экономить нечего; заодно в
комментарии зафиксировано, что «выравнивание блока акселератора» ничем не
подтверждается и требует проверки артефактом.

docs/midtable_analysis.md — разбор слоёв оригинала и замеры:
- логический кадр 1 289 526 тактов;
- pop_redraw_needed 978 тактов (0,08 %);
- fore-проход 128 778 тактов (10 %) на ОДНОГО персонажа, но выполняется
  лишь на 4 % кадров — гасит пропуск неизменившегося персонажа;
- максимум объектов на одном тайле 2 (пара из loose_fall), значит
  сортировка внутри тайла бесплатна.

Вывод: единственный риск порта objtable — сохранится ли окно клипа
fore-прохода; до разбора redraw_frames_fore выбирать вариант рано.

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

228 lines
14 KiB
Markdown
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.
# Слои отрисовки: как устроен оригинал и чего стоит порт
Разбор 2026-08-13, по `../SDLPoP/src/seg008.c`. Повод — семь дефектов
падающих плит на уровне 13, из которых три оказались не багами кода, а
следствием того, что у нас нет слоя, в котором объекты и куски тайлов
сортируются между собой. Решение по этому документу ещё не принято.
---
## 1. Как это работает в оригинале
### 1.1 Три таблицы, а не «слои»
`draw_tables` (seg008:1373) рисует ровно в таком порядке:
```
restore_peels();
draw_wipes(0);
draw_table(0); // BACKTABLE
draw_table(3); // MIDTABLE
draw_wipes(1);
draw_table(1); // FORETABLE
```
Это грубое разделение на три уровня глубины. Куда попадёт кусок тайла,
решает переменная `ptr_add_table`, которую вызывающий переставляет перед
`draw_tile*`: по умолчанию `add_backtable`, в оверлее кромки —
`add_midtable` (`draw_other_overlay`, seg008:1499), а `add_foretable`
вызывается явно и точечно.
### 1.2 Объекты живут НЕ в таблицах, а в objtable — и привязаны к ТАЙЛУ
Персонажи, падающие куски, мечи, брызги попадают в `objtable`, и у каждой
записи есть поле `tilepos` — тайл, которому объект принадлежит.
Ключевое: объекты рисуются **не отдельным проходом после фона**, а ВНУТРИ
обхода тайлов. В `redraw_needed_tile` (seg008:207) стоит:
```c
if (tile_object_redraw[tilepos]) {
if (tile_object_redraw[tilepos] == 0xFF)
draw_objtable_items_at_tile(tilepos - 1);
draw_objtable_items_at_tile(tilepos);
tile_object_redraw[tilepos] = 0;
}
if (redraw_frames_fore[tilepos]) draw_tile_fore();
```
То есть на каждом тайле: сначала его фоновые куски, потом объекты ЭТОГО
тайла, потом его передние куски.
### 1.3 Порядок глубины складывается из ТРЁХ независимых механизмов
| механизм | что даёт |
|---|---|
| порядок обхода тайлов: ряды **2, 1, 0**, колонки 0..9 (seg008:129) | тайл, обойдённый позже, рисуется поверх |
| сортировка объектов ВНУТРИ одного тайла (`sort_curr_objs`, seg008:1553) | кто из объектов одного тайла поверх кого |
| три таблицы back/mid/fore | грубая глубина для кусков тайлов |
Сортировка внутри тайла (`compare_curr_objs`, seg008:1572) — пузырьком, и
правил в ней три:
```
объект типа 1 (ТЕНЬ) — всегда первым;
оба объекта — падающие плиты (0x80): y1 < y2 → по УБЫВАНИЮ y;
любая другая пара: y1 > y2 → по ВОЗРАСТАНИЮ y.
```
Обратный порядок для пары плит — не описка: две плиты из `loose_fall` летят
в 6 пикселях друг от друга, и верхняя обязана лечь поверх нижней.
### 1.4 Что из этого следует
**«Сортируемый midtable» — неточное имя.** Глобальной сортировки среднего
слоя в оригинале нет. Есть привязка объекта к тайлу и сортировка внутри
тайла; всё остальное решает порядок обхода. Это принципиально дешевле
общей сортировки: объектов на один тайл обычно 1-2.
---
## 2. Что делаем мы
Наш кадр — жёсткая последовательность проходов, без привязки объектов к
тайлам:
```
фон: pop_loose_tick (физика кусков) -> pop_process_trobs -> pop_redraw_needed
-> редрой шва
объекты: pop_loose_mob_draw (куски ПОД Kid, отсортированы по y между собой)
pop_char_draw(OPP/KID) (порядок задаёт guard_over_kid)
pop_loose_mob_draw_over (куски ПОВЕРХ Kid)
перед: pop_fore_over_char — ТОЛЬКО в окне вокруг персонажа
```
Отличия, из которых растут все три оставшихся дефекта:
1. **Объект не знает своего тайла.** Глубина «кусок против Кида» считается
отдельной формулой (`pop_room.c`, поле `defer`), а «кусок против КУСКА
ТАЙЛА» не считается вовсе — куски тайлов рисуются раньше всех объектов.
2. **Передний слой считается только вокруг персонажа.** Это наша
оптимизация (memory `pop_fore_layer_cost`: полный проход стоил 78 %
кадра). Падающая плита в чужом углу комнаты передних частей тайлов
поверх себя не получает — отсюда «плита перед колонной».
3. **Оверлей кромки идёт после персонажей** и потому безусловно поверх
всех, тогда как у оригинала он в midtable и сортируется.
---
## 3. Что затрагивает порт
| участок | объём правки |
|---|---|
| `pop_room.c` — куски | привязать к тайлу, убрать `defer`, убрать собственную сортировку |
| `pop_cdraw.c` — персонажи | то же: объект вместо слота, привязка к тайлу |
| `pop_bg.c``overlay_mid_tile`, `fore_only_tile`, `ceil_over_kid_tile` | вызов из обхода тайлов, а не из отдельного прохода |
| `pop_redraw.c` — пометки | добавить «на этом тайле есть объект» (порт `tile_object_redraw`) |
| `roomtest.c` — главный цикл | вместо трёх проходов один: обход тайлов с объектами внутри |
| окно fore-клипа (`pop_t_fclip_*`) | смысл меняется: клип по тайлу, а не по персонажу |
Плюс новая структура objtable и её сортировка — но маленькая, на тайл.
---
## 4. Плюсы
* **Уходят разом** MOB-CLIP-RIGHT, MID-OVERLAY-LAYER и «плита перед
колонной»: все три — следствие отсутствия привязки к тайлу, а не
самостоятельные баги.
* **Уходят подпорки.** Перерисовка соседнего тайла поверх куска,
`defer`, ручная сортировка кусков, отдельный проход `draw_over`
всё это заменяется одним механизмом.
* **Совпадение с оригиналом по построению.** Дальше любой вопрос «что
поверх чего» решается чтением seg008, а не экспериментом в MAME.
* **Возможный выигрыш по кадру.** Сейчас fore-проход считает окно вокруг
персонажа и всё равно перебирает до девяти тайлов; при привязке к тайлу
передние части рисуются только там, где реально есть помеченный объект.
Но это НАДО ЗАМЕРИТЬ, а не обещать.
---
## 5. Минусы и риски
* **Риск регресса широкий.** Трогается порядок отрисовки ВСЕГО: Кид,
соперник, меч, брызги, зеркало, куски, оверлеи, полоса потолка.
Уровни 1-11 приняты и держатся на текущем порядке.
* **Наша оптимизация fore-окна может не пережить порт в прежнем виде.**
Она даёт 3.2x на самом дорогом проходе (memory `pop_fore_layer_cost`).
Если привязка к тайлу заставит рисовать передние части шире — можно
потерять больше, чем выиграть.
* **Дабл-буфер.** У оригинала один экран с dirty-rect, у нас две страницы
со своими копиями фона и heal. Пометка «на тайле есть объект» обязана
быть счётчиком страниц, как остальные наши пометки, — иначе объект
перерисуется на одной странице и не перерисуется на другой.
* **Банки.** Отрисовка размазана по трём банкам (`pop_bg` 2, `pop_cdraw` 4,
`pop_room` 7) плюс резидент. Единый обход тайлов с объектами внутри
означает, что цикл обхода зовёт код из всех трёх — надо проверить, что не
упрёмся в границы банков и трамплины.
* **Объём.** Это не правка, а этап: сопоставимо с тем, что делалось для
fore-слоя.
---
## 6. Развилки
**A. Полный порт** — objtable с привязкой к тайлу, сортировка внутри тайла,
объекты внутри обхода. Максимально близко к оригиналу, максимальный риск и
объём.
**B. Частичный: только привязать КУСКИ к тайлам.** Персонажей оставить как
есть. Закрывает MOB-CLIP-RIGHT и «плиту перед колонной», не трогает
проверенный порядок персонажей. Дешевле и безопаснее; MID-OVERLAY-LAYER
остаётся.
**C. Отложить** до этапа BG-ONCE и делать вместе — там всё равно
пересматриваются слои, и два пересмотра подряд дороже одного.
Рекомендация: **B или C**. Вариант A целиком оправдан только если мы
одновременно берёмся за BG-ONCE — тогда это один пересмотр слоёв вместо
двух, и замер кадра делается один раз.
---
## 7. ЗАМЕРЫ (сделаны 2026-08-13, уровень 13)
Метод — маркеры-пустышки в РЕЗИДЕНТЕ вокруг измеряемого вызова плюс
брейкпоинты с `printf totalcycles` (memory `z80_profiling_method`). В
банковый код брейкпоинт ставить нельзя: 0xC000 — общее окно всех банков.
| что | такты | доля логического кадра |
|---|---|---|
| логический кадр целиком | **1 289 526** | 100 % |
| `pop_redraw_needed` | **978** | 0,08 % |
| **fore-проход, ОДИН персонаж** | **128 778** | **10 %** |
Плюс два счётчика за прогон ~229 логических кадров:
* fore-проход вызван **10 раз** — на 96 % кадров он не выполняется вовсе
(пропуск неизменившегося персонажа, `pop_char_skip_mask`). Средняя цена
по кадру выходит ~0,4 %, но КАК ТОЛЬКО персонаж движется — платим все 10 %
каждый кадр, и при двух персонажах это ~20 %;
* **максимум объектов на одном тайле = 2** (комната 16, пара из
`loose_fall`: плита сбивает плиту, дальше летят обе). В комнате 23, где
гряда падает в пустоту, максимум 1.
### 7.1 Что эти числа меняют в оценке
**Сортировка внутри тайла — бесплатна.** Два объекта, пузырёк на два
элемента. Возражение против варианта A, которое закладывалось в §5, снято.
**`pop_redraw_needed` можно не считать вовсе.** 978 тактов против 128 778 у
fore-прохода — соотношение 131 к 1.
**Единственный настоящий риск порта — окно клипа fore-прохода.** Если
привязка объектов к тайлам заставит рисовать передние части шире нынешнего
окна вокруг персонажа, мы потеряем 10 % кадра, и потеряем их НА ДВИЖЕНИИ,
когда бюджет и так самый напряжённый.
### 7.2 Что осталось выяснить ДО выбора варианта
Как оригинал отсекает лишнюю работу переднего слоя: `redraw_frames_fore[tilepos]`
рисует передние части ТОЛЬКО у помеченных тайлов (seg008:214). Надо понять,
насколько узко эта пометка ставится — если не шире нашего окна, вариант A
безопасен по кадру; если шире, остаёмся на B.
**Обновлённая рекомендация:** решение упирается в §7.2, и до его разбора
выбирать между A и B преждевременно. B остаётся безопасным запасным
вариантом.