Commit Graph

17 Commits

Author SHA1 Message Date
snark13 6673279cef PoP: скелет уровня 3, цвета стражей, окклюзия соперника; кэш соседних комнат
Скелет (L3-SKEL, ассеты + механика):
- pop_pack_guard.py получил параметр набора (GUARD/SKEL): атлас скелета
  poc/res/skel/g0..g3.atl (28 кадров), палитра — из его res750.pal (на ур. 3
  curr_guard_color = 0, оригинал палитру не подменяет);
- pop_guard_load выбирает набор по tbl_guard_type и перезагружается ПРИ СМЕНЕ
  УРОВНЯ (load_lev_spr, seg000:1092) — без этого скелет рисовался атласом
  стража и был невидим;
- load_frame: charid_4_skeleton идёт по таблице стража (seg006:529), тень —
  только в кадрах 150..189.  Пока ветка была одна (charid_2_guard), скелет
  получал image из таблицы Кида (180 при 28 спрайтах) и не рисовался;
- check_skel (seg002:1042), ветка charid_4 в enter_guard, возрождение в
  комнате 3 при падении (seg002:252), autocontrol_skeleton;
- leveldoor_open (seg007:456) — новый флаг, сбрасывается стартом уровня.

Цвета стражей (BUG-GUARD-COLOR-1, закрыт):
- все 7 палитр res10.bin -> pop_guard_pal.h, заливка 16 слотов по
  guards_color комнаты перед отрисовкой (set_chtab_palette, seg003:257).
  Проверено в MAME: ур. 2 комн. 11 = цвет 1, комн. 7 = цвет 3, полоса HP
  меняется вместе со стражем.  Грабля: gfx_pal_load отдаёт указатель в BIOS,
  а тот читает только #4000-#BFFF — таблицу из банка копируем в стек.

Кэш соседних комнат (BUG-SWORD-GHOST-1, закрыт):
- pop_map кэширует fg соседей слева/справа ЦЕЛИКОМ и резолвит col -10..19.
  Было -2..11, дальше мнимая стена: луч видимости упирался в неё (страж
  после follow_guard в col 12), Кид прятал меч посреди боя и не мог достать
  обратно.  +48 байт W2.

Окклюзия соперника:
- pop_fore_over_char получил проход other_overlay_tile (порядок midtable,
  seg008:1B06) и расширение перебора объединённым прямоугольником
  «персонаж + клинок + брызги» — падающий скелет больше не рисуется поверх
  кладки и верхней грани пола;
- клип полем 192 строк (reset_obj_clip, seg006:0507) для спрайта, клинка
  (общий pop_sword_draw) и брызг — спрайт не залезает на полосу HP;
- ROOMNAV после смерти Кида делает честный pop_start_level: телепорт
  «оживлял» мёртвого мимо старта уровня, оставляя живого скелета рядом с
  вернувшейся кучей костей.

Ассеты чомпера (под L3-CHOMP): весь набор кадров в атласе явным списком
(101-105 низ, 111-113 верх, 106-110 фронт, 114-123 кровь mono-силуэтом) —
render_room анимированные тайлы пропускает, и в атласе не было ни одного.
Число EMM-страниц не изменилось.

Тесты: tests-host все 5 наборов зелёные, t_char вырос до 65 проверок
(резолв колонок за краем комнаты, возрождение скелета); в testkit добавлен
гард «код наехал на данные» (DATA_LOC).

Доски: TASKS.md разнесён на TASKS_OPEN/TASKS_CLOSED, закрытые баги с
разбором корней — в bug_closed.md; заведены DRAW-CHAR (отрисовка одна на всех
Char, как физика после GUARD-PHYS) и L3-COLOR (зелёная кладка уровня 3:
level_var_palettes = ресурс 20, есть в MSDOS/PRINCE.DAT).

