P15 переписан: перекрытия нет, виновата грубость метки

Пользователь поймал ошибку в моём расчёте, глядя на экран: страж целиком
правее пламени, пересекаться может только меч.

Проверка по памяти машины подтвердила и уточнила:

  страж, спрайт   x 257..284  y 18..56
  страж, клинок   x 241..261  y 31..37
  пламя факела    x 232..247  y  5..22

Ошибок было две.  Первая: координаты пламени я взял по предположению
«факел в колонке 7», а он в колонке 6 (пламя рисуется в ячейке правого
соседа).  Вторая, содержательная: ФИЗИЧЕСКОГО ПЕРЕКРЫТИЯ НЕТ ВООБЩЕ — по x
клинок и пламя пересекаются, но по y между ними девять пикселей зазора.

Настоящая причина: pop_cd_touch хранит метку как маску КОЛОНОК по 32 px на
ТРИ ряда по 63 px (cd_row_of).  Пламя (y 5..22) и клинок (y 31..37)
попадают в один ряд 0 и одну колонку 7 — cd_quiet считает слот задетым.
148 302 такта, 23 % кадра, за ложную тревогу.

Решение стало проще и точнее: хранить на колонку диапазон y вместо номера
ряда (10 x 2 байта x 2 страницы = 40 байт).  Расчётом проверено, что это
спасает стража и НЕ спасает Кида в тяжёлой позиции — там перекрытие
настоящее, и он честно перерисовывается.  Вариант с 8-пиксельными полосами
тоже работает, 16-пиксельные уже нет.

