Шаги 1-2 из плана стражей:
1. Спрайты (chtab_5_guard, база 750). Новый упаковщик pop_pack_guard.py:
data/GUARD/res751..784 -> GUARD\g0..g4.atl (5 EMM-страниц, адресация
id>>3 / id&7, как у Kid). Палитра берётся НЕ из PNG, а из res10.bin
(guard_palettes: 7 палитр по 16 цветов, 6-бит) по level.guards_color —
на уровне 1 у обоих стражей color = 2; группа слотов 0x90..0x9F
добавлена в общий kid.pal.
2. Таблица кадров стража у оригинала СВОЯ (frame_tbl_guard, seg006:372,
41 запись) и индексируется как frame + add_frame - 149, где add_frame
= 70 для кадров 102..106. Она дописана в kid_data.bin (3515 -> 3720 Б,
смещение в KID_BIN_GFRAMES_OFF); pop_kid получил pop_kid_data_frame()
— чтение кадра из ЛЮБОЙ таблицы страницы данных.
3. Появление: pop_level_guard() читает guards_tile/dir/color/skill из
уровня, pop_guard_enter() ставит стража по enter_guard (seg002:0112) +
pos_guards (seg003): row из тайла, y = y_land[row+1], x из колонки,
charid = guard, sword сложен, alive = -1, HP = 3. Отрисовка
pop_guard_draw() — та же математика, что kid_draw (load_frame_to_obj +
calc_screen_x_coord), но атлас стража и своя таблица кадров; heal по
странице дабл-буфера, как у Kid.
Интерпретатора последовательностей у стража ПОКА НЕТ: кадр ставится
напрямую (166 = frame_166_stand_inactive, что и даёт seq_77 при входе в
комнату). play_seq для произвольного персонажа + ИИ — следующая фаза.
Проверено в MAME: в комнате 3 страж появляется на своём месте (ряд 1,
кол 7) и рисуется; цвета совпадают с эталонным рендером спрайта в
палитре color=2.
ВНИМАНИЕ по памяти: куча W2 просела до 805 Б (было 2349). Перед ИИ
стражей нужен шаг 5 плана (данные: room_modif 720 Б, dl1/dl2 512 Б,
_kbdraw_down 512 Б) либо вынос кода отрисовки стража в резидент W3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Три фикса, найденные разбором в MAME.
1. Просвечивание Кида сквозь торец кнопки (подъём/спуск у кромки).
draw_tile_bottom (seg008:570) рисует нижнюю грань блиттером
blitters_0_no_transp: пиксель индекса 0 заливается ЧЁРНЫМ, а не
пропускается. У граней кнопок (env 96/149) и нижних кадров loose (73/74)
последняя строка целиком нулевая — наш упаковщик переводил 0 в 0xFF для
ВСЕХ спрайтов, и она становилась дырой, сквозь которую был виден Kid за
тайлом (на запечённом фоне не видно — там и так чёрное).
pop_pack_bg.py: NO_TRANSP_ENV_IDS пакуется с сохранением индекса 0 как
ЦВЕТА (0x50 = чёрный в палитре env). Ноль тактов в рантайме.
2. Разворот в проёме опущенной решётки выкидывал Кида на другую её сторону.
check_bumped проверял столкновение УРОВНЕМ («край зашёл за грань»), поэтому
разворот на месте внутри тайла-стены менял, какую грань мы меряем, и это
читалось как новое столкновение → Kid.x = char_dx_forward(d) швырял его
сквозь решётку. Оригинал (check_collisions + get_row_collision_data,
seg004:0004) считает флаги перекрытия от ГАБАРИТА персонажа, БЕЗ
направления, и зовёт bumped() только на переходе 0 -> 1 (вправо смотрит на
0x0F, влево на 0xF0). Порт этого edge-триггера: флаги от габарита + память
прошлого кадра по колонке, сброс при смене комнаты (в оригинале это
несовпадение prev_coll_room/curr_row_coll_room). Проверено: теперь
разворот даёт уход в комнату 8 (leave_room, взгляд влево, char_x_left<=54)
— то есть туда, где Kid физически и стоит.
3. Снимок шва расширен до ДВУХ колонок с каждой стороны (lcol_fg[6]/
rcol_fg[6]: col9+col8 и col0+col1). Kid, стоящий В шве (curr_col=-1),
смотрит вперёд на колонку -2, где раньше была мнимая стена (get_tile ->
TILE_WALL). Оригинал резолвит любую колонку через find_room_of_tile
(seg006:005D).
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>
Loose-пол room1(2,6) при падении даёт кусок, который в оригинале улетает в
комнату снизу (links.down) и приземляется на первый floor, превращая его в
debris (порт move_loose/loose_land seg007). Для room1(2,6): room2(0,6)=empty
пролёт → room2(1,6)=floor приземление → debris.
Минимальная реализация (без анимации полёта куска, только персистентный
результат — мини-версия P0 из docs/gates_spikes_plan.md):
- pop_map: сигнал pop_loose_fell (tilepos+1 упавшего loose).
- pop_level: pop_room_col_landing(room,col) — ряд первого floor сверху вниз
(пустые ряды пролетаются), иначе -1.
- roomtest: при pop_loose_fell вычисляет debris-приземление в комнате снизу →
персистентный override (dbr_room/dbr_pos, живёт между переходами) →
enter_room применяет поверх загруженных тайлов (floor→debris 0x0E).
Проверено в MAME: провал loose room1(2,6) → debris в room2(1,6), сохраняется
при повторных входах.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Переходы в соседние комнаты по горизонтали (порт SDLPoP leave_room/
goto_other_room/find_room_of_tile), проверено в MAME (room2↔room3↔room9,
room1↔room5 и т.д.):
- pop_level: pop_room_load теперь извлекает и rightcol (col0 правого соседа),
как leftcol — для коллизии/рендера правого шва.
- pop_map: pop_map_set_edges(l,r,u,d, lcol_fg, rcol_fg) — связи + кромки швов.
get_tile(col=-1)=lcol (col9 левого), get_tile(col=10)=rcol (col0 правого),
с гейтом по связи (нет соседа → стена) — порт find_room_of_tile. check_leave
(порт leave_room): передний край char_x_left<=54 / char_x_right>=201 (и
обратные <=57 / >=198) → x∓140, сигнал pop_leave_dir (1=left,2=right).
- roomtest: rcol-массивы; enter_room задаёт edges; обработчик pop_leave_dir
переключает комнату по pop_room_link.
- Фикс двойного перехода: без коллизии ЛЕВОГО шва Kid, войдя справа в соседа
(curr_col=-1), стоял «в стене» → in_wall выбрасывал его на второй переход.
Левый шов (get_tile(-1)=lcol) это чинит.
docs/gates_spikes_plan.md — ПОДРОБНЫЙ самодостаточный план на след. сессию:
интерактивные объекты (RAISE/DROP-кнопки, гейты, пики) + HP/смерть. Контекст
текущего roomtest, форматы уровня (LINKLOC/LINKMAP @1440/1696), данные room6,
декод связи кнопка(0,2)→гейт room8(0,9), триггер пик (check_spike_below),
точные ссылки SDLPoP, фазы P0(персистентное per-room состояние+trob)/
S(пики+HP/смерть)/B(кнопки+гейты).
NB: L3-вверх (climb-up в комнату сверху) ещё не сделан.
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>