В roomtest.c временно оставлен автостоп на падении соперника (отладка
падений скелета) — помечен ВРЕМЕННО.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 22:11:03 +03:00
Александр Петров 4737ec323c MEM-BANK5: pop_ctrl.c → банк 5; чит [/] подгонки Кида по X
Три правки едут вместе намеренно: n_banks и --bank обязаны меняться
атомарно, иначе промежуточный коммит — зависание (см. ниже).

MEM-BANK5.  CODE и DATA делят одно 32-КБ пространство W1+W2, поэтому
килобайт кода, уехавший в банк, — это килобайт, доступный данным.
Кандидат выбран не по размеру, а по частоте вызова: диспетчер управления
дёргается раз в кадр на персонажа и горячих банк→банк переходов не
создаёт (в отличие от pop_level, чей pop_level_tile зовётся из банка 2 на
КАЖДЫЙ тайл).

  _CODE   26 780 -> 24 662 Б   (−2 118)
  куча       180 -> 2 298 Б
  банк 5            2 211 / 16 384 (13.5 %)

Шина control_* (8 глобалов) переехала в pop_state.c.  Сегодня она уцелела
бы и в pop_ctrl.c — банки собираются без --bank-data, их писучие данные
остаются в общем _DATA, — но это флаг сборки, а не свойство кода, а шину
трогают уже три банка: 5 пишет с клавиатуры, 1 подаёт синтетический ввод
ИИ (autocontrol_*, seg002), 3 читает через pop_ctrl_shift_held.
Заодно pop_ctrl.c наконец включает собственный заголовок — раньше
объявления жили прямо в нём.

ГРАБЛИ, на которые наступили (стоили дольше самой задачи): n_banks в
roomtest.c захардкожен, и его надо править вместе с числом --bank.  С
n_banks=4 и пятым банком crt0 выделил четыре страницы, _bank_pages[5]
остался нулём, и первый же вызов pop_ctrl_init() через трамплин
отобразил в W3 страницу 0 и прыгнул на 0xC874 в мусор — исполнение
забрело в дисковый код DSS и осталось крутить чтение секторов.  Симптом:
загрузка ресурсов проходит целиком (open=45 — все атласы), комната и Кид
успевают нарисоваться из enter_room, а HP и номер комнаты уже нет, и
kid_tick не вызывается ни разу.  Ровно предупреждение из шапки
runtime/bank.s.  Сверку n_banks с реальным максимальным индексом банка
записал в docs/TODO.md (Auto-banking) — это должно быть ошибкой сборки.

DBG-CHEATS: [ (0x54) и ] (0x5B) двигают Кида на пиксель (seg000:1828),
по фронту нажатия, под pop_cheats.  Нужны потому, что мост MAME теряет
нажатия при быстрой отправке и подогнать Кида в позу скриптом нельзя —
на это упёрлись BUG-LOOSE-2 и BUG-GATE-PASS-1.

ROOMNAV больше не зовёт pop_trob_reset: reset обнуляет room_seen, то есть
чит ОТМАТЫВАЛ МИР (открытые/закрытые ворота, нажатые кнопки).  Навигация
обязана только телепортировать.