Прежние предложения (частичная перерисовка по пересечению, обрезка фона под
персонажем) записаны как НЕ НУЖНЫЕ: они решали задачу, которой нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 15:40:30 +03:00
parent a65da96960
commit 17c41b32de
2 changed files with 64 additions and 52 deletions
+41 -37
View File
@@ -261,58 +261,62 @@ MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS.
## P0 — делаем сейчас
### <a id="char-partial-redraw"></a>CHAR-PARTIAL-REDRAW. Неподвижный персонаж перерисовывается целиком из-за нескольких пикселей — ОБЯЗАТЕЛЬНО
### <a id="char-partial-redraw"></a>CHAR-PARTIAL-REDRAW. Неподвижный персонаж перерисовывается зря: метка «фон трогали» слишком груба — ОБЯЗАТЕЛЬНО
**Постановка (пользователь, 2026-08-19).** Проверять, нужна ли отрисовка
стража, когда он НЕ ДВИГАЕТСЯ. Если движется — лишние ~150 000 тактов
приемлемы: в оригинале во время боя число физических кадров на логический
тоже растёт на единицу, и прийти к тому же решению не страшно.
тоже растёт на единицу.
**Измерено** (сцена 11/15, MAME, чтение `pop_cd` из памяти):
**Измерено** (сцена 11/15, чтение `pop_cd` из памяти машины):
| | прямоугольник | размер |
| объект | x | y |
|---|---|---|
| страж | x 257..284, y 18..56 | 28 × 39 |
| пламя правого факела (0,7) | x 264..279, y 5..22 | 16 × 18 |
| **пересечение** | **x 264..279, y 18..22** | **16 × 5** |
| страж, спрайт | 257..284 | 18..56 |
| страж, клинок (накладной) | 241..261 | 31..37 |
| пламя факела (колонка 6 → рисуется в ячейке 7) | 232..247 | 5..22 |
То есть пламя задевает страже только макушку — 80 пикселей, — а
перерисовывается он ЦЕЛИКОМ и стоит **148 302 такта** (85 524 сам спрайт с
клинком и снимком, 62 778 fore-проход). Это 23 % работы кадра.
**Физического перекрытия НЕТ.** По x клинок и пламя пересекаются
(241..247), но по y расходятся: пламя кончается на 22, клинок начинается с
31 — девять пикселей чистого зазора. Пользователь увидел это на экране
раньше, чем я в числах: «страж полностью правее пламени, только меч может
пересекаться».
Механика: `pop_torch_draw` запекает пламя в фон каждый кадр (`TORCH_ANIM_DIV
= 1`), `pop_cd_touch` метит область, `cd_quiet` видит пересечение с bbox
слота и снимает «тихий» бит — дальше `pop_char_draw` + `pop_char_fore`
идут полностью. Метка тут ни при чём: пересечение НАСТОЯЩЕЕ (проверено
числами выше, а не по грубой маске колонок).
**Почему тогда он перерисовывается.** Метка «фон трогали» (`pop_cd_touch`
в `pop_tile.c`) хранится как битовая маска КОЛОНОК по 32 px, отдельно на
каждый из ТРЁХ рядов по 63 px (`cd_row_of`). Пламя и клинок попадают в
один ряд 0 и в одну колонку 7 — и `cd_quiet` считает слот задетым.
Расплата: **148 302 такта, 23 % работы кадра** (85 524 спрайт с клинком и
снимком + 62 778 fore-проход) за перекрытие, которого нет.
**Варианты решения** (оба нуждаются в прототипе и замере):
**Решение — поднять вертикальную точность метки.** Вместо «маска колонок ×
3 ряда» хранить на каждую колонку ДИАПАЗОН y (`ymin`/`ymax`): 10 колонок ×
2 байта × 2 страницы = 40 байт. `pop_cd_touch` расширяет диапазон
затронутых колонок, `pop_cd_hit` проверяет пересечение диапазонов.
- **A. Частичная перерисовка.** Если сигнатура слота не изменилась, но
фон задет — восстановить только пересечение: блит нужных колонок/строк
спрайта (`gfx_blit_cols_part_w` это умеет) и fore-проход только по
задетым тайлам. Ожидание: вместо 148 302 порядка 20 000.
Риск: надо аккуратно с клинком и брызгами (у них свой прямоугольник) и с
порядком слоёв внутри пересечения.
- **B. Не рисовать фон там, где он всё равно перекрыт.** Пламя обрезать по
прямоугольнику неподвижного персонажа — визуально результат тот же (эти
строки под ним и не видны), а персонажа не надо трогать вовсе:
экономия все 148 302. **Опасность:** обрезанное пламя попадает в
ОЗУ-копию, и когда персонаж сдвинется, `heal` вернёт фон С ДЫРОЙ.
Годится, только если запекать в копию полное пламя, а обрезать лишь
запись в видео-ОЗУ — то есть нужен блит с двумя разными масками.
Проверка решения на обоих случаях сцены:
**Порядок:** сначала A (безопаснее и укладывается в существующий контракт
`pop_cd`), B — только если A не хватит.
- **страж:** клинок в колонках 7-8. В колонке 7 у метки лежит y 5..22
(пламя), у клинка 31..37 — не пересекаются, колонку 8 пламя не трогало.
Слот остаётся «тихим» → экономия 148 302;
- **Кид в тяжёлой позиции:** пламя левого факела y 5..22, Кид y 15..55 —
пересечение НАСТОЯЩЕЕ, и он честно перерисовывается. Так и должно быть.
**Проверка:** замер 11/15 в ОБЕИХ позициях Кида (лёгкая x = 99 и тяжёлая
x = 106, разбор в [`../docs/perf_l11_room15.md`](../docs/perf_l11_room15.md)
§7) плюс визуальный прогон боя: персонаж не должен оставлять хвостов и
не должен просвечивать сквозь пламя.
Вариант с 8-пиксельными полосами вместо диапазонов тоже работает
(24 полосы), но 16-пиксельные УЖЕ НЕТ: пламя и клинок снова слипаются в
одной полосе. Диапазон на колонку точнее и не требует битовой возни.
**Чего делать НЕ надо** (проверено расчётом, не повторять): частичную
перерисовку персонажа по пересечению и обрезку фона под ним. Обе правки
решают задачу, которой нет — перекрытия не существует.
**Проверка:** замер 11/15 в обеих позициях Кида (лёгкая x = 99, тяжёлая
x = 106; разбор — [`../docs/perf_l11_room15.md`](../docs/perf_l11_room15.md)
§7), плюс визуальный прогон боя и прохода Кида под факелами: персонаж не
должен оставлять хвостов и не должен просвечивать сквозь пламя.
**Место в очереди — [`../docs/perf_registry.md`](../docs/perf_registry.md),
позиция P15.** Смежное: P14 (fore-проход персонажа — вторая половина той же
цены).
позиция P15.** Смежное: P14 (fore-проход — вторая половина той же цены).
### <a id="heal-width"></a>HEAL-WIDTH. Ширина точечных heal'ов не совпадает со следом перерисовки — ОБЯЗАТЕЛЬНО