Files
Sprinter-SDCC/applications/PoP/docs/KID_PLAN.md
T
snark13 cd8d566d82 applications/PoP: порт Prince of Persia — PoC (roomtest) + пайплайн
Порт PoP на Sprinter.  Текущий PoC — applications/PoP/roomtest/:
комната 1 (фон-композиция тайлов) + Kid с управлением на raw-клавиатуре
и коллизией с картой.

- roomtest — pop_bg (фон), pop_kid (спрайты Kid, column-major флип,
  seqtbl-анимация), pop_ctrl (порт control() PoP на held-state
  kbd_raw), pop_map (коллизия seg004/005: бег/стоп у стены,
  падение/приземление, отскок seq_47, вертикальный прыжок K4.1).
  MEMORY=small (DATA сразу за CODE, ~23КБ кода не лезет в huge).
- toolchain (PoP) — pop_pack_kid/pop_pack_bg/render_room/extract —
  распаковка res-графики MSDOS в атласы + композиция комнат.
- toolchain/ (корень) — make_hdd.sh (быстрый HDD-тест вместо FDD),
  png_strip.py / room_compose.py (ассет-пайплайн).
- docs — PORT_PLAN, KID_PLAN, форматы ресурсов (Apple II / MSDOS / DAT).
- bgtest/coltest/poc — ранние PoC (фон, коллизия, первый прототип).

.gitignore: build-артефакты applications/*/*/*; исключены внешние
референс-репозитории (SDLPoP/mininim/PR/Apple-II — свои git-клоны) и
оригинальные game-данные MSDOS/ (копирайт, только для реверса форматов).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:51:08 +03:00

200 lines
13 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.
# 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).