Проверено в MAME: старт уровня 1 рисуется полностью (комната, Кид, HP,
номер), бег вправо и падение на второй ряд отрабатывают, ] даёт x+1 и
[ даёт x−1 по одному нажатию, Shift+→ — осторожный шаг (x 131 -> 142,
колонка 4 -> 5) и при удержании 90 кадров не срывается в бег (x 142 ->
151), то есть pop_ctrl_shift_held работает через границу банк 3 -> банк 5.
Наборы под ucsim: geom 39, grab 53, phys 1723 — все зелёные.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:45:15 +03:00
Александр Петров cfc3602375 L2: падение с мечом, разбег-прыжок через 3 тайла, стартовое состояние ворот
BUG-FALL-SWORD-1 — start_fall (seg006:1044) был портирован не целиком:
не хватало трёх веток выбора последовательности и уборки меча в ножны.
Из-за seq_7 с set_fall(1,15) (дрейф 1 px/кадр) Кид с мечом уезжал примерно
на тайл вбок; в оригинале это seq_81_fightfall — падение строго вниз.
Сверено по логу SDLPoP: кадры 102..105 дают x = 155,157,159,160.

BUG-RJUMP-1 — run_jump (seg005:0AA8) не выравнивал Кида по кромке пола
перед толчком, а был заглушкой «полировка K3».  Суммарный dx seq_4 —
62 px при тайле 14, то есть провал ровно в три тайла берётся ТОЛЬКО с
кромки: без выравнивания Кид не перепрыгивал его никогда.  Порт —
pop_run_jump_align() в pop_map (беззнаковое сравнение оригинала = «сдвиг
не попал в [-8,-1]»).  На харнессе: было — толчок с x=165, кадр 44 в
колонке 3 (провал); стало — 5 кадров добега, толчок с x=149, кадр 44 даёт
x=87 col=1 row=1.  Остальные 8 сценариев не изменились.

BUG-GATEMOD-1 — load_alter_mod (seg008:198E) был портирован только для
зелий, поэтому ворота с bg=1 («Open» по спецификации DAT, табл. 8)
стартовали закрытыми.  Добавлены ветки gate (1 -> 188) и loose.  Ветка
wall намеренно НЕ портируется: связи стен наш pop_bg считает по типам
соседей в момент отрисовки.  На уровнях 1-3 таких ворот всего двое
(ур. 1 комн. 5 (0,9); ур. 2 комн. 13 (1,5)) — у остальных bg=2, а 2 и 0
ведут себя одинаково.

Убран временный трассировщик pop_dbg_trace/pop_dbg_draw; pop_dbg_trap()
оставлен как многоразовый инструмент.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 12:34:26 +03:00
Александр Петров e67117219f BUG-CTRL-FRAME-1: геометрия Кида считалась по кадру стража
cur_frame — один глобал на всех персонажей (как в оригинале), владелец —
тот, кто последним прошёл load_frame.  Последним в кадре тикает страж,
поэтому к моменту control() Кида там лежал кадр СТРАЖА, а через
kid_cur_dx/dy/flags по нему считается вся геометрия управления:
dx_weight -> determine_col -> distance_to_edge_weight -> get_edge_distance
-> выбор ветки в check_jump_up.

Оригинал зовёт load_fram_det_col() (seg006:0144) сразу после
loadkid/loadshad и ДО control() — play_kid_frame (seg000:1211) и
play_guard_frame (seg000:1248).  У нас этого не было.

Замерено брейкпоинтом на pop_jump_up_seq: Kid x=156 col=6 кадр 15
(dx=0 weight_x=3) при кадре стража image17 (dx=-1 weight_x=8) дал
curr_col=7 и distance=2 вместо 6 и 10 — то есть jump_up_plain (вернулось
A=28, пустой прыжок) вместо «шаг назад на x=160 + зацеп».  Предсказание
по кадру стража совпало с намеренным до единицы.

Отсюда же плавающее поведение: кадр стража меняется каждый тик, distance
Кида скакал через порог 6 — то прыжок, то попытка зацепа с неверной X.
И «голова Кида поверх плиты (1,6)» — не баг отрисовки, а следствие позы,
которой в оригинале в этом месте не бывает.

Фикс: pop_load_fram_det_col() (pop_kid.c) + вызовы в pop_ctrl_tick и
pop_guard_tick.  determine_col — только на ветке Кида: у нас он
существует лишь для него (pop_map работает с Kid, а не с Char).

Разбор с числами — bug_closed.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 12:55:43 +03:00
Александр Петров b5d2a81ee3 L1: фиксы прогона уровня 1 — уровень мутабелен, стражи, loose-плиты, порядок
Одиннадцать наблюдений первого прогона свелись к шести корням, четыре
наблюдения второго — ещё к четырём.  Разбор каждого — bug_closed.md.

Первая волна:
- BUG-LVLSTATE-1: уровень стал мутабельным (эталонная копия foretable для
  рестарта, pop_level_set_tile вместо таблицы оверрайдов);
- BUG-RESPAWN-1: рестарт = load_level, тайлы возвращаются из эталона;
- BUG-DEATH-1: смерть от меча доигрывается (порт control_kid, seg006:0CD1);
- BUG-GATE-ANIM-1: ворота в отрисованной комнате перерисовываются
  (POP_RD_GATE, порт draw_trob seg007:01E6);
- BUG-COLL-1: полный порт check_collisions/bumped (seg004) вместо поиска
  стены только в колонке переднего края;
- BUG-STANDUP-1: убран лишний guard в bumped_floor — вставание у стены
  роняло Кида сквозь пол.

Вторая волна:
- BUG-RESPAWN-2: рестарт возвращает и СТРАЖЕЙ (в оригинале play_level на
  каждой итерации делает load_level + pos_guards);
- BUG-LOOSE-2: падающий кусок привязан к своей комнате и долетает после
  ухода Кида (do_mobs крутит mobs[] независимо от drawn_room);
- BUG-DRAWORDER-1: порядок «Кид / страж» задаётся обходом тайлов
  (redraw_needed_tiles: ряды 2,1,0, колонки 0..9), а не ролью персонажа.

По BUG-DRAWORDER-1 понадобилось три захода, и два первых были неполны:
  1) сам порядок — но общее окно fore-клипа осталось стражьим, и Кид
     нарисовался поверх передних столбов (kid_fore_clip_restore);
  2) enter_guard брал curr_col из тайла, а leave_guard пишет туда
     get_tilepos(0,row) — у запомненного ТРУПА колонка была 0 при
     настоящей X.  Теперь колонка выводится из X, как в оригинале;
  3) ветка actions_1_run_jump в set_objtile_at_char оказалась не
     «упрощаемой»: в беге тайл берётся из нижнего ряда и ЛЕВОЙ колонки
     габарита, поэтому бегущий Кид уходит за объекты справа.  Считается
     для обоих персонажей — enter_guard ставит action=1 и стражу.

