# Prince of Persia — Kid (персонаж): анализ и план Статус: план (2026-07-16). Опирается на разбор `SDLPoP/src/seg006.c` (ядро физики/управления Kid), `seqtbl.c` (таблицы последовательностей), `types.h` (char_type, seq_*, SEQ_*, actions_*), `SDLPoP/data/KID` (спрайты). Фон уже готов и проверен на MAME (`applications/PoP/roomtest`, см. `memory/pop_background_strategy`) — Kid развиваем в том же `roomtest` как новый PoC (решение пользователя: старый `poc/` не трогаем). **Копирайт:** спрайты Kid (`SDLPoP/data/KID`) — Broderbund/Ubisoft. Использование настоящей графики Kid — сознательное решение пользователя (в отличие от плейсхолдера в старом `poc/`, см. PORT_PLAN §5.1). --- ## 1. Как устроен персонаж в оригинале (что портируем) ### 1.1 Состояние — `char_type` (14 полей, types.h) ``` frame текущий номер кадра (индекс во frame_table_kid) x, y позиция (byte; x — с учётом direction) direction -1 влево / 0 вправо curr_col, логическая клетка (тайл), где персонаж curr_row action КАТЕГОРИЯ действия (actions_*, см. 1.2) fall_x, скорость падения (fall_y<22 = 1 ряд, <33 = 2 ряда) fall_y room комната repeat счётчик для удержания-ввода (напр. повторный прыжок) sword есть ли меч (бой — вне Фазы 1) alive жив/мёртв curr_seq УКАЗАТЕЛЬ в seqtbl (байткод текущей последовательности) ``` Состояние крошечное — легко живёт в W2. ### 1.2 Категории действия — `actions_*` (9 шт) `0 stand`, `1 run_jump`, `2 hang_climb`, `3 in_midair`, `4 in_freefall`, `5 bumped`, `6 hang_straight`, `7 turn`, `99 hurt`. `action` определяет, как `check_action()`/`play_kid()` реагируют на ввод и физику каждый тик. ### 1.3 Движок анимации/движения — ГЛАВНОЕ **Движение НЕ физика, а байткод + per-frame смещения** (подтверждает PORT_PLAN §6). Три уровня: 1. **`seqtbl`** — байткод-программа на действие. Опкоды (types.h): `SEQ_DX`(0xFB) сдвиг x на amount×direction, `SEQ_DY`(0xFA) сдвиг y, `SEQ_FLIP`(0xFE) разворот, `SEQ_JMP`(0xFF)/`SEQ_JMP_IF_FEATHER`(0xF7), `SEQ_UP`/`SEQ_DOWN`(0xFD/0xFC) смена ряда, `SEQ_ACTION`(0xF9) задать `Char.action`, `SEQ_SET_FALL`(0xF8), `SEQ_KNOCK_UP/DOWN`, `SEQ_SOUND`, `SEQ_DIE`/`SEQ_END_LEVEL`/`SEQ_GET_ITEM`. **Байт < 0xF0 = НОМЕР КАДРА** → ставит `Char.frame` и play_seq возвращается (один кадр за тик). 2. **`play_seq()`** (seg006.c:570) — интерпретатор: крутит опкоды из `seqtbl + Char.curr_seq`, пока не встретит кадр. ~15 case — портируется 1-в-1. **Квирк:** seqtbl использует АБСОЛЮТНЫЕ DOS-адреса в JMP; `SEQTBL_0 = seqtbl - SEQTBL_BASE(0x196E)` — при порте пересчитать базу (JMP-адреса в наших данных). 3. **`frame_table_kid[]`** (seg006.c:127, ~180 кадров) — на КАЖДЫЙ кадр: `{image, sword_flags, dx, dy, flags}`. `image` — индекс спрайта Kid; `dx/dy` — смещение позиции ЭТОГО кадра; `flags`: 0x1F weight_x, 0x20 thin, 0x40 needs_floor, 0x80 even/odd-pixel (влияет на x-рендер). **Тик персонажа:** `play_kid()` (диспетчер по action+вводу) → `play_seq()` (двигает curr_seq, ставит кадр, применяет seq-dx/dy) → `frame_table[frame]` даёт image+собственные dx/dy → позиция и спрайт. У нас это ложится на `sprite_frame`+`sprite_move` (НЕ `sprite_anim`/`sprite_moveto` — см. PORT_PLAN §6: авторские таблицы, не автопрогрессия). ### 1.4 Управление — `control_kid()`/`read_user_control()` (seg006.c) Читает ввод (у нас — held-state `kbd_raw`, уже готово, §2 PORT_PLAN) и по `Char.action` выбирает последовательность (`seqtbl_offset_char(seq_id)`). Логика «что можно из какого состояния» — ядро ощущения PoP. ### 1.5 Взаимодействие с картой — collision (seg006.c) `check_on_floor()`/`start_fall()` — пол под ногами / падение в яму; `in_wall()` — упор в стену (сдвиг наружу); `check_grab()`/ `can_grab_front_above()` — зацеп за уступ; `fell_out()` — вывалиться из комнаты; `check_spiked()`/loose — ловушки; `fall_accel()`/`fall_speed()` — ускорение падения. Всё читает ТИП тайла (`get_tile`) — у нас это уже разобранные `fg[]/bg[]` (level.h/room1_data.h). --- ## 2. Спрайты Kid (219 шт, 16 цветов, 177 КБ) - 219 PNG (`data/KID`), 16-цветные (палитра `res400.pal`, 16×RGB как env/ wall), макс кадр **53×35** — влезает в лимит движка 64×64. 177 КБ в 8bpp. - `frame_table_kid` отображает кадр→`image` (индекс спрайта). Число РАЗЛИЧНЫХ image — уточнить (≤219); паковать те, что реально используются платформинг-последовательностями Фазы 1 (не все 219 — бой/катсцены отдельно). - **Палитра:** Kid 16 цветов → слоты Sprinter `0x70-0x7F` (env 0x50, wall 0x60 уже заняты; Kid не пересекается). Пиксель i: 0→0xFF, i→0x70+i. Тот же пайплайн, что `pop_pack_bg.py`. - **Атлас:** прямая адресация по номеру image (как фон): `kid[img>>5]`, idx `img&31`; ~7 EMM-страниц (или SHIFT=4). Свой пакер `pop_pack_kid.py` (переиспользовать код `pop_pack_bg.py`). ### 2.1 РЕШЕНИЕ ДО СТАРТА: per-frame offset vs padding Кадры Kid — РАЗНОГО размера, а `sprite_t` рисует от угла фикс. w/h. Два пути (см. PORT_PLAN §6.1, `memory/png_strip_padding_tradeoff`): - **Padding** (bottom-center) — просто, но 219×53×35 ≈ 406 КБ (раздув ×2.3). - **Per-frame offset** — хранить XCO/YCO кадра (у оригинала он и есть, `APPLEII_RESOURCE_FORMAT §2.2`), рисовать `blit(x+xco, y+yco)`; паддинг не нужен, память по факту (177 КБ). Требует лёгкого расширения хранения (offset рядом с кадром) ИЛИ ручного смещения в коде рендера Kid. **Рекомендация:** per-frame offset — оригинал так и делает (frame_table dx/dy + image XCO/YCO), даёт точное позиционирование И экономию. Хранить xco/yco в нашей копии frame_table (добавить 2 байта/кадр — ~360 Б). Не тянуть расширение `sprite.h` — рисовать Kid прямым `gfx_blit(x+xco, y+yco, img)` (как фон), НЕ через retained `sprite_t`, раз позиция и кадр всё равно задаются вручную каждый тик. --- ## 3. Данные для порта (объём) - `frame_table_kid` → C-массив ~180×(5+2 offset) ≈ 1.3 КБ (const, ROM). - `seqtbl` (нужные последовательности) → C-массив байт. Весь seqtbl ~1-2 КБ; для Фазы 1 можно взять только платформинг-последовательности (вырезать бой/гардов 55-92) — оценить после разметки. JMP-адреса пересчитать под свою базу. - Спрайты — атласы (EMM, не W2). --- ## 4. Фазы работы (по твоему списку, порядок по зависимостям) **Фаза K0 — конвейер спрайтов + отрисовка одного кадра** - `pop_pack_kid.py`: 219 (или подмножество) → `kid*.atl` + `kid.pal` (слоты 0x70), таблица кадр→image + xco/yco. - Отрисовать Kid ОДНИМ кадром (stand) в roomtest поверх фона на верном тайле — проверить палитру/позицию/прозрачность на MAME. - Артефакт-цель: Kid стоит на уступе комнаты 1 как в `1.1-2.png`. **Фаза K1 — движок анимации (play_seq + frame_table)** - Портировать `play_seq()` (интерпретатор) + `frame_table_kid` + минимальный `seqtbl` (stand/run/turn). - Прогнать несколько последовательностей вручную (stand→run→stop) — проверить, что кадры и смещения совпадают с оригиналом (сверять с SDLPoP/скриншотами, тайминг 50 Гц). **Фаза K2 — управление на месте + ходьба (твои а, б)** - `control_kid` подмножество: stand (2), run (1/84/13), turn (5/6), standing_jump (3), crouch (50/49), safe_step (29-44 — аккуратный шаг). - Held-state через `kbd_raw` (готово). **Фаза K3 — коллизия с картой (твой п.3)** - `check_on_floor`/`start_fall` — падение в ямы (тип тайла под ногами из `fg[]`); `in_wall`/стоп у стены; `fell_out` (край экрана — пока без перехода комнат). - Падения/приземления (seq 7/17/19/20) + `fall_accel/fall_speed`. **Фаза K4 — прыжки и повисание (твои а-прыжок, в)** - run_jump (4), jump_up (28/14), grab (8/16/24), climb_up (10)/down (68), hang (25/6), release (11/23). Это самый «PoP-овый» кусок — сверять дистанции/тайминг с оригиналом (не на глаз). **Фаза K5 — прочее (твой г)** - drink (78), level_door (70), crouch_hop (79), spiked/loose/chomped (ловушки, если тайлы есть в комнате), death (71). Бой (меч, seq 55-92, стражники — seg005) — ВНЕ этого плана (отдельная фаза полного приложения, PORT_PLAN §7 Фаза 3). --- ## 5. Риски/решения ДО кода (правило defer_unexplained_quirks) 1. **Per-frame offset** (§2.1) — решить до K0 (влияет на формат данных). Рекомендация: xco/yco в frame_table, прямой blit. 2. **seqtbl rebasing** — JMP-адреса абсолютные (SEQTBL_BASE 0x196E); при порте пересчитать в оффсеты своего массива. Проверить на 1-2 seq. 3. **Тайминг** — оригинал (DOS) фиксированный тик; наш 50 Гц. Если логическая частота кадров иная — пересчёт dx/dy (PORT_PLAN §8.4). Сверять дистанцию бега/прыжка с эталоном. 4. **Число реально нужных кадров/последовательностей** для Фазы 1 — разметить (вырезать бой/катсцены/гардов), чтобы не тянуть все 219 спрайта и весь seqtbl. 5. **Копирайт графики Kid** — подтверждено решение пользователя (§вводная). --- ## 6. Что переиспользуем (готово) - Фон комнаты (`pop_bg.c`) — Kid рисуется ПОВЕРХ (сейчас — прямым blit; heal против фона — когда/если понадобится через RAM-копию, фон её уже заполняет, `GFX_BANK_TRANSPARENT`). - `kbd_raw` held-state (§2 PORT_PLAN) — готов и проверен. - Пакер спрайтов/палитра (`pop_pack_bg.py`) — шаблон для `pop_pack_kid.py`. - Разобранная карта комнаты (`fg[]/bg[]`, level.h) — для коллизий. - `gfx_blit`/`gfx_w0_map` из W0-атласа — проверенный путь (bgtest/roomtest).