Commit Graph

43 Commits

Author SHA1 Message Date
snark13 4cffbc9aa4 PoP roomtest (план v2, фаза 1b): pop_map переведён на пометки перерисовки
Loose-полы, плита-потолок и щебень от приземления больше не зовут pop_bg —
ставят пометки (pop_set_redraw / pop_set_redraw_above), которые разбирает
pop_redraw_needed из главного цикла.  Удалены самодельные счётчики
loose_bake/loose_rest/ceil_rest/ceil_bake/land_bake: их роль (вторая
страница дабл-буфера) теперь у счётчика страниц в пометке.

Осталось ОДНО исключение: падающий кусок (mob) — spawn/tick/pos.  Это
движущийся ОБЪЕКТ, а не перерисовка тайла, и в оригинале он живёт отдельно
(mobs + draw_moving), поэтому разделение его на логику и отрисовку —
следующая фаза.  Из-за него pop_loose_tick остаётся единственной функцией
pop_map, которую нельзя звать из __banked-кода; вся коллизия, физика,
кромки и предметы — то, что понадобится стражам — чисты от графики.

Замер: _CODE 24718 -> 24462, _DATA 4301 -> 4219 (ушли rest-массивы).

Проверено в MAME: комната 12, осторожный шаг на плиту — тряска, падение
плиты, Kid проваливается на ряд 1, дыра и щебень отрисованы; на
замороженном кадре обе страницы дабл-буфера побайтово совпадают в области
изменений (7 строк).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:44:56 +03:00
snark13 24bb724c22 PoP roomtest (план v2, фаза 1a): пометки перерисовки вместо прямых блитов
Порт архитектуры оригинала: логика анимации тайлов НИЧЕГО не рисует, она
ставит флаг (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>
2026-07-29 20:37:03 +03:00
snark13 eef6eebd8c PoP roomtest: фикс чтения таблицы кадров — баг кодогенерации SDCC
Кадры Kid читались из EMM-страницы по НЕВЕРНОМУ адресу для индексов >= 52.
Запись

    (const uint8_t *)(0x100) + (uint16_t)i * 5u

SDCC 4.5 собрал так: умножение честно в 16 битах (add hl,hl / add hl,bc),
а затем `ld c,l` + `inc b` — то есть взял только МЛАДШИЙ байт результата и
подставил старший байт константы.  При i*5 >= 256 адрес уезжал на -256*k,
и cur_frame наполнялся чужой строкой таблицы: у кадров бега/шага/подъёма
пропадал бит FRAME_NEEDS_FLOOR — Kid «вкручивался» в пол и проваливался
вниз, последовательности кадров не соответствовали seqtbl.

Фикс: адрес считается в uint16_t (i*5 = i + i<<2, без умножения) и
кастуется один раз — сгенерированный код теперь сохраняет старший байт
(ex de,hl / inc d).

Коварство бага: тот же паттерн в pop_level.c (room_fg_ptr/room_bg_ptr,
links) компилируется ПРАВИЛЬНО — проверил все три места по .asm.  Записано
в memory sdcc_z80_const_ptr_index_bug.

Проверено в MAME на замороженных кадрах: 10 выборок (кадры 4,10,15,49,50,
54,123) — cur_frame совпадает с таблицей во всех, включая те, что раньше
были испорчены.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:24:03 +03:00
snark13 3de8c7500c PoP roomtest (план v2, шаг 4): в W3-резиденте остаётся только pop_bg
--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>
2026-07-29 19:30:57 +03:00
snark13 8e33cd07bc PoP roomtest (план v2, шаг 1): таблицы анимации Kid -> EMM-страница
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>
2026-07-29 19:15:16 +03:00
snark13 a5773ab654 docs(PoP): план v2 — размер кода и раскладка по окнам/банкам/страницам
Новый документ applications/PoP/docs/layout_plan_v2.md по свежему замеру
(коммит 1214785): точные размеры окон/модулей/функций/данных, уточнённая
модель банкинга и пошаговый план.

Главное уточнение против v1: из __banked-кода резидент W3 недостижим — и
транзитивно тоже (bank -> pop_map -> pop_bg сломается).  Отсюда целевая
раскладка: W3-резидент = графика, которую зовёт только главный цикл;
W1/W2 = ядро, достижимое отовсюду (включая банки); банки = новая холодная
логика (стражи/боёвка).  Проверено по libbgi: скобка _bgi_begin/_bgi_end
сохраняет и возвращает ТЕКУЩУЮ страницу W3, поэтому примитивы libbgi
можно звать и из банка; нельзя лишь открывать скобку из кода, лежащего
в W3.

Крупнейшие цели: kid_data.h (3745 Б таблиц в _CODE) -> EMM-страница с
портом load_frame/cur_frame; вынос loose/потолка из pop_map в W3 (делает
pop_map bank-safe); дедуп геометрии в pop_geom.c; разгрузка _DATA.

Попутная находка: --w3 принимает ОДИН файл на флаг, поэтому в Makefile
"--w3 pop_trob.c pop_map.c" кладёт в W3 только pop_trob, а pop_map едет
в W1/W2 (в build-каталоге остался устаревший w3_pop_map.rel).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 18:55:36 +03:00
snark13 64ce6339eb docs: справочники по железу Sprinter + правка gfx_scroll_h
- docs/new/ — сводные справочники (архитектура, BIOS, DSS, память,
  графика, акселератор, IRQ, порты, ввод, звук, известные баги);
- docs/Original/ — первоисточники, из которых они собраны (BIOS, Estex
  DSS, мануалы, описание акселератора), + Форум.doc/.docx в reference;
- libbgi/common/gfx_scroll_h.c — обход бага скролла при ширине >256
  (правка автора: шаг банды 255 и продвижение указателей на cw; старый
  вариант с 256 оставлен закомментированным с TODO);
- удалён applications/PoP/roomtest/hang_variants.png — рабочая раскладка
  из разбора позы виса, в репозитории ей не место.

Большие архивы (docs/extra ~568 МБ, docs/sources с вложенными git-репо
~68 МБ) в коммит НЕ включены — см. обсуждение.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 18:26:31 +03:00
snark13 1214785a56 PoP roomtest: дверь уровня — непрозрачные куски + финальный кадр на 2-ю страницу
Два дефекта открытой двери:

1. Слева от лестницы оставалась поднявшаяся решётка.  Оригинал рисует ВСЕ
   куски двери blitters_0_no_transp, а наш упаковщик по умолчанию гонит
   пиксель 0 в 0xFF (ключ прозрачности) — марш лестницы 144 переставал
   закрашивать створку под собой.  Добавил LEVELDOOR_ENV_IDS в
   NO_TRANSP_ENV_IDS (тот же приём, что для 43/73/74/96/149).

2. Дверь дрожала через кадр: створка анимируется в back-страницу, и
   ПОСЛЕДНИЙ кадр анимации ложился только на одну из двух страниц, вторая
   застревала на шаг раньше.  Добавлен отложенный редрой (ldoor_rest) —
   повтор финального кадра на второй странице, как spike_rest/button_rest.

Проверено в MAME с заморозкой кадра: обе страницы в области двери
побайтово одинаковы, слева от лестницы чистый чёрный фон.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 18:18:01 +03:00
snark13 63a25530f5 PoP roomtest: дверь уровня — створка, лестница и анимация открытия
Комната 9: портал на уровень 2 рисовался чёрным проёмом — не был
портирован draw_leveldoor (seg008:1D29).  Дверь рисуется при обработке
ПРАВОЙ половины (tile_left = 0x10), все куски со сдвигом +8 px:
  99  низ лестницы (всегда),
  144 марш лестницы за створкой (когда створка тронулась),
  33  слайс створки — повторяется вниз с шагом 4 px до y = ybottom-modif
      (modif 0 = закрыто на всю высоту, 43 = открыто, остаётся кромка),
  34  верх коробки.

Анимация: animate_leveldoor (seg007:05F1) — type 0..2 открытие (modif++
до 43), type>=3 быстрое закрытие со скоростями {0,5,17,99}.  Кнопка
заводит trob через trigger_1 (seg007:0999): дверь открывается ОДИН раз,
при modif != 0 кнопка уже ничего не делает.

Перерисовка створки — pop_leveldoor_redraw: draw_tile правой половины С
ЗАПЕЧКОЙ в ОЗУ-копию (банк TRANSPARENT), иначе kid_heal возвращал бы из
фона закрытую створку.  Куски двери непрозрачные, wipe не нужен.

Спрайты 33/34/99/144 не попадали в атлас: сбор идёт прогоном
render_room.py, а он draw_leveldoor не реализует — добавлены явным
набором LEVELDOOR_ENV_IDS (как STUCK_ENV_IDS для нажатой кнопки).

Проверено в MAME (комната 9): закрытая дверь = решётка как в оригинале;
после нажатия кнопки (0,0) створка едет вверх, открывая лестницу.
Вход в дверь (кадры 217..228 + переход на уровень 2) НЕ делался.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 18:05:57 +03:00
snark13 fb495ace42 PoP roomtest: вспышка фона стробом + красная вспышка урона (+ урон падения)
1. Жёлтая вспышка подъёма меча была ОДНОЙ длинной заливкой.  В оригинале
   (seg003:0AFC flash_if_hurt / remove_flash_if_hurt) цвет ставится и
   СНИМАЕТСЯ в том же кадре — пока идёт flash_time, видно быстрый строб
   «жёлтый/чёрный».  У нас теперь так же: цвет ставится в кадре и
   снимается после ПЕРВОГО gfx_wait_vsync из трёх (≈20 мс жёлтого,
   40 мс чёрного — близко к оригинальным 2 тикам таймера).

2. Красной вспышки при потере HP не было вовсе.  Порт второй ветки
   flash_if_hurt: если flash_time не активен, но в этом кадре hitp_delta<0
   — do_flash(color_12_brightred) ровно на кадр.  У нас триггер —
   pop_kid_hurt (он же рисует «брызги»).

3. Чтобы вспышке было от чего срабатывать, портирован урон падения из
   land() (seg005), которого у нас не было: <22 — мягко, <33 — −1 HP и
   seq_20 (2 этажа), иначе take_hp(100) + seq_22_crushed (насмерть).
   ВНИМАНИЕ: это меняет геймплей — падение с 2 этажей теперь отнимает HP,
   с 3+ убивает (как в оригинале).

Проверено в MAME: pop_flash_time тикает 5→3→1→0 (строб), падение с ряда 0
на ряд 2 даёт hitp_curr 3→2 и кадр 109 (medium land).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 17:40:52 +03:00
snark13 e1846369ad PoP roomtest: блеск лежащего меча, клинок в руке, вспышка фона
Три расхождения с оригиналом на сцене подъёма меча (комната 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>
2026-07-29 17:22:22 +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 6769b6a01b PoP roomtest: loose-плита впереди = КРОМКА в get_edge_distance
Осторожный шаг к проваливающемуся полу проваливал Kid с первого же шага:
в pop_edge_distance не было ветки оригинала (seg004:067C)

    if (tiletype == tiles_11_loose) goto loc_59FB;  // CLOSER + до кромки

— плита читалась как обычный пол (EDGE_FLOOR, 11), и весь механизм
«проверки ногой» пролетал мимо.  С веткой safe_step (seg005) отрабатывает
три фазы, как в оригинале: (1) укороченный шаг РОВНО до кромки плиты
(seq 29..42 по distance), (2) seq_44 testfoot — щуп ногой, плита трясётся
по SEQ_KNOCK_DOWN, Kid остаётся на месте (Char.repeat), (3) только третье
нажатие — шаг на плиту и падение вместе с ней.

Заодно портированы соседние ветки той же функции: верх двери лицом вправо
(проверяется ДО wall_type) и closer/меч/зелье (кромка, пока есть зазор).

Проверено вручную в MAME: все три фазы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:29:01 +03:00
snark13 5f5eefc9fc PoP roomtest: loose-плита поверх Кида (порт draw_loose + draw_tile_base)
Комната 12, вис и подтягивание на кромке loose-плиты над дырой от
соседней упавшей: плита рисовалась ПОД Кидом — он лез на неё «с
переднего края» вместо проёма.  Не хватало двух кусков draw_tile:

1. draw_loose (seg008:0A38) кладёт нижнюю грань плиты (loose_fram_bottom)
   В ОБЕ таблицы — backtable И foretable, безусловно.  Это единственный
   кусок тайла с таким поведением: у обычного пола bottom идёт только в
   backtable, а fore_id = 0.  Добавлено в fore_tile (наш проход foretable
   по тайлам футпринта Кида); ceiling-случай это уже делал отдельно.
2. draw_tile_base (seg008:0A8E) подставляет id: у loose верх плиты берётся
   из loose_fram_left, у opener'а без пола слева — 148.  В нашем
   midtable-оверлее (overlay_mid_tile) стоял голый tile_table.base_id, а у
   loose он 0 — верх плиты в оверлей не попадал, и поверх Кида ложилась
   только передняя грань.  Перенесён draw_tile_base целиком.

Проверено в MAME: кадр виса на кромке целой плиты (frame 89, x=95,
col 2) — плита закрывает Кида, наружу торчат только пальцы, как в SDLPoP.

Голова стоящего Кида поверх падающей НА НЕГО плиты — артефакт САМОГО
оригинала (сверено с SDLPoP v1.24), не чинить: записано в bug_list.md
(раздел «НЕ БАГИ») и в memory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:14:23 +03:00
snark13 517d225091 PoP roomtest: полный порт bumped (seg004) — прижатие Y к полу + hardbump
Комната 5, прыжок в решётку: Kid оставался стоять на 6 пикселей выше
пола и без приземления-приседания.  Шесть пикселей — это dy(-6) кадра
frame_25_standing_jump_10: удар обрывал standjump ровно между кадрами
25 и 26, парный dy(+6) не выполнялся.

От оригинального bumped() у нас был портирован только хвост (выровнять
X к грани + seq_47_bump).  Добавлены недостающие ветки:

- bumped()      — исход удара выбирается по тайлу, НА КОТОРОМ персонаж
                  оказался после отжатия (сквозь стену/верх двери это
                  соседняя клетка, для ворот/зеркала — сама клетка);
- bumped_floor  — прижимает Y к y_land, ветка fall_y>=22 (только отжать
                  на 5) и выбор сиквенса по кадру: 24/25/40..42/102..106
                  -> seq_46_hardbump (отскок с приседанием), иначе 47;
- bumped_fall   — удар выше пола на 15+ px (беззнаковое сравнение
                  оригинала) или не над полом: seq_45, в свободном
                  падении — только гашение fall_x.

Гард «уже в отскоке — не рестартить» (наш, от edge-триггера коллизий)
перенесён внутрь bumped_floor: выравнивание X/Y идёт всегда, рестарта
анимации нет.  Проверено в MAME.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 14:23:10 +03:00
snark13 fc69316c2c PoP roomtest: окклюзия падающей плиты соседним полом — ряд из координаты
Комната 1, плита (2,6): её правая грань (env 42, лежит целиком в ячейке 7)
оставалась ПОВЕРХ пола (2,7) и перекрывала его переднюю грань.

Причина: mob_render перерисовывал соседний тайл по m->row — а это
ЛОГИЧЕСКИЙ счётчик «сквозь какой ряд летим», который mob_down_a_row уводит
на ряд вперёд.  Для плиты НИЖНЕГО ряда он сразу становится 3, и окклюзия
звала draw_tile(3, col+1) — ряда 3 нет, тайл рисовался за нижним краем
экрана, то есть пол соседа не перерисовывался никогда.

Оригинал берёт ряд ИЗ КООРДИНАТЫ куска, draw_mob (seg007:13E5):
    tile_row = y_to_row_mod4(ypos);      set_redraw2(tilepos справа)
    top_row  = y_to_row_mod4(ypos - 18); если отличается — ТОЖЕ пометить
То есть помечаются ДВА тайла справа: под низом куска и под его верхом, пока
кусок висит на границе рядов.  Второго у нас не было вовсе — и именно он тут
решающий: при y=194 нижний ряд даёт -1 (полоса у потолка), а верхний — 2,
то есть настоящий пол (2,7).

Проверено в MAME покадрово (bp на pop_loose_mob_spawn + cmd gv; NB: главный
цикл ждёт ТРИ vsync на итерацию, один gv = треть игрового кадра): следов
плиты на полу (2,7) нет, передняя грань цела, потолочная полоса не тронута.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 12:28:50 +03:00
snark13 08ebf09d4d PoP roomtest: пометить недостижимые комнаты уровня 1 (13, 18, 24)
Обход графа связей res2001.bin (links @1952) от стартовой комнаты 1: 13, 18
и 24 недостижимы — ссылки наружу у них есть, на них не ссылается никто
(24: L->9, но у 9 R=0).  Несимметричные ссылки ровно у этих трёх, у прочих
21 симметрия полная — признак выкинутых из компоновки комнат.  В таблицу
обхода добавлена пометка, чтобы не гоняться за призраками: рендер кромки
читает колонку соседа ПО ССЫЛКЕ, а сосед этих комнат соседом себя не
считает, так что странный шов там — свойство данных.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 12:06:47 +03:00
snark13 08c6a8504f PoP roomtest: отладочный обход комнат (+/-) + TODO по редрою пик и idle-skip
ROOMNAV (одна строка #define в roomtest.c): '+'/'-' — следующая/предыдущая
комната уровня ПО НОМЕРУ (1..24, с обёрткой), Kid ставится на первый пол,
pop_trob_reset() возвращает пики/ворота в исходное.  Нужен для обхода всех
24 комнат в поиске багов отрисовки, включая недостижимые обычным путём.
Цена — 505 Б кода W1 (22816 -> 23321), они же вычитаются из кучи (4178 ->
3673 Б); W3 не тронут.  Переводить приложение в huge ради этого не стали:
смена модели памяти (банкованный W1 + трамплины) ради полукилобайта —
плохой размен прямо перед прогоном комнат.

Номер комнаты — ПОЛОСКАМИ в верхнем борте (слева десятки, справа единицы),
а не outtextxy: текст тянет системный знакогенератор (_gfx_font_buf, 2 КБ
статики) и сажает кучу до 576 Б.  Цвет полосок 0x57, а не WHITE: палитра
игровая, запись 15 в ней ЧЁРНАЯ (kid.pal 0x0F = 0,0,0) — из-за этого
закомментированный ранее лейбл был бы невидим в любом случае.

bug_list.md: раздел TODO (T-1 редрой пик по причине, T-2 idle-skip) +
пустая таблица на 24 комнаты под результаты обхода.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 11:58:06 +03:00
snark13 723da3c5c1 PoP roomtest: пики — редрой каждый кадр, полный draw_tile; пламя без heal
Выдвинутая пика живёт ТОЛЬКО в видео-ОЗУ (банк SPRITE, в запечённый фон не
входит), а kid_heal каждый кадр возвращает под спрайтом Кида печёный фон —
то есть стирает попавшие под него части остриёв.  Редрой «только при смене
видимой сигнатуры» их не возвращал: hold держится 15 кадров (modif
0x8F..0x81) с неизменным кадром 5.  Итог по дампу VRAM (комната 14, Kid на
(2,7), modif 0x8E): у спрайта 132 стёрто третье остриё (x 245..247), у 138 —
прямое целиком (x 258..260) и низ наклонного, страницы дабл-буфера
расходились на 8 пикселей.  SDLPoP: animate_spike (seg007:0353) зовёт
redraw_21h БЕЗУСЛОВНО, вне всяких if — редрой каждый кадр, пока trob жив.

pop_spike_redraw переписан на честный redraw_tile_height (seg007:0218):
heal + ПОЛНЫЙ draw_tile своего тайла и правого соседа.  Рисовать только два
спрайта остриёв нельзя — теряется порядок слоёв внутри тайла
(draw_tile_anim_right идёт ПЕРВЫМ, база и fore соседа ложатся поверх), и
остриё лезло на колонну (2,9).  draw_tile заодно сам восстанавливает вклад
соседних пик (ветка lcode==2), так что две пики подряд больше не гасят
друг друга.

Пламя факела: heal убран.  Оригинал рисует его blitters_0_no_transp (seg008
draw_tile_anim_right), все 9 кадров лежат на общем канвасе 16x18 и бокс
центрирован в ячейке — непрозрачный блит сам полностью накрывает предыдущий
кадр.  Кадры пакуются с opaque=True (POT_FLAME_IDS).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 11:34:07 +03:00
snark13 e143ee010a PoP roomtest: нажатая кнопка-closer, таблица падающих кусков, фон-покой при запечке
Кнопка-closer (tiles_6) в нажатом состоянии рисуется как tiles_5_stuck
(get_tile_to_draw seg008:2FE), но её грани — env id 35 (правая) и 36
(нижняя) — не попадали в атлас: pop_pack_bg собирает набор спрайтов
прогоном render_room по статике уровней, а тайла 5 статически в уровнях
нет.  Итог — чёрный провал на месте нижней грани.  Добавлены явным
набором STUCK_ENV_IDS (как loose/пики/ворота).  Заодно OPENER_NOFLOOR_ENV_IDS
= {148}: левая половина кнопки-opener, когда слева пусто (draw_tile_base
seg008:0A8E) — ветка была не реализована ни в pop_bg, ни в render_room.

bake_rest: при ЗАПЕЧКЕ статического фона (запись идёт в ОЗУ-копию
акселератора, из неё восстанавливает heal) get_loose_frame возвращает
кадр ПОКОЯ.  Иначе транзиентный кадр тряски соседней плиты консервируется
в копии, а так как запечка идёт двумя кадрами (по одному на страницу
дабл-буфера) — на страницах застывают РАЗНЫЕ кадры, вечное мерцание.

mobs[MOB_MAX] вместо одиночных статиков: две плиты, упавшие подряд,
теряли первый кусок (застывал в воздухе) и оставляли щебень только от
одной.  Плюс отложенная допечка прошлого приземления ПЕРЕД разбором
нового и ограничение чёрного бара в pop_loose_bake_empty низом тайла.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 12:07:29 +03:00
snark13 b106aba59b PoP roomtest: подъём/спуск на ОТКРЫТЫХ воротах (потерянная проверка openness)
Kid не мог залезть на тайл поднятой решётки: срывался обратно.  can_climb_up
(seg005:09DF) выбирает seq_73 («влез под решётку и съехал») только для
ЗАКРЫТЫХ ворот — условие включает curr_room_modif[curr_tilepos] >> 2 < 6, а у
нас его не было, и seq_73 играл при любой решётке над головой при взгляде
влево.

Та же дыра нашлась в спуске: down_pressed (seg005:482) запрещает climb-down с
тайла ворот лицом влево тоже ТОЛЬКО пока они закрыты
(|| curr_room_modif[curr_tilepos] >> 2 >= 6).  Без этого Kid приседал вместо
спуска даже под поднятой решёткой.

Openness обеих проверок берётся общим хелпером gate_modif(col,row) (вынесен из
gate_passable — умеет свою комнату и соседнюю через шов).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 10:59:30 +03:00
snark13 5e09bd9d5a PoP roomtest: непрозрачные нижние грани + edge-триггер бампа + 2 колонки шва
Три фикса, найденные разбором в 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>
2026-07-28 10:47:58 +03:00
snark13 6582154381 PoP roomtest: clip_char (верхняя обрезка спрайта) + подстановка нажатой кнопки
Спуск Кида с кнопки (room8, кромка (0,6)) рисовался неверно: Кид просвечивал
в щель между кнопкой и ближним столбом, а ближняя рука была срезана до одного
оторванного пикселя.  Две независимые причины.

1. Не был портирован clip_char() (seg006:1749) — оригинал перед add_objtable
   обрезает спрайт персонажа сверху по y_clip[curr_row+1], когда тайл над
   головой стена или пол.  Порт: pop_clip_char_top() (pop_map.c, метрики по
   set_char_collision) + новый примитив gfx_blit_cols_part() в libbgi (блит
   column-major с пропуском skip верхних строк; обрезка сверху бесплатна —
   колонка непрерывна в ОЗУ, сдвигается только старт).  gfx_blit_cols стал
   тонкой обёрткой над ним.  kid_heal чистит уже ОБРЕЗАННЫЙ прямоугольник,
   иначе стирается кромка пола над срезом.

2. climb_overlay_tile (порт draw_floor_overlay, seg008:1E3A) выбирал ветку по
   СЫРОМУ коду тайла, а get_tile_to_draw (seg008:240) подменяет нажатую кнопку
   на floor/stuck.  Тайл-кнопка не проходил тест floor → уходил в
   draw_other_overlay, который кладёт поверх Кида ВЕСЬ тайл вместо узкой
   кромки floor_left_overlay[frame-137].  Фикс — tile_code_drawn(): одна
   подстановка на все слои (fore_only_tile/overlay_mid_tile перестали её
   дублировать, W3 −293 Б).

Проверено покадрово в MAME (шаг gv + снимок): кадры спуска 148..138 чистые,
после приземления на кнопке мусора нет.

Заодно: gfx_heal_noclip() (пара к gfx_blit_noclip; heal 11658 -> 8982 тактов)
и rest_pending в pop_trob — холостой проход по 30 тайлам только когда есть
отложенные редрои пик/кнопок (pop_process_trobs 89-111К -> 70-92К тактов).

START_ROOM временно = 8 (отладка спуска с кнопки), вернуть на 6/старт уровня.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 09:50:47 +03:00
snark13 c127a4b0d2 PoP: факелы и зелья (chtab_1) + быстрый блит фона gfx_blit_noclip
Графика 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>
2026-07-27 20:53:04 +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 d1bc97c589 roomtest: обломки упавшего loose в комнате снизу (debris)
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>
2026-07-20 21:26:21 +03:00
snark13 bf7ddfd4da roomtest: L3 переходы вбок (право+лево) + план объектов (кнопки/гейты/пики)
Переходы в соседние комнаты по горизонтали (порт 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>
2026-07-20 21:14:03 +03:00
snark13 7f3e32ddd6 roomtest: L2 — переход в комнату снизу (провал/спуск) + фиксы окклюзии
При пересечении нижней границы комнаты Kid переходит в комнату links.down,
продолжая падать/спускаться (порт SDLPoP goto_other_room/leave_room).

- pop_map check_leave_below (порт leave_room, seg002): триггер по Kid.y>=211
  (не curr_row) — единый для провала И спуска-зацепа/climbdown; репроекция
  координат в кадр комнаты снизу (y-=189, curr_row=y_to_row_mod4) + сигнал
  pop_fell_out.  Убран прежний преждевременный триггер y>=189 из do_fall
  (из-за него climbdown с curr_row=3 ждал y_land[4]=244 → пролёт через комнату).
- roomtest: enter_room(room) — загрузка комнаты в рабочие массивы + отрисовка
  фона в обе страницы; обработчик pop_fell_out переключает комнату по
  pop_room_link(down) или респавнит (нет комнаты снизу).  cur_room-трекинг.
- pop_loose_reset (pop_map) + pop_loose_mob_reset (pop_bg): сброс loose-состояния
  (индексировано позицией тайла) при смене комнаты — иначе течёт в новую.

Фиксы окклюзии при висе/спуске (сверено с SDLPoP):
- other_overlay_tile: пустой тайл НЕ окклюдирует — draw_tile для него рисует
  лишь фоновую сетку (BLUELINE «силуэт кладки»), которая в оригинале уходит в
  y-сортируемый midtable ПОЗАДИ Kid; у нас клалась поверх.  Пропускаем.
- pop_fore_over_kid: порт set_char_collision (seg006:0723) — char_bottom_row =
  y_to_row_mod4(obj_y), обёртка -1 → ряд 3 (Kid НИЖЕ комнаты при спуске), а не
  ряд 0.  Прежний кламп в 0 давал rT>rB → цикл окклюзии не выполнялся, нога
  рисовалась поверх пола.  single-room: ряд 3 клампится в 2 (передняя грань пола).

Проверено в MAME: провал вниз, спуск-зацеп/climbdown с кромки, окклюзия ноги.
Известно (позже): осколки упавшего loose в комнате снизу (перенос mob),
per-room modified-tile tracking (re-entry восстанавливает исходные тайлы), HP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:22:52 +03:00
snark13 21f978d441 roomtest: L1 — данные уровня из сырого res2001.bin вместо хардкода
Отказ от 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>
2026-07-20 15:52:31 +03:00
snark13 8b30dc20c8 roomtest: loose-полы — тряска (knock), падение с окклюзией, фикс дабл-буфера
Проваливающиеся полы в roomtest (порт SDLPoP seg007/seg008), проверено в MAME:

- Падающий кусок (mob): правый край env-42 (w=26, доходит до mob_x+57)
  теперь полностью покрыт heal-коридором (MOB_W 56->64) — убран тёмный
  хвост-тень на полу 2,7 ПОСЛЕ падения.
- Окклюзия правого куска во ВРЕМЯ падения: после отрисовки плиты
  перерисовывается сосед-пол draw_tile(row,col+1) в том же кадре — край
  прячется за полом 2,7 (порт redraw_at_cur_mob: set_redraw_full+1).
- Тряска от сотрясения (knock): seqtbl-команды KNOCK_DOWN/UP в play_seq ->
  флаг knock -> do_knock(ряд) трясёт loose ряда из покоя (modif=0x80).
  KNOCK_DOWN в land-seq (приземление) и runcyc (footstep).
- Фикс «бесконечной тряски» (дабл-буфер): при завершении тряски (0x84->0)
  перерисовать покойный кадр плиты на ОБЕИХ страницах (loose_rest, 2 кадра) —
  иначе на одной странице застревает дрожащий кадр правой грани (живёт в
  тайлах col и col+1) -> мерцание через флип.

Инфраструктура/документация:
- app.mk: цель `make hdd` (упаковка в D: для MCP-моста MAME).
- docs: POP-DAT-FormatSpecifications.pdf/.txt как каноническая спецификация
  форматов ресурсов; ссылки в README/MSDOS_RESOURCE_FORMAT.
- README.md/CLAUDE.md для applications/PoP и roomtest (правило «SDLPoP —
  источник истины», порядок слоёв, режим отладки freeze 1/2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 15:27:47 +03:00
snark13 5ef084eca4 docs: loose_floors_plan — конкретика SDLPoP + подход интеграции
Дополнен точными данными из SDLPoP (сверено): кадровые таблицы
loose_fram_left/right/bottom, get_loose_frame, y_loose_land, delay=11;
триггеры с call-sites (check_press->make_loose_fall при стоянии на 11,
frame79-сверху; do_knock на жёстком приземлении; animate каждый кадр);
tile_is_floor(11)=1.  Плюс подход интеграции в наш движок: общая
мутабельная копия комнаты pop_map<->pop_bg + per-page запекание пустоты
после падения; тонкое место — перерисовка динамического тайла в дабл-буфере.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 21:43:43 +03:00
snark13 36a5a5e194 roomtest: фикс провала сквозь пол при беге с кромки (порт start_fall/in_wall)
Тап-бег с кромки [1,3] вправо -> Kid проваливался в стену col2/3 ->
респавн, вместо посадки на [2,4].  Сверено с SDLPoP seg006:

- start_fall: frame 9 -> seq_7_fall (был ошибочно seq_19 как у 13);
  frame 13 -> seq_19 (лишний dx(1)).  + хвост seg006:1099: тайл ПЕРЕД
  персонажем — стена -> Char.x = char_dx_forward(-1);
- in_wall переписан аутентично (seg006:1292): выталкивает ВПЕРЁД из стены
  (delta+4) на соседний тайл, а не назад вглубь (наш старый через
  dist_from_wall_forward давал отрицательный сдвиг -> Kid уходил в стену
  col2 -> проваливался).  distance_to_edge_weight + 6-delta/+4.

Проверено в MAME (пользователь): бег с кромки [1,3] -> посадка на [2,4].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 21:23:09 +03:00
snark13 237f780e0e roomtest: fore-окклюзия пола/стены над Kid (порт redraw_at_char/char2)
Kid — высокий спрайт: голова/руки торчат в ряд выше опорного, floor/wall
там должны перекрывать его.  Сверено с SDLPoP (seg003 redraw_at_char2 +
seg008 draw_tile_fore/draw_other_overlay/draw_floor_overlay):

- fore_tile (draw_tile_fore): стена рисует и WALL_FRAM_BOTTOM (нижняя
  грань-решётка), не только MAIN — руки при прыжке в потолок [2,7]->[1,7]
  уходят за стену;
- диапазон рядов форсит включение ряда над опорным (y_to_row верха спрайта
  мог схлопнуться из-за +60-сдвига);
- other_overlay_tile (draw_other_overlay): на КРОМКЕ пола (сосед слева пуст)
  перерисовать весь тайл поверх Kid — но ТОЛЬКО в позах захвата (78-79),
  виса (action 2/6), полёта (3/4), старта падения (bumped 102-106) и начала
  подъёма (135/136, до SEQ_UP на 141 Kid ещё в ряду ПОД полом).  Гейтинг
  как в redraw_at_char2 — при стоянии/беге ложной окклюзии нет;
- отладочный стоп-кадр: '1' заморозить / '2' продолжить (для разбора поз).

Проверено в MAME (пользователь): прыжок-в-потолок, короткий прыжок,
спрыгивание, подъём на [0,3] — окклюзия корректна.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 20:41:55 +03:00
snark13 023b45eb85 roomtest: двойная буферизация (два экрана + флип), тумблер SPACE
Убирает мерцание/тиринг при перерисовке слоёв (Kid/fore/пол-оверлей):
рендер всего кадра в скрытую страницу, tear-free флип на vblank.
Инфра libbgi (gfx_set_draw_page/visible_page/wait_vsync) уже была.

- фон комнаты рисуется в ОБЕ графические страницы (у каждой своя
  ОЗУ-копия — источник heal); палитра 0->1 уже синкалась gfx_pal_sync;
- kid_heal/kid_draw: прямоугольник Kid запоминается ПО СТРАНИЦЕ
  (kid_l*[2]) — при чередовании страниц heal стирает пиксели своей
  страницы (прошлый Kid там был 2 логических кадра назад);
- цикл: draw в back, 3x wait_vsync (пейсинг), gfx_set_visible_page(back);
- SPACE (edge) — тумблер: off = однобуфер (draw==visible==0) для отладки.

Мерцание при подъёме подтверждено устранённым в MAME (пользователь).
План: applications/PoP/docs/double_buffer_plan.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 16:07:03 +03:00
snark13 fce3e58830 applications/PoP/docs: план порта loose floors (проваливающиеся полы)
Разбор SDLPoP (seg007 loose/trob/mob, seg008 draw_loose): хранение
состояния (tile 11 + curr_room_modif: 0 покой / 0x80.. тряска / 1..11
отсчёт падения), триггеры тряски (do_knock на приземлении → shake ряда)
и падения (make_loose_fall при стойке/зацепе на loose), падающий кусок
(mob → debris снизу, empty сверху), отрисовка по статусу (loose_fram_*
через get_loose_frame).  Порядок реализации L1..L5 + что нужно в нашем
движке (динамический тайловый слой: room_modif[], trob-очередь, mob).

ПЛАН — не реализация.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 12:31: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 cbae48dbc4 applications/PoP/roomtest: пол-оверлей при подъёме (draw_floor_overlay)
Проблема из динамики: при подъёме [1,2]→[0,3] нижняя часть Kid, которая
физически за полом назначения, просвечивала.

Порт SDLPoP draw_floor_overlay (seg008:1457): на кадрах подъёма 137..144,
если тайл-назначения floor-подобный (floor/pillar/stuck/torch) И тайл СЛЕВА
пуст (кромка), передний край пола (floor_left_overlay[frame-137] =
{32,151,151,150,150,151,32,32}) + низ пола рисуются ПОВЕРХ нижней части Kid.

- pop_bg: climb_overlay_tile + доп-проход в pop_fore_over_kid (новый параметр
  frame) на кадрах 137..144.
- pop_pack_bg.py: CLIMB_OVERLAY_ENV_IDS={32,150,151} явно добавлены в env-фон
  (не попадают в used render_room — рантайм-анимация); env4 count 16→24.
- roomtest: передача Kid.frame в pop_fore_over_kid.

Проверено в MAME (кадр 137): при подтягивании видна только голова/плечи Kid
над кромкой [0,3], нижняя часть скрыта за полом.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 11:37:56 +03:00
snark13 419d5e4eff applications/PoP/roomtest: фикс ухода Kid в стену при прыжке с кромки
Баг (из динамики): стоя на кромке [1,3]/[1,4] лицом вправо + Up, Kid
телепортировался/застревал в стене col8.

КОРЕНЬ (трасса MCP): jumpup(seq_14) на кромке → приземление над ямой →
падение; на кадрах падения action=3 (midair) check_bumped был НЕ заглушён
(guard покрывал только freefall/hang/climb-кадры).  Kid дрейфовал к стене,
get_tile вне рядов давал WALL, dist_from_wall_forward/x_bump[] — мусор с
большим отрицательным сдвигом → Kid.x=char_dx_forward((int8_t)d) → underflow
uint8 X (0→255) → долёт до col8 и застревание в стене.

Фикс check_bumped: заглушить и в action MIDAIR (кадры падения), и при
curr_row вне [0..2] (падение мимо пола — тайлы вне комнаты = WALL, bump-мусор;
fell_out ловит do_fall).  Проверено в MAME: на кромке Kid больше НЕ уходит
в стену — чисто падает (в тесте сброс на старт).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 11:20:48 +03:00
snark13 5935724897 applications/PoP/roomtest: стены в fore-проходе поверх Kid
Проблема из динамики: за стенами Kid не прятался (стена [2,9] должна
перекрывать).  В PoP основная грань стены (wall_fram_main) добавляется в
FORETABLE (seg008:712) — перекрывает персонажа.  У нас fore-проход
рисовал только fore_id, а у стены (0x14) fore_id=0.

fore_tile: для стены (code==20) рисуем WALL_FRAM_MAIN + wall_pattern(,,1)
поверх Kid (как draw_tile_fore), а не только fore_id.  Проверено в MAME:
Kid прячется за правой стеной col9.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:47:10 +03:00
snark13 78f2aaec1a applications/PoP/roomtest: fore-слой поверх Kid (передние тайлы)
Порт SDLPoP seg003 redraw_at_char + seg008 set_char_collision: после
kid_draw передний слой (fore_id = foretable-кусок) тайлов ФУТПРИНТА
спрайта Kid перерисовывается ПОВЕРХ него — то, что по изометрии перед
персонажем (передние грани колонн/ворот/большой колонны/щебня).  Стены и
факелы имеют fore_id=0 → остаются сзади (в статическом фоне).

- pop_bg: pop_fore_over_kid(obj_x,obj_y,w,h,dir) — считает футпринт
  (char_x_left/right, col_from_x, y_to_row_mod4) рядов top..bottom ×
  колонок left..right (≤2×2=4 тайла) и рисует fore_id каждого в
  GFX_BANK_SPRITE (видео-ОЗУ; kid_heal восстановит из теневого фона,
  fore в нём запечён pop_room_draw).
- pop_kid: kid_fp_obj_x/y/width/height — метрики последнего кадра
  (obj_x ЛОГИЧЕСКАЯ, до ×8/7) для футпринта.
- roomtest: вызов pop_fore_over_kid после kid_draw.

Проверено в MAME: Kid, идя влево мимо колонны под навесом (row1 col3),
корректно уходит ЗА её переднюю грань (скрывается) и выходит с другой
стороны; задние колонны/факелы остаются сзади.

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