Проверено в MAME: зелья/меч/плиты переживают выход из комнаты и
восстанавливаются после смерти; кнопка room5 поднимает решётку; падение с
кнопки больше не роняет в комнату 6; убитый страж жив после respawn;
Кид проходит за телом стража.  make size-check — роста нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 22:35:41 +03:00
Александр Петров e86f254b87 L1-START + L1-EXIT: старт по данным уровня и выход через дверь уровня
L1-START.  Старт и оба рестарта (смерть, выпадение из уровня) сведены в
pop_start_level() — порт start_level + do_startpos + set_start_pos (seg003).
Комната/тайл/направление берутся из pop_level_start_*, направление
инвертируется (~start_dir), поза входа — из tbl_entry_pose: у уровня 1 это
падение внутрь (seq_7_fall) плюс нажатие кнопки room5(0,2), то самое, что
захлопывает решётку за спиной.  Жёсткие START_ROOM/COL/ROW убраны.
Проверено в MAME: старт даёт room 1, col 0, падение на row 1 — как по данным.

L1-EXIT.  Ветка двери уровня из up_pressed + go_up_leveldoor (seg005:0482/
0574): тайлы и геометрия — pop_leveldoor_enter() в pop_map, последовательность
seq_70 — в pop_ctrl.  Опкод 0xF1 END_LEVEL в play_seq инкрементит
pop_next_level (порт next_level), главный цикл по нему перезапускает уровень
— ровно та точка, куда levels_plan §2.2 подключит загрузку уровня 2.
Открытость двери проверяется по modifier >= 42 (ветка fix_exit_door), а не по
ванильному leveldoor_open: иначе можно войти в ещё ползущую створку.

