Тень: набор спрайтов выбирает поле кадра, а не charid; SHADOW.DAT у нас нет

Поправка к вчерашнему выводу «в бою Тень рисуется спрайтами стража».
Набор берётся из cur_frame.sword>>6 (seg008.c:1752), chtab_base жёстко
равен Киду.  Тень идёт через chtab_5, но chtab_5 — это «соперник уровня»,
и на 12-м это SHADOW.DAT: графика КИДА в боевых позах, палитра побайтно
равна палитре Кида.  Пользователь прав — Тень всегда выглядит Кидом.

Наш pop_guard_load уводит тип 4 в guard_names, SHADOW.DAT в ассетах нет
вообще — заведён BUG-SHADOW-SET.

Пересчитал палитру на правильных наборах: 251 кадр, 45 цветов; 16 цветов
гибридом дают 76 грубых промахов на все кадры (было 129 на ошибочном
наборе).
This commit is contained in:
2026-08-20 10:50:36 +03:00
parent b6660d7694
commit e60a04e900
2 changed files with 115 additions and 38 deletions
+81 -38
View File
@@ -33,6 +33,51 @@ case 1: // shadow
Отсюда и берётся необходимость СВОЕЙ палитры: XOR двух цветов игровой
палитры даёт цвет, которого в ней нет.
### Каким набором спрайтов рисуется Тень (проверено 2026-08-20)
Сначала я решил, что в боевых кадрах Тень рисуется спрайтами СТРАЖА, и
записал это в план. **Это было неверно, поправка ниже.**
Набор выбирает НЕ charid. `load_frame_to_obj` (`seg008.c:1752`):
```c
word chtab_base = id_chtab_2_kid; // жёстко Кид
obj_chtab = chtab_base + (cur_frame.sword >> 6); // старшие 2 бита кадра
```
то есть набор берётся из САМИХ ДАННЫХ КАДРА, поле `sword`: младшие 6 бит —
картинка меча, старшие два — смещение chtab относительно Кида
(`types.h:361`). Проверил обе таблицы:
- `frame_table_kid`**все 241 кадра** имеют `sword & 0xC0 == 0` → chtab_2.
Значит **Кид всегда рисуется своими спрайтами**, боевые кадры 150..189 не
исключение;
- `frame_tbl_guard` — все кадры имеют `0xC0` → chtab_5.
Тень берёт `frame_tbl_guard` для кадров 150..189 (`seg006.c:533`), значит в
бою она идёт через chtab_5. **Но chtab_5 — это не «страж», это «соперник
уровня»**: он грузится из `tbl_guard_dat[tbl_guard_type[уровень]]`
(`seg000.c:1117`), а `tbl_guard_type[12] == 4`**SHADOW.DAT**.
Открыл SHADOW.DAT: его палитра **побайтно равна палитре Кида** (у GUARD.DAT
там серая рампа под перекраску), а спрайты — Кид в боевых позах, не страж.
Отрендерил тройками «Кид / SHADOW.DAT / GUARD.DAT» для кадров
151/153/158/161/167: первые две колонки — один и тот же персонаж, третья —
серый страж в тюрбане.
**Вывод: Тень ВСЕГДА выглядит Кидом, и ощущение пользователя верно.**
Спрайты при этом лежат в двух файлах: не-боевые кадры в chtab_2 (KID),
боевые — в chtab_5, заполненном SHADOW.DAT.
### Можно ли взять для боя собственные кадры Кида
Механически да: `frame_table_kid` покрывает 150..189 со своей геометрией,
и хватило бы снять спецветку для `charid_1_shadow` в `load_frame`. Но
кадры НЕ совпадают: из 34 боевых у 31 отличается габарит (на 1-6 px), у 3
отличаются пиксели. Это разная графика, а не одна и та же в двух файлах.
Поэтому берём SHADOW.DAT — он и есть «кадры Кида для Тени», подготовленные
авторами.
## 2. Единственное место, где запечка отличается от оригинала
XOR идёт по тому, что УЖЕ на экране, то есть результат зависит от фона.
@@ -53,21 +98,18 @@ XOR идёт по тому, что УЖЕ на экране, то есть ре
## 3. Замеры на реальных ассетах
Прогон алгоритма по всем кадрам (`SDLPoP/data/KID`, `SDLPoP/data/GUARD`):
Прогон алгоритма по правильным наборам (`SDLPoP/data/KID` +
`SDLPoP/data/SHADOW`):
| набор | кадров | макс. габарит | разных цветов |
|---|---:|---|---:|
| Кид (chtab_2) | 219 | 56×57 | 44 |
| Страж (chtab_5) | 34 | 53×42 | 16 |
| **вместе** | **253** | 57×57 (с учётом +1 px сдвига) | **59** |
| SHADOW.DAT (chtab_5 на ур. 12) | 32 | 49×38 | 22 |
| **вместе** | **251** | 57×57 (с учётом +1 px сдвига) | **45** |
Всего пикселей во всех кадрах Тени — 96 746. Квантование до N цветов
(оставляем N самых частых, остальные — в ближайший):
| палитра | затронуто пикселей | доля | худшая ошибка (евклид RGB) |
|---|---:|---:|---:|
| 16 цветов | 3 483 | 3,60 % | 151 |
| **32 цвета** | **426** | **0,44 %** | 113 |
Цветов вместе почти столько же, сколько у одного Кида: SHADOW.DAT сидит на
той же палитре, новых сочетаний XOR почти не даёт. Всего пикселей во всех
кадрах Тени — 91 045.
### Насколько заметна подмена
@@ -79,37 +121,34 @@ XOR идёт по тому, что УЖЕ на экране, то есть ре
- **B** — взвешенный k-means по всем цветам (двигает вообще все);
- **C** — гибрид: часть слотов под точные частые, остаток — кластеры хвоста.
| палитра | стратегия | изменено px | заметно (40..90) | сильно (>90) | худшая | средняя |
|---|---|---:|---:|---:|---:|---:|
| 16 | A самые частые | 3 483 (3,60 %) | 2 342 | 257 | 151 | 57,7 |
| 16 | B k-means | 29 368 (30,4 %) | 1 972 | 256 | 142 | 10,9 |
| 16 | C гибрид 10+6 | 7 255 (7,50 %) | 2 146 | **129** | 142 | 33,7 |
| **32** | **A самые частые** | **426 (0,44 %)** | **205** | **27** | 113 | 46,0 |
| 32 | B k-means | 3 425 (3,54 %) | 133 | 27 | 110 | 9,1 |
| 32 | C гибрид 20+12 | 1 997 (2,06 %) | 133 | 27 | 110 | 16,0 |
| палитра | стратегия | изменено px | заметно (40..90) | сильно (>90) | худшая |
|---|---|---:|---:|---:|---:|
| 16 | A самые частые | 959 (1,05 %) | 569 | 381 | 128 |
| **16** | **C гибрид 5+11** | 6 984 (7,67 %) | 1 078 | **76** | 114 |
| 32 | A самые частые | 50 (0,05 %) | 40 | 4 | 113 |
| 32 | C гибрид 28+4 | 73 (0,08 %) | 58 | **0** | 90 |
Читается так. При 32 цветах стратегия не важна: «сильно» — ровно **27
пикселей на все 253 кадра** в любой из трёх, это неустранимые одиночки без
близкого соседа (по 1-3 пикселя на кадр, увидеть нельзя). Перцептивно
значимого остаётся 232 пикселя из 96 746, то есть примерно один пиксель на
кадр. При 16 цветах разница уже есть: гибрид вдвое срезает «сильно» (257
→ 129), но заметных всё равно две с лишним тысячи.
Читается так. При 32 цветах всё практически идеально: 73 пикселя на 251
кадр, грубых промахов нет вовсе. При 16 цветах выбор стратегии виден:
«самые частые» трогает меньше пикселей (959), но 381 из них уезжает СИЛЬНО;
гибрид размазывает ошибку — грубых остаётся 76, то есть примерно **0,3
пикселя на кадр**.
### Решение: **16 цветов, стратегия C (гибрид 10 + 6)**
### Решение: **16 цветов, стратегия C (гибрид 5 + 11)**
Числа выше — про пиксели, а решает глаз. Отрендерил одни и те же кадры в
трёх видах (точный цвет / 32 / 16) и сравнил в увеличении ×3: **отличий
не видно**. Объяснение в самих числах: перцептивно значимых пикселей при
16 цветах 2 275 на 253 кадра, это ~9 пикселей на кадр при ~1 000 видимых,
и они РАССЫПАНЫ по контуру, а не собраны в пятно.
16 цветах гибридом — 1 154 на 251 кадр, это ~4,6 пикселя на кадр при
~1 000 видимых, и они РАССЫПАНЫ по контуру, а не собраны в пятно.
Поэтому берём 16, а не 32: экономим блок палитры (пригодится под будущие
наборы — принцесса, визирь, мышь), а разница неразличима.
**Но стратегия обязана быть гибридной.** При 16 цветах «взять самые
частые» вдвое хуже по грубым промахам (257 пикселей против 129), и это
единственное место, где выбор стратегии виден. Гибрид: 10 самых частых
берём ТОЧНО, оставшиеся 6 слотов отдаём под взвешенные кластеры хвоста.
частые» впятеро хуже по грубым промахам (381 пиксель против 76), и это
единственное место, где выбор стратегии виден. Гибрид: 5 самых частых
берём ТОЧНО, оставшиеся 11 слотов отдаём под взвешенные кластеры хвоста.
Если в реальной игре кайма всё же будет резать глаз — переход на 32 цвета
это одна константа в упаковщике и один блок палитры, данные не меняются.
@@ -142,8 +181,8 @@ XOR идёт по тому, что УЖЕ на экране, то есть ре
## 5. Объём
253 кадра против 219 у Кида — по страницам EMM примерно как нынешний
набор Кида (28 страниц), плюс пара на кадры стража. При 215 свободных
251 кадр против 219 у Кида — по страницам EMM примерно как нынешний
набор Кида (28 страниц), плюс пара на кадры из SHADOW.DAT. При 215 свободных
страницах на старте (memory `sprinter_emm_budget`) это не проблема.
Кадры смерти («убитый Кид») пока НЕ вырезаем — экономия несколько
@@ -152,6 +191,9 @@ XOR идёт по тому, что УЖЕ на экране, то есть ре
## 6. План работ
**Ш0. Довезти SHADOW.DAT.** Его у нас нет вовсе (см. BUG-SHADOW-SET) —
добавить каталог ассетов и правило в Makefile рядом с GUARD/SKEL/VIZIER.
**Ш1. Упаковщик** `toolchain/pop_pack_shadow.py`: прогнать оба набора
через алгоритм §1, собрать 16-цветную палитру гибридом (10 точных + 6
кластеров хвоста), выдать
@@ -165,9 +207,9 @@ XOR идёт по тому, что УЖЕ на экране, то есть ре
**Ш3. Отрисовка**: одно место — `pop_cdraw.c:551..568`, где выбирается
`pages`. Сейчас там для соперника берётся `gp`, а для не-боевых кадров
Тени подменяется на `kidp`; добавится ветка «charid == CHARID_1_SHADOW →
shadowp», причём обе половины набора (кадры Кида и кадры стража) лежат в
ОДНОМ атласе Тени, так что подмена становится проще нынешней.
Тени подменяется на `kidp`; станет «charid == CHARID_1_SHADOW → shadowp»
БЕЗ подмены: обе половины (кадры Кида и кадры SHADOW.DAT) лежат в ОДНОМ
атласе Тени, так что ветка становится проще нынешней.
**Ш4. Проверка в MAME**: уровень 4 (Тень у зеркала), уровень 6 (Тень
крадёт зелье), уровень 12 (бой с Тенью — там она в боевых кадрах, то
@@ -179,6 +221,7 @@ shadowp», причём обе половины набора (кадры Кид
как `frame + add_frame - 149` (`seg006.c:535`), то есть у нашего
атласа Тени нумерация двух половин должна совпадать с тем, что уже
делает `pop_frame_tbl_is_guard`.
2. Цвет стража для Тени: у не-стражей `curr_guard_color = 0`
(`seg002:183`), значит запекать половину «кадры стража» надо с
палитрой стража цвета 0, а не с текущей.
2. Перекраска Тени НЕ нужна: SHADOW.DAT приходит уже в палитре Кида, а
`curr_guard_color` у не-стражей равен 0 (`seg002:183`). Ветку
`pop_guard_set_palette` для типа 4 звать нельзя — она затрёт палитру
Тени палитрой стража.