Раскладка (по подтверждённой пробником модели, tests/w3bankgfx):
- roomtest переведён на MEMORY=huge: та же small-раскладка резидента
(CODE в W1, DATA за ним) плюс банки кода в W3;
- guards.c собирается как --bank 1=guards.c — там будет ИИ и боёвка;
- СОСТОЯНИЕ стража живёт в W1/W2 (pop_guard.c): писучие статики
__banked-модуля линкуются в страницу банка и снаружи не читаются, так
что банк — только код;
- поля pop_char_t повторяют char_type оригинала (types.h:302), чтобы порт
seg005/seg006 ложился один в один.
Режим читов (порт cheats_enabled, seg000:111): глобальный флаг pop_cheats,
на время разработки включается в main. Реализован один чит — K (kill
guard, seg000:786): скелета не берёт, живому стражу ставит
guardhp_delta = -guardhp_curr и alive = 0. Обработка по фронту нажатия.
Остальные читы оригинала не портированы.
Проверено в MAME: приложение в huge-раскладке стартует, комната рисуется,
Kid бегает — то есть банкованный pop_guard_tick() зовётся каждый кадр
через трамплин и корректно возвращается; K не роняет приложение (стража
в комнате пока нет). Банк занят на 6 Б из 16384.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Сведены дубли, разъехавшиеся по модулям:
- x_bump[20] был в pop_kid (uint8_t!) и в pop_map (int16_t) — теперь одна
таблица int16_t;
- y_land[5] — две копии;
- y_to_row_mod4 — в pop_bg и pop_map;
- 32-битный LCG оригинала (prandom) — в pop_bg и pop_trob; функция теперь
одна, а СИДЫ остались раздельными (у раскладки кладки и у фаз факелов
свои последовательности, смешивать нельзя — иначе поедет рисунок стен).
Экономия по коду скромная (_CODE 24462 -> 24421, W3 11643 -> 11632: часть
выигрыша съели межмодульные вызовы). Главное здесь другое: pop_geom лежит
в W1/W2 и не трогает графику, то есть это тот самый «чистый» слой, который
сможет звать __banked-код стражей (docs/layout_plan_v2.md §4, §5.2).
Проверено в MAME: комната 1 после пересборки отрисована ПОБАЙТОВО так же,
как до правки (0 различающихся пикселей в области комнаты) — значит
последовательности PRNG и геометрия не поехали; Kid бегает.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Порт архитектуры оригинала: логика анимации тайлов НИЧЕГО не рисует, она
ставит флаг (set_redraw_full/set_wipe/redraw_20h/redraw_21h, seg007), а
отрисовка идёт отдельным проходом redraw_needed (seg008:0178). У нас
появился pop_redraw.c/.h: pop_set_redraw(tilepos, вид, страницы) +
pop_set_redraw_above(col, ...) + pop_redraw_needed(), который зовёт
главный цикл в слое фона (до kid_draw).
Отличие от оригинала (наша платформа): счётчик пометки — это ЧИСЛО СТРАНИЦ
дабл-буфера (обычно 2), а вид перерисовки хранится явно (heal+поверх или
запечь фон), потому что у нас у каждой страницы своя ОЗУ-копия фона. В
оригинале вид кодируется тем, в какой из таблиц redraw_frames_* стоит флаг.
pop_trob переведён на пометки: пики, кнопки, дверь уровня. Его самодельные
массивы spike_rest/button_rest/ldoor_rest/rest_pending удалены — их роль
теперь у счётчика страниц в pop_redraw. Прямыми вызовами pop_bg осталось
только пламя факела и пузырёк зелья: это не тайловая перерисовка, а
покадровый оверлей; из-за них pop_process_trobs остаётся единственной
функцией модуля, которую нельзя звать из банка.
Зачем: из __banked-кода резидентная страница W3 недостижима транзитивно
(docs/layout_plan_v2.md §2 R2), поэтому логика, которую будут звать стражи,
не должна вызывать pop_bg.
Проверено в MAME: комната 6 — Kid на кнопке-открывалке, решётка в шве
поднимается; сошёл с кнопки — закрывается; упал в шахту на пики — пики
выдвинулись и отрисованы (кадр 177).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
--w3 берёт ОДИН файл на флаг, поэтому запись "--w3 pop_trob.c pop_map.c"
означала "W3 = pop_trob", а pop_map всё это время ехал в W1/W2 (в
build-каталоге лежал осиротевший w3_pop_map.rel). Теперь список явный.
pop_trob переведён в W1/W2: в W3 должно оставаться только то, что банк
никогда не позовёт (из __banked резидентная страница W3 не видна ни
напрямую, ни транзитивно — docs/layout_plan_v2.md §2 R2). pop_trob же
стражам понадобится: в оригинале они тоже давят кнопки.
Замер: W3 14376 -> 11643 (свободно 2008 -> 4741 Б), W1/W2 _CODE
21634 -> 24367 (куча 5315 -> 2582 Б). Освободившееся место в W3 —
задел под шаг 3 (loose/потолок из pop_map, чтобы pop_map стал
bank-safe).
Проверено в MAME скриптом: комната рисуется, Kid бежит и тормозит
(кадры 15 -> 10 -> 15), факелы анимируются (152 различающихся пикселя
между соседними кадрами) — то есть pop_trob работает из нового окна.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kid_frames (241*5) и kid_seqtbl (2310) занимали 3.5 КБ в _CODE окна W1/W2
— самого дефицитного ресурса. Теперь они лежат в kid_data.bin (отдельная
EMM-страница), которая маппится в W0 ровно на время play_seq — один
map/unmap за тик, в фазе тика, без конфликта с атласом в W0.
Ключ к переносу — порт load_frame/cur_frame (seg006): оригинал раз за тик
копирует кадр в структуру, и вся коллизия/отрисовка читает ЕЁ, а не
таблицу. У нас так же: kid_cur_dx/kid_cur_flags (их дёргают несколько раз
за кадр из pop_map) и kid_draw читают cur_frame — 5 байт в _DATA.
kid_seq_off (230 Б) оставлен резидентным: его читает kid_set_seq из
pop_ctrl/pop_map, вне страницы.
pop_extract_kid_data.py теперь пишет и kid_data.bin, и урезанный
kid_data.h (тип kframe, размеры, смещения в бинаре, kid_seq_off).
Замер: _CODE 24881 -> 21634 (-3247 Б), куча W2 2076 -> 5315 Б, W3 без
изменений. Проверено в MAME скриптом: стойка -> бег (кадр 8) -> стоп,
перемещение и коллизия у кромки работают, спрайт рисуется.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Три расхождения с оригиналом на сцене подъёма меча (комната 15):
1. Лежащий меч не блестел. Порт animate_sword (seg007:0425) +
start_anim_sword (seg007:087C): при входе в комнату тайлу даётся
случайная фаза (prandom & 0x1F), каждый кадр счётчик вниз, на 0 —
новый период 0x28..0x67. Кадр блеска рисуется РОВНО на modif==1
((modif==1)+10 в draw_tile), т.е. одиночная вспышка раз в 40..103
тика. Перерисовка тайла — по смене видимого кадра, схемой кнопки
(текущая страница сразу, вторая через rest-цикл).
2. В кадрах «нашёл меч» клинка не было видно. Порт
add_sword_to_objtable (seg006:1798): клинок — ОТДЕЛЬНЫЙ спрайт
chtab_0 поверх Kid со смещением из sword_tbl. Смещения в ЭКРАННЫХ
пикселях (оригинал применяет их после calc_screen_x_coord), поэтому
берём уже масштабированный obj_x. Из таблицы взяты только строки
35..42 (кадры 229..236) — бой не портирован. Новый атлас sword.atl
(8 спрайтов, 1.6 КБ) + палитра chtab_0 в слотах 0x80..0x8F.
Прямоугольник heal расширяется объединением с клинком, иначе он
оставлял след за габаритом Kid.
3. Не было вспышки фона. do_flash = set_bg_attr(0, color) — оригинал
подменяет НУЛЕВУЮ запись палитры, вспыхивает всё чёрное поле экрана;
proc_get_object ставит flash_color=14, flash_time=8. У нас gfx_pal_set
на обе страницы. ВАЖНО: вызов gfx_pal_set(0,0,0,0,0) пятью литералами
ломает SDCC 4.5 (эмитит невалидный `ld hl, a`) — обёрнуто в функцию с
параметрами.
Попутно: TROBS_MAX 24 -> 30 (как в оригинале) + при входе в комнату из
списка выбрасываются «декоративные» trob ДРУГИХ комнат (факелы/зелья/
меч анимируются только в отрисованной). Без этого отладочный обход всех
24 комнат забивал список, и новые анимации молча не заводились.
Проверено в MAME: блеск (брейк на редрое тайла срабатывает), клинок в
руке виден, фон вспыхивает жёлтым.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Графика chtab_1 (flame/sword/potion) — новый атлас pop_pot.atl:
- склянка зелья берётся из chtab_1 (seg008 draw_tile_fore: id 12 малая /
13 большая при типах 2..4), а НЕ chtab_6 id 12 — res212 в VDUNGEON нет,
каскад падал на VPALACE и рисовал кусок дворцовой декорации («мусорный
объект» в комнате 5);
- пакуем chtab_1 целиком (id 1..23): пламя, склянки, кадры пузырька;
- MONO-блит (method_3_blit_mono, seg009:3040) красит спрайт цветом из
ОБЩЕЙ 16-цветной палитры экрана — она добавлена в 0x30..0x3F, палитра
chtab_1 в 0x40..0x4F; пузырёк пакуется силуэтом цвета 12 (красный),
его маска — цвета 0;
- в env-атлас добавлено основание факела (env 146, seg008:489 — рисует
правый сосед); в статический render_room оно не попадало.
Анимация (seg007 animate_torch/animate_potion + seg000:0B12
anim_tile_modif): при входе в комнату факелам и зельям задаётся случайная
стартовая фаза и заводится trob; факел — get_torch_frame, пламя в ячейке
ПРАВОГО соседа (xh = draw_xh+1, y = draw_main_y−40); зелье —
bubble_next_frame по младшим 3 битам модификатора (старшие 5 = тип, при
загрузке уровня modif <<= 3, seg009). Пламя и пузырёк не запекаются в
фон: heal своей области + кадр поверх. Скорость пламени /2 — наш
логический кадр короче игрового тика оригинала.
Скорость отрисовки (профиль в MAME, кадр = 430 080 тактов):
- замер показал, что цена блита почти НЕ зависит от размера — 13 288
тактов на спрайт 32×3 против 4 617 у линейного спрайтового ядра без
клипа; платим за проход gfx_blit → gfx_blit_part → _gfx_blit_full;
- в libbgi добавлен gfx_blit_noclip() — блит без клипа в ТЕКУЩЕМ банке
(putsprite не годится: навязывает GFX_BANK_SPRITE, фону нужен
TRANSPARENT ради ОЗУ-копии); pop_bg.blit_b уходит на него, когда
спрайт целиком на экране и не нужен g_clip_top → ~2.9× на блит;
- W3-скобку ставит САМА libbgi: из модуля с --w3 её вызывать нельзя —
после _bgi_begin окно W3 занято видеобанком и код вызывающего исчезает
(проявлялось белым экраном);
- редрой шва больше не перерисовывает полосу потолка (bar начинается с
POP_YOFF+3) — это удваивало стоимость блока при анимации решётки;
- docs/size_optimization_plan.md §7–§8: замеры, сделанное и запас
(батчинг W3-скобки, решётка одним спрайтом, лишние блиты).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Фича — плита-ПОТОЛОК (loose ряда 2 комнаты сверху, комн.5 (2,5) над комн.6):
- check_press (seg006:1683) портирован целиком: кадры виса 87..99 и подъёма
135..140 «нажимают» тайл, за который Kid держится; кадр 79 — удар снизу
(с проверкой action, как в оригинале); стояние — тайл под ним;
- ряд −1 в pop_map.get_tile = ряд 2 комнаты сверху (порт find_room_of_tile),
состояние тряски/падения — pop_ceil_modif[10]; do_knock(-1) трясёт потолок;
- падение: mob переведён на координаты оригинала (y_loose_land/y_something),
спавнится в ряду −1, по move_loose находит пол → loose_land: debris +
do_knock; heal коридора — по прошлой позиции ДЛЯ КАЖДОЙ страницы;
- урон: check_loose_fall_on_kid + fell_on_your_head (seq_52/seq_22, −1 HP)
и «брызги» draw_hurt_splash (kid image 218);
- колодец наверх: leave_room «вверх» проверяется ПЕРВЫМ (иначе y≈248 при
подтягивании ловил check_leave_below и ронял Kid из уровня);
- персистентность: общий tile-override (щебень внизу, пустота в комнате
сверху) вместо debris-only.
Окклюзия — приведена к оригиналу (найдено трассировкой собранного SDLPoP):
- draw_tile_right/anim_right/draw_loose кладут спрайты в add_backtable
НАПРЯМУЮ, минуя ptr_add_table → правая грань соседа («шахматка» столба 93),
blueline и грани loose/пик соседа ВСЕГДА под персонажем. draw_other_overlay
поверх Kid даёт только: 42 при левом соседе-поле, base, свои пики, bottom
(overlay_mid_tile);
- порядок объектов: тайл Kid (set_objtile_at_char) и порядок обхода
(ряды 2→0, колонки 0→9), внутри тайла — сортировка по y (sort_curr_objs);
то же правило для падающей плиты (pop_loose_mob_draw_over);
- fore-слой рисуется ПОСЛЕ оверлеев (foretable после midtable), иначе тёмный
скос floor_left_overlay ложился поверх колонны;
- fore_tile больше не рисует bottom_id (в оригинале он в backtable) — минус
2..4 блита за кадр;
- полоса потолка поверх Kid = draw_tile_bottom(1)+draw_loose(1)+draw_tile_fore
(arg_0=1 добавляет в foretable), остальное — под ним;
- следы оверлея восстанавливаются на ОБЕИХ страницах (pop_fore_heal).
Прочее:
- control(): при action bumped/in_freefall управление игнорируется целиком
(seg005) — иначе прерванный присед терял dy(1)+dy(1) из medland и Kid
навсегда оставался на 2px выше пола;
- check_bumped: стена ищется по колонке переднего края (а не curr_col) +
гард разворота — Kid больше не проходит сквозь опущенную решётку шва;
- pop_floor_bake для смены floor→debris (bake_empty стирал верх стены ряда
ниже — чёрный прямоугольник);
- в pop_room_redraw_seam_left восстанавливается полоса потолка над решёткой.
Проверено в MAME (bridge); эталон — собранный SDLPoP из applications/PoP/SDLPoP.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Порт Prince of Persia (applications/PoP/roomtest): развитие уровня,
объекты (loose-полы/обломки), переходы между комнатами, фон-упаковка;
планы (room_model/size_optimization), bug_list, pop_trob.
Арт third_party/16x16-RPG-characters.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Отказ от room1_data.h: уровень читается из сырого blueprnt DAT 1.0
(applications/PoP/SDLPoP/data/LEVELS/res2001.bin, формат — Table 6 POP-DAT).
- pop_level.c/.h: уровень грузится ОДИН раз в отдельную EMM-страницу (данные
с offset 0x100, ISR-стаб в первых байтах — страница безопасна для W0-маппинга,
как атласы). pop_room_load(room) извлекает комнату в массивы приложения (W2):
fg[30] (тайл-код = байт & 0x1F, верхние биты-модификаторы пока отброшены,
как делал render_room.py), bg[30] (raw) + срезы соседей для кромок — правый
столбец left-комнаты (leftcol), верхний ряд down-комнаты (belowrow).
pop_room_link() — связи для будущих переходов (L2/L3).
- Рабочая копия ТЕКУЩЕЙ комнаты — в обычной памяти (W2, мутабельная: loose→
empty); страница уровня маппится в W0 только на время извлечения.
- Проверено в MAME: комната 1 рисуется идентично. Данные сверены байт-в-байт
(fg&0x1F, bg raw, leftcol из room5 совпали); belowrow теперь из реального
room2 (links.down=2), а не из прежнего частично-выдуманного хардкода.
- Память (small): DATA до 0xA260, стек от 0xBFFF — запас ~7.5КБ; EMM-страница
уровня вне W1/W2.
NB: res2001.bin берётся из клона SDLPoP (data/ в .gitignore) — как и генерация
атласов через pop_pack_bg.py; в наш репозиторий сырой уровень не тащим.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>