Отдельно стоило разбора: go_up_leveldoor сначала писал Char.x/Char.direction,
и оба присваивания молча терялись — окно Char вокруг диспетчера возвращает
назад только curr_seq и sword (pop_savekid_state).  Направление оставалось
«вправо», а все DX в seq_70 отрицательные, поэтому Кид уходил ИЗ проёма
влево (поймано стоп-кадром).  Геометрию персонажа в этом порте меняет
pop_map, пишет в Kid — как pop_down_action и pop_jump_up_seq.

Известный остаток — BUG-DOOR-CLIP в bug_list.md: нет обрезки силуэта правым
косяком проёма (недопортирован obj_clip_right в clip_char); нужен вариант
колоночного блита с ограничением ширины.  ROOMNAV пока оставлен включённым —
он нужен, чтобы попадать в комнату 9 для этой работы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 22:47:47 +03:00
snark13 75a51fb1db PoP roomtest: выпивание зелья (уровень 1 — склянка здоровья)
Каркас предметов уже был (check_get_item/do_pickup/proc_get_object), пустой
оставалась только ветка зелий.  Порт seg005 get_item + seg006
proc_get_object:

- pop_get_item_action теперь отдаёт 3 = «пить» и делает do_pickup с ТИПОМ
  зелья, который лежит в старших битах модификатора тайла (modif >> 3);
- pop_ctrl на код 3 запускает seq_78_drink;
- эффекты: тип 1 (здоровье) — +1 HP через hitp_delta и красная вспышка,
  причём как в оригинале только если HP не полные; тип 5 («злое») — −1 HP.
  Типы 2/3/4/6 (жизнь, перо, переворот, открыть ворота) — свойства поздних
  уровней, портируем вместе с ними.

Вспышка фона получила цвет: меч даёт ярко-жёлтую (было), зелье — красную
(flash_color оригинала; двух значений достаточно, других в игре нет).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 22:09:36 +03:00
snark13 3983fa4513 PoP roomtest: HP-учёт, индикаторы HP, чит бессмертия; фикс кэша кадра
Боёвка (порт seg002/seg006):
- check_sword_hurting / check_hurting / check_sword_hurt / hurt_by_sword /
  take_hp через дельты; do_delta_hp сводит их раз в кадр;
- парирование (justblocked), refractimer после ранения стража;
- смерть по seq_71_dying — через неё же теперь работает чит K: страж
  действительно погибает, а не замирает на месте;
- парные окна Char/Opp: loadkid_and_opp / savekid_and_opp /
  saveshad_and_opp.

Индикаторы HP (порт draw_kid_hp / draw_guard_hp): Кид слева, страж справа.
Перерисовка ТОЛЬКО при изменении числа и тогда на ОБЕИХ страницах
дабл-буфера (счётчик hp_todo, иначе на второй странице осталось бы старое
значение и мерцало через кадр); pop_hp_invalidate при входе в комнату, где
фон перерисован целиком.

Чит I — бессмертие Кида (нашего изобретения, в оригинале его нет).
Перекрывает и путь «безоружного закалывают насмерть»: тот идёт мимо HP, и
без этого чит бесполезен ровно там, где нужен.

ДВА НАЙДЕННЫХ БАГА:
1. Полосу HP блитил row-major примитивом, а атласы Кида и стража хранятся
   COLUMN-major (ради бесплатного флипа) — марки выходили транспонированными.
   Теперь колоночный блит, стрелки как в оригинале.
2. Кэш кадра для ОТРИСОВКИ заполняли pop_savekid/pop_saveshad.  Любое окно
   Char БЕЗ play_seq — а это оба окна боёвки — записывало Киду кадр, который
   принадлежал СТРАЖУ, и kid_draw искал этот image в атласе Кида, рисуя
   произвольную позу.  Владельцем кэша стал load_frame: он один знает, чей
   кадр загружен (по Char.charid).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 18:43:39 +03:00
snark13 d014a3f577 PoP roomtest: ИИ стража — подход к Киду и боевые ветки диспетчера
Пункт 2 плана закрыт: страж не только замечает Кида, но и идёт к нему и
дерётся.  Порт по SDLPoP, диспетчер общий — ИИ выставляет те же control_*,
что и клавиатура игрока.

guards.c (банк 1), порт seg002:
- autocontrol_guard_active (737) + kid_in_sight (0A93) + kid_armed (0AC1)
  + kid_far (09CB);
- guard_advance / guard_block / guard_strike с таблицами вероятностей по
  12 градациям мастерства (seg002:26..38), бросок prob > prandom(255);
- move_2_backward / move_3_up / move_6_shift / move_down_back;
- таймеры justblocked / kid_sword_strike / guard_refrac убывают раз в кадр
  в autocontrol_opponent, как в оригинале.

pop_ctrl.c, порт seg005: control_with_sword (964), swordfight (0CDB),
sword_strike, parry, forward_with_sword, back_with_sword.  Ветвление у
Кида и у соперника разное — соперник блокирует только на кадре 152, Кид
ещё и по 153 (и тогда последовательность прокручивается сразу).

pop_guard.c: guard_skill из данных уровня (12 градаций, вне диапазона -> 3),
HP по get_guard_hp (extrastrength[skill] + tbl_guard_hp[уровень]),
собственный сид бросков pop_fight_seed — иначе перерисовка стены сбивала бы
решения стража.

char_opp_dist переехал из банка в pop_kid.c: он нужен по ОБЕ стороны
банковой границы — и ИИ, и диспетчеру боёвки.

Проверено в MAME (комната 3): страж проходит комнату, встаёт в дистанцию и
машет мечом, позы меняются.  Урона пока нет — HP-учёт и check_hurt
следующим шагом, без них бой не заканчивается.

Бюджет В БОЮ (175 кадров): 412 224 – 421 068 = 0.958–0.979 кадра.
В покое было 400 800.  Запас в худшем кадре ~9 000 — тесно, но в один
кадр укладываемся; оптимизация отложена сознательно.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:24:53 +03:00
snark13 79ea473910 PoP roomtest: страж замечает Кида и достаёт меч (ИИ, шаг 1)
Первый самостоятельный кусок ИИ стража (план, пункт 2).  В оригинале у
соперника нет своего диспетчера: autocontrol_* (seg002) выставляет те же
глобалы control_*, что и ввод игрока, а дальше исполняется общий control().
Инфраструктура под это встала прошлым коммитом, здесь — сама логика.

Порт:
- check_can_guard_see_kid (seg003:688) — луч видимости по ряду: стены и
  верхи дверей рвут его совсем, loose/чомпер/дыра/неподнятые ворота дают
  «вижу, но не пойду».  В guards.c (банк 1);
- Opp + loadshad_and_opp (seg006:841) и char_opp_dist (seg006:2135);
- autocontrol_guard_inactive (seg002:710) + move_* (seg002:0706..);
- ветки control(): control_guard_inactive (seg006:2123) и draw_sword
  (seg005:945) — соперник уходит сразу в seq_90 en garde;
- pop_guard_tick перестроен по play_guard_frame (seg000:1246): окно
  Char/Opp вокруг ИИ, диспетчера и play_seq.

По дороге:
- Kid.alive не выставлялся (= 0 = «мёртв» в семантике оригинала), из-за
  чего луч видимости не мог сработать в принципе — ставим -1 в kid_init;
- Kid.room не выставлялась вовсе; условие Kid.room == Guard.room всегда
  было ложным.  Ставим в enter_room (полная модель Kid.room != drawn_room
  у шва по-прежнему впереди);
- pop_tile_at — тайл текущей комнаты наружу из pop_map (луч видимости);
- control_x/y/shift открыты в шину: ИИ заполняет оси как есть, без
  flip_control_x (его «вперёд» уже в системе персонажа).

Проверено в MAME (комната 3, страж на tile 17): страж переходит из
стойки 166 в 171 stand_with_sword, sword=2, can_guard_see_kid=2; ввод
игрока не пострадал.  Клинок отдельным спрайтом пока не рисуется —
sword.atl содержит только кадры 229..237 (подъём меча Кидом), остальные
строки sword_tbl приедут с боёвкой.

Бюджет (225 кадров, комната 3): 384 168 – 384 636 тактов, 0.893–0.895
кадра, запас 45 364.  Прошлый замер 382 584 – 383 064 — шаг стоил ~1 570.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 11:09:12 +03:00
snark13 cf06896dbd PoP roomtest: окно Char вокруг control() + общая шина ввода — база под ИИ стража
Инфраструктура пункта 2.  В оригинале у соперника НЕТ своего диспетчера:
autocontrol_* (seg002) выставляет те же глобалы control_*, что и ввод
игрока, а дальше исполняется тот же control() (seg005:252).  Значит перед
портом ИИ надо было привести к этому обе половины:

- control_forward/backward/up/down/shift2 перестали быть static в
  pop_ctrl.c — это общая шина синтетического ввода, объявлена в pop_ctrl.h
  вместе с POP_CONTROL_*;
- control() переименован в pop_control() и работает с Char, а не с Kid;
- ввод игрока обёрнут в окно Char (loadkid/user_control/savekid), как в
  play_frame оригинала.

Ловушка по дороге (ввод отвалился целиком, Kid не двигался): макрос
seqtbl_offset_char вёл на kid_set_seq, который пишет прямо в Kid, а
следом savekid затирал Kid копией Char со старой curr_seq.  В оригинале
seqtbl_offset_char работает именно с Char — макрос переведён на
pop_char_set_seq.  kid_set_seq остался для вызовов ВНЕ окна (pop_map).

Добавлен pop_savekid_state (Kid = Char без кадра): control() кадр не
трогает, а cur_frame в этот момент принадлежит тому, кто последним крутил
play_seq.

Проверено в MAME: бег и упор в стену работают как прежде.
Бюджет (комната 3, 125 кадров): 382 584..383 064 против 380 292..381 282,
то есть +2 300 тактов на копии окна.  0.891 кадра, запас 46 936.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 10:32:15 +03:00
snark13 1c91c5f92a PoP roomtest: меч — отрисовка на полу, подъём по Shift, статус have_sword
Комната 15, тайл (2,2): меч не рисовался вовсе — в draw_tile не было
ветки tiles_22_sword (seg008 draw_tile_anim):

    add_midtable(chtab_1, (modifier == 1) + 10, draw_xh, 0, draw_main_y - 3, ...)

Спрайты уже лежали в нашем pot-атласе (chtab_1 пакуется целиком, id 1..23),
так что понадобился только вызов.  У нас предмет рисуется статикой в фоне,
а не в midtable: пока меч лежит, он не анимируется, а Kid и так поверх фона.

Подъём — порт цепочки оригинала:
- check_get_item/get_item (seg005:061F/073E) -> pop_get_item_action() в
  pop_map (тайлы): стоя НА предмете отступить на тайл назад, подровняться
  по кромке и присесть; из приседа над мечом — поднять;
- do_pickup (seg006:1671): тайл -> пол, pop_item_taken = tilepos+1;
  roomtest запекает пол на ОБЕИХ страницах (pop_floor_bake) и пишет
  per-room override, чтобы меч не воскресал при возврате в комнату;
- триггер — Shift (control_shift2) в стойке и в приседе, как в
  control_standing/control_crouched;
- seq_91 pickupsword: опкод SEQ_GET_ITEM с аргументом 1 в play_seq теперь
  зовёт pop_proc_get_object (seg006:16CB) -> pop_have_sword = 1.

Статус: pop_have_sword.  Боевой режим (стойка с мечом, бой) НЕ делаем и
он тут не нужен: seq_91 сам заканчивается убиранием меча в ножны
(кадры 230..240) и возвратом в обычную стойку — как в оригинале до
встречи со стражем.  Питьё зелий не портировано: над зельем Kid только
приседает (get_item возвращает «не обработано»).

Проверено в MAME (комната 15): меч виден на полу, Shift над ним даёт
присед -> кадр 229 «нашёл меч» -> ножны -> стойка; меч с пола исчезает,
pop_have_sword = 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:46:09 +03:00
snark13 63a3b60524 PoP roomtest: плита-потолок, порядок слоёв по SDLPoP, фиксы окклюзии и Y
Фича — плита-ПОТОЛОК (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>
2026-07-27 17:35:25 +03:00
snark13 86d7615841 PoP: roomtest — объекты/обломки/переходы комнат + арт
Порт 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>
2026-07-23 19:38:23 +03:00
snark13 3c0baacbf6 libc/kbd: recovery по Rx-overrun SIO (залипание клавиш) + kbd_raw_sync
Симптом (интермиттентный): при отпускании shift+стрелка иногда стрелка
залипает.  Диагностика: на чистом одновременном release break-коды
обрабатываются верно (проверено MCP) → drain-логика ISR корректна.
Остаточное залипание = переполнение 3-байтного аппаратного FIFO SIO при
пачке скан-кодов (F0 12 E0 F0 74 = 5 байт) во время длинных DI-окон →
потерян break → залипание.

Фикс: трамплин после drain читает RR1 SIO (бит5 = Rx Overrun), при
overrun делает Error Reset (WR0=0x30) и взводит _kbdraw_overrun.
Новый kbd_raw_sync() (звать раз в кадр) по флагу сбрасывает всё
held-состояние _kbdraw_down (какой break потерян — неизвестно; реально
зажатые перечитаются).  pop_ctrl_tick зовёт kbd_raw_sync().  Буфер W2-
трамплина 288→320 (трамплин 244 Б).

ВНИМАНИЕ: путь overrun НЕ проверен детерминированно (баг интермиттентный,
зависит от тайминга DI) — ТРЕБУЕТ ПРОВЕРКИ на железе/в длинной сессии.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 12:26:42 +03:00
snark13 ea57a03543 applications/PoP/roomtest: K4 — зацеп/вис/подтягивание/спуск
Порт SDLPoP seg004/005/006 «hang state»:

- pop_map: check_grab (зацеп за уступ в падении по Shift → seq_15 → вис),
  can_grab/can_grab_front_above + tile-запросы над/за персонажем;
  pop_jump_up_seq (check_jump_up: ↑ в стойке = чистый прыжок seq_28/14
  ЛИБО прыжок-с-зацепом seq_8/24/16 за уступ выше → запрыгнуть на этаж);
  pop_hang_* (climb_up seq_10/73, hang_fall seq_23/11, hang-у-стены);
  pop_down_action (спуск seq_68 у края лицом от края / отступ / присед).
- pop_ctrl: control_hanging/can_climb_up/hang_fall, control_jumpup,
  jump_up через pop_jump_up_seq, down_pressed через pop_down_action;
  pop_ctrl_shift_held() для check_grab.

Ключевой фикс check_bumped (seg004 guard'ы): не бампить при action
hang_climb/hang_straight, на кадрах подъёма/спуска 135..148 И на кадрах
виса 87..99.  Без последнего спуск (seq_68) на кадре frame_91 (action
ещё midair, act(hang_climb) идёт следующим опкодом) ловил отскок у стены
и рвал цепочку hang→hang_fall→seq_11, приземляя не туда.

Проверено в MAME (HDD-тест, покадровая трасса через мост): прыжок-с-
зацепом [row2 col4]→вис→подтягивание→[row1 col3]; спуск [row1 col3]→
[row2 col4] с корректной позицией у стены (x=122, совпадает с SDLPoP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:07:20 +03:00
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