Commit Graph

38 Commits

Author SHA1 Message Date
snark13 a3c5c600da Таблица рекордов: оба показа оригинала, ввод имени, переходы полосами
pop_hof.c был написан, но никуда не вызывался: после финала автомат уходил
мимо него в title, а на титрах экран получался чёрным. Теперь это хвост
end_sequence и шаг show_title между credits и attract-demo, как в оригинале.

Два корня пустого экрана:

- story.pal — 256 записей, и запись 0x3F (цвет глифов шрифта) в ней чёрная:
  текст рисовался, но был не виден. Генератор кладёт туда золотой 0xB7;
- полосовой переход копирует страницу акселератором, а тот читает ОЗУ-копию,
  куда прозрачный блит текста не пишет. Строки рисуются после перехода,
  прямо в видимую страницу.

Попутно:

- s5 в архиве PV — фон таблицы (рамка story + логотип на y=24, HOF_POP);
- отдельный индекс палитры под фон текстовой рамки: финал красит его в
  #800000, титры оставляют #100060 (load_title_images(bgcolor)). Прежний
  ремап в индекс 9 подменить было нельзя — им нарисована сама титульная
  картинка;
- второй набор глифов в font.atl цветом 0x3E: у SDLPoP шрифт маска и
  show_hof_text рисует текст дважды разным цветом, у нас цвет запечён в
  пиксели. Каталог SPA1 держит счётчик в байте, поэтому набор обрезан по '_';
- общий pop_screen_present_ltr(): им теперь пользуются story-переход интро,
  титры, таблица и титульная картинка финала. Заодно вернулся пропущенный
  шаг show_title — экран с логотипом и Jordan Mechner между «свадьбой» и
  титрами (BUGS_CLOSED#cutscene-ltr).

Формат POP.HOF — 6 записей, имя до 15 символов, версия 2. Проверено в MAME
на сборке LEVEL=14: HAIL → титульная картинка → таблица с полосой ввода и
временем справа → ввод имени → титульная картинка; на следующем запуске
таблица показывается между credits и demo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 16:41:41 +03:00
snark13 faec9a7d3a Финальная тема won: кольцевой стриминг с диска
Последний неозвученный кусок игры. 115 секунд и 1,2 МБ в память не влезают
никак, поэтому тема играется кольцом из шести страниц (96 КБ = 9 с): насос
идёт по кругу, а pop_music_service дочитывает файл в те страницы, которые
насос уже прошёл. Страница звучит 1,5 с и читается 33 мс — запас
сорокакратный; дистанция считается без отдельных счётчиков, потому что
страница это ровно 128 блоков насоса.

Три вещи, всплывшие при живой проверке:

- кольцо обязано сниматься при любом обычном запуске трека, иначе
  следующая тема играет по кругу первых шести страниц (поймано на титрах
  сразу после победы);
- живые сцены комнаты принцессы теперь открывают CBL сами (cut_begin):
  pop_ending_show глушит звук первым действием, и «arrived to princess»
  не звучал вовсе;
- тема дослушивается до конца с возможностью прервать клавишей — как
  seg001:637. У оригинала там стоит ввод имени в таблицу рекордов; когда
  он появится, ожидание переедет за него (задача HOF-ENTRY).

Проверено в MAME на сборке LEVEL=14: после встречи с принцессой звучит
тема победы (id 56, кольцо 6), курсор уходит далеко за размер кольца —
подкачка успевает. Host-тесты: 15 наборов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 12:32:40 +03:00
snark13 0086ac80c2 Таблицы анимации Кида: kid_data.bin -> KID\kid.ani
Все ресурсы Кида теперь зовутся kid.*: kid.arc (атласы), kid.pal
(палитра), kid.ani (кадры + seqtbl). Расширение .ani говорит о
содержимом: это не «какие-то данные», а таблица кадров и байткод
последовательностей движения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 22:02:14 +03:00
snark13 7e6b38cca6 Музыка по ходу игры: смерть, зелья, меч, конец уровня, катсцены
Звуки 24..43 в наборе оцифровки пустые — в оригинале это Adlib-музыка, и
в digisnd её нет. На этом и построено подключение: pop_sfx_play для звука
с нулевой длиной кладёт номер в pop_mus_req, а разбирает заявку
pop_music_service() — один вызов на кадр из любого цикла (игра, интро,
катсцена). Всё чтение с диска живёт там.

Ждать полной загрузки джингла нельзя — это фриз посреди игры. Поэтому
pop_music_stream читает первую страницу (33 мс) и сразу пускает трек: она
звучит 1,5 с, а следующая читается те же 33 мс. Остальные доливаются по
одной за кадр.

Расставлено по местам оригинала: смерть (24/28/32), вступление первого
уровня и тень шестого (25), сцены перед уровнями (27/35/40), встреча с
Джафаром (29), зелья (30/33/39), время вышло (36), меч и смерть стража
(37), смерть Джафара (43), конец уровня (41/32), встреча с принцессой
(26). Темы «раз за уровень» сбрасывает pop_music_level_start.

Упакован 21 трек (2,4 МБ). Не озвучен только финал: won — 115 с и 78
страниц EMM, ему нужен кольцевой стриминг (задача MUS-WON на доске).

Проверено в MAME: вступительная тема первого уровня отыгрывается на
старте (курсор дошёл до конца, id 25). Host-тесты: 15 наборов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 21:52:37 +03:00
snark13 65797af44c Ресурсы — в архивы PBA1; тайминги сцены PV — по часам насоса CBL
ТАЙМИНГИ.  Пользователь заметил, что заклинание Джафара не совпадает с
музыкой. Замер в MAME (breakpoint на вспышке + курсор трека): реплика m53
успевала ДОИГРАТЬ до молнии, хотя по шкале оригинала между входом Джафара
и заклинанием ровно 822 тика = 13,7 с.

Причина: пейсинг считал НАШИ ожидания vsync, а не прошедшее время.
Полноэкранная копия страницы стоит больше кадра луча, и разница копилась.
Кадровые прерывания для счёта не годятся (теряются в di-окнах
акселератора), поэтому часами стал насос CBL: он идёт от расхода буфера
железом, 85,4 Гц, и ему безразлично, чем занят главный цикл. Один тик
оригинала = 57/40 тика насоса (0,07 % ошибки, без деления в кадре).
Когда звук выключен, работает прежний путь по кадрам луча.

После фикса замер даёт 229 блоков остатка m53 против расчётных 233 —
расхождение 47 мс.

АРХИВЫ.  Группы ресурсов сложены в PBA1: kid (58 атласов), pv (78),
shadow (32), bg (25), title (20), guard (10), vizier (5), skel (4). Было
232 файла на образе, стало 8 архивов плюс шесть палитр и таблицы уровней.
При цене `open` 51,4 мс против 32,6 мс за чтение 16 КБ это возвращает
секунды на каждой загрузке.

- pop_pack_arc.py печатает индексы элементов константами (ARC_<GRP>_<FILE>),
  порядок задаёт Makefile, а серии код проверяет статически;
- atlas_load разделён: atlas_attach принимает уже прочитанную страницу,
  поэтому чтение из архива не дублирует проверку магии и подготовку W0;
- имена архивов живут в pop_arc.c и адресуются номером. Строковый литерал
  лежит в rodata своего банка, и указатель на него из другого банка после
  переключения W3 показывает на чужие данные — ровно так падала загрузка
  фона (поймано брейкпоинтом на puts).

Проверено в MAME: заставка, интро и уровень 1 собираются из архивов,
Тень грузит все 32 страницы. Host-тесты: 15 наборов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 21:29:14 +03:00
snark13 d86adc54c4 Музыка: озвучена вся заставка, тайминги сведены со шкалой оригинала
Добавлены треки 54 (тема титров), 50/53/52 (три реплики сцены с
принцессой и Джафаром) — заставка звучит целиком, как в оригинале.

pop_music: два слота EMM. Реплики идут встык, паузы под загрузку нет,
поэтому следующая читается в НЕ играющий слот, а play подменяет
резидентную таблицу страниц. Своей копии таблицы слот не держит — её
хранит блок EMM. Плюс постраничная загрузка (load_begin/load_step):
страница стоит 33 мс, четверть логического кадра сцены, и подкачка
посреди анимации не видна.

Тайминги: все длительности сцен — в тиках оригинала (60 Гц), перевод в
кадры луча делает POP_T60. Пока сцены были немыми, разбег в 20 % был
незаметен; с музыкой реплика кончалась раньше картинки. Переведены
титры, история, хвост после PV и пейсинг самой PV-сцены.

Паузы, которые оригинал отмеряет концом сэмпла, теперь равны реальной
длине наших записей: конец m50 на тике 846, вход Джафара 1046, уход
2073, конец сцены 2500 (было 1959). Fade перед PV сдвинут так, чтобы
полная темнота наступала в тот же момент трека, что у SDLPoP: их fade
длиннее нашего на 123 тика, наш полосовой переход длиннее на 16.

Финальная сцена: убраны песочные часы — end_sequence_anim не трогает
hourglass_state, а reset_cutscene его обнулил. Уровень пройден, время
больше не идёт.

Проверено в MAME: цепочка 54 -> 55 -> 50 -> 53 -> 52 отыгрывается
целиком, интро доходит до демо-режима. Host-тесты: 15 наборов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 20:17:38 +03:00
snark13 ba3aca07bf Архивы ресурсов PBA1: звук одним файлом, музыка потоком; ALLOCS 6000
ЗАМЕР, из которого всё выросло (MAME, 2026-08-25): `open` стоит 51,4 мс,
а чтение 16 КБ из уже открытого файла — 32,6 мс.  То есть 61 % времени
загрузки уходит на ОТКРЫТИЕ, а не на данные, и чистая скорость чтения —
491 КБ/с, а не 260.  На диске игры 343 файла; одна только стартовая пачка
(kid+shadow+bg+sound+guard, ~142 файла) — это 7,3 секунды чистого open.

* toolchain/pop_pack_arc.py + pop_arc.c/h (банк 8) — формат PBA1:
  заголовок 512 Б, магия, count, записи по 4 байта (offset в СЕКТОРАХ —
  архив бывает больше 64 КБ; size в байтах — ресурс всегда <= 16 КБ,
  он обязан лезть в EMM-страницу).  Таблицу держит вызывающий на своём
  стеке: в W2 её класть нельзя, а стека в точке загрузки израсходовано
  75 байт из 1279 (замер там же).
* libc: bank_read_page — чтение из УЖЕ ОТКРЫТОГО файла в EMM-страницу.
  bank_load_file переписана через неё, поэтому дублирования работы с W3
  не осталось и резидент почти не вырос.
* Набор эффектов: 10 файлов -> SND\snd.arc, один open вместо десяти.
* Музыка: трек лежит ОДНИМ файлом и читается порциями по странице —
  нарезка на куски больше не нужна.  Этот же путь понадобится финалу
  `won` (1,2 МБ), который в память целиком не влезает.
* Менеджер звука приведён к оригиналу: играющая музыка участвует в
  таблицах приоритета наравне с эффектами (seg000:1672 + data.h:433).
  Почти вся музыка НЕперебиваема, поэтому поверх неё эффект не стартует —
  ровно как в SDLPoP; раньше эффект её перебивал.
* ALLOCS 3000 -> 6000 (решение пользователя): сборка +20 с, резидент
  -161 байт.  Куча 87 -> 248 Б.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 17:13:25 +03:00
snark13 34ba5f71d5 Музыка: PCM-трек через CBL, первый экран истории озвучен
Путь C из sound_plan.md вместо ранее выбранного A (ноты спикера на AY).
Причина смены: появились готовые записи DOS-версии, и строка «нужен
синтезатор на хосте», из-за которой C считался дорогим, отпала —
осталось `ffmpeg -ac 1 -ar 10937 -f u8`.  Оцифровки музыки в самой игре
нет вовсе: digisnd*.dat содержит только эффекты, музыка лежит MIDI под
Adlib, так что конвертировать ресурс игры всё равно было не из чего.

* toolchain/pop_pack_music.py — запись flac -> сырой u8 на 10 937,5 Гц
  (частота CBL, чтобы её не переключать никогда), нарезка на куски
  MUS\m<id>_<nn>.bin по 16 КБ: bank_load_file читает файл только целиком
  и только в одну страницу.  Каталог -> pop_music_tbl.h; длина трека
  хранится ПОРЦИЯМИ по 128 байт — 169 КБ в uint16 не влезает, 1350
  блоков влезает (32-бит арифметики на Z80 избегаем).
* pop_music.c (банк 9) — загрузка трека в EMM и курсор; pop_sfx_fill
  (резидент) получил третий источник: эффект важнее музыки, музыка
  важнее тишины.  Эффект музыку не сбрасывает — её курсор стоит, пока
  тот доигрывает, и она продолжается с места.
* story_1_absence звучит на первом экране истории, где была тишина.
  Грузим ПОСЛЕ сборки картинки: чтение идёт через ESTEX, и при открытом
  CBL насос не успел бы долить блок.

Проверено записью звука из MAME: корреляция огибающих с эталоном 0,836
при сдвиге 1,8 с, RMS 22,6 против 18,3 (разница — 8-битное квантование).
Эффекты двери в PV-сцене после трека звучат по-прежнему.

Бюджет: резидент +83 Б кода и +25 Б данных, запас W2 151 Б (кучи в
приложении нет, malloc не слинкован); банк 9 занят на 88 %.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 15:21:22 +03:00
snark13 47a4b084c4 Меню, статус-строка и оболочка игры: title/intro/cutscene/HoF, POP.CFG, палитры
Эта сессия (меню + текст в служебных полосах):

* Меню: рамка выделения считается от силуэта текста (SEL_PAD сверху и
  снизу), а не «на глаз»; все экраны центрируются в игровых 200 строках
  (UI_CENTER_TOP/UI_CENTER_FIELD) и в них помещаются; Enter и Space —
  равноправные клавиши действия (ui_action_down).
* Settings: убраны SHOW SPRINTER SCREEN и BACK, добавлены DEBUG BAR и
  ABOUT.  About показывает тот же текст, что стартовый Sprinter screen,
  минус строка про клавиши — общий about_text(), чтобы экраны не
  разъехались.  CONTROLS собирается таблицей и центрируется по
  фактическому числу строк.
* GAME PAUSED переехала в нижнюю статус-строку, как в оригинале
  (SDLPoP rect_bottom_text = {193,70,202,250}): это состояние программы,
  а не пункт меню.  POP_HP_Y вынесен в pop_cdraw.h — полосу делят два
  модуля.
* pop_status.c (банк 9) — текст в обеих служебных полосах.  Нижняя:
  порт display_text_bottom + таймера (QUICKSAVE/QUICKLOAD/SOUND ON/OFF,
  24 тика).  Верхняя отладочная переведена с палочек на текст
  «Level ##, Room ##, Speed: …, Sound: …, Immortal #» малым шрифтом, с
  своим форматированием чисел (без printf и без деления).
  Заявка сообщения — запись одного байта pop_status_msg: резидент W1/W2
  не растёт, весь рендер в банке.  Бюджет после правок не изменился
  (_CODE 23981, куча 267 Б).
* Цена вывода: блит глифа ~4,6 тыс. тактов независимо от размера, поэтому
  всё change-driven, отладочная строка перерисовывается ПО ПОЛЯМ, пробелы
  не блитятся вовсе, а вход в комнату заливает только игровое поле
  (pop_screen_fill_field) — борта от комнаты к комнате не меняются.
* Интро: в PV-сцене зазвучали пропавшие эффекты оригинала — закрытие
  ворот (4) и открытие двери покоев (51), из которой входит Джафар.
* docs/status_line_text.md — полная инвентаризация ВСЕХ текстов SDLPoP в
  статус-строке: геометрия, семантика text_time_total как идентификатора
  сообщения, мигание, рестарт по истечении 36/288.

Вместе с этим выкладывается накопленная работа по оболочке полной игры:
автомат состояний (pop_app), title, intro/PV и cutscene, attract-demo,
Hall of Fame, глобальный таймер, настройки и POP.CFG, модуль палитр и
fade, звуковой набор, host-тесты на новые швы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:58:36 +03:00
snark13 8ea4c32e51 Звуковые эффекты PoP: оцифровка оригинала через CBL
Набор digisnd1..3.dat приведён упаковщиком к 10 937,5 Гц (частота
железа), склеен в 8 EMM-страниц с выравниванием каждого звука на 128 —
размер блока запроса CBL, поэтому ни один блок не пересекает границу
страницы и проигрыватель не знает слова «стык».

Насос (pop_sfx.c) резидентный: его зовут из прерывания CBL, из горячих
мест физики и из play_seq.  Тишину льём свою (первый блок набора), а не
через CBL_UNDERRUN_SILENCE с его malloc — куча в резиденте W2 тесная.
Открытие CBL разведено с загрузкой (pop_sfx_start отдельно от
pop_sfx_init): пока ESTEX читает файлы, насос не успевает долить блок и
железо крутит хвост буфера — на слух скрежет.

Разведены все места play_sound() SDLPoP, у которых есть оцифровка
(id 0..23, 44..49): посадки, падение, удары о стену, зацеп, тряска и
провал плит, ворота, дверь уровня, пики, чомпер, кнопки, боёвка, меч,
зеркало, скелет, зелье.  Таблица соответствий — docs/sound_plan.md §8.
Музыкальные id остаются с нулевой длиной до фазы музыки.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 15:16:43 +03:00
snark13 24ced6058f Упаковщик звуковых эффектов + сверка наборов MSDOS и SDLPoP
Эффекты берём из MSDOS/digisnd*.dat: 28 из 31 звука совпадают побайтно с
набором SDLPoP, но три различаются не в пользу последнего — у него
sword_vs_sword короче, sword_moving другой, а spiked вообще пустой
(7 сэмплов против 5 069).

MIDI наоборот: у MSDOS формат 0 (всё слито в одну дорожку), у SDLPoP
формат 1 с 8-9 дорожками — готовое разделение голосов, если дойдём до
пути B (MIDI -> три канала AY).

Упаковщик: 31 эффект -> 10 937,5 Гц, 131 072 Б = 8 EMM-страниц.  Начало
каждого звука выровнено на 128 (блок запроса CBL), а страница кратна 128,
поэтому ни один блок не пересекает границу страницы — проигрывателю не
нужна логика стыка.
2026-08-20 12:42:19 +03:00
snark13 30bcc3459b Атлас Тени: запечённый набор вместо спрайтов стража
Оригинал кладёт спрайт дважды — прозрачным блитом в x и XOR-блиттером в
x+1; пакетный блит так не умеет, поэтому результат запечён упаковщиком.
Две половины, как и у оригинала: sk* — кадры вне боя (спрайты Кида),
sf* — кадры 150..189 (SHADOW.DAT, тоже графика Кида).  251 спрайт,
32 EMM-страницы, палитра 16 цветов в 0xA0..0xAF.

Закрывает BUG-SHADOW-SET: раньше тип 4 уходил в guard_names, и Тень в
бою дралась серым стражем.

Грабля: kid.pal заливает все 256 записей и затирает слоты Тени —
палитра вынесена в pop_shadow_pal_apply рядом с pop_bg_pal_apply.

Проверено в MAME на 6-м уровне: силуэт с контуром, как в оригинале.
2026-08-20 11:22:53 +03:00
snark13 67d62e3e8b Уровни 12 и 13: тень, Джафар, падающие плиты + разбор слоёв
Уровень 12 (тень) — порт seg002/seg006:
- check_shadow: подъём тени в комнате 15 по условию «меч подобран»,
  init_shad_12, вход падением (seq 7);
- autocontrol_shadow_level12: ждать Кида, бой, сближение, СЛИЯНИЕ;
- общий урон (ранил тень — ранил себя) и check_killed_shadow
  (убил тень — убил себя);
- таймер вспышки слияния 42 -> -1, sword_disappears при уходе из
  комнаты 18, появление плит в комнатах 2/13 после слияния.

Переход 12 -> 13 бесшовный: двери у уровня нет, он кончается фактом
попадания в комнату 23 (tbl_seamless_exit); флаг pop_seamless не даёт
сбросить HP.  Чит навигации по комнатам этот триггер придерживает —
иначе комнату 23 двенадцатого уровня не посмотреть в принципе.

Уровень 13 (Джафар): guard_notice_timer (фора после встречи),
on_guard_killed + Jaffar_exit, check_fall_flo (гряда плит сверху) и три
исключения loose-механики.  Плюс спрайты визиря VIZIER.DAT — без них он
рисовался обычным стражем; заодно цвет из данных комнаты применяется
только к обычному стражу, как в оригинале.

Падающие плиты — семь дефектов, найденных прогонами:
- MOB_MAX 4 -> 14: check_fall_flo роняет шесть плит разом, лишние
  терялись без щебня;
- одиночные сигналы посадки/провала больше не затираются в кадре;
- честный loose_fall: сбитая плита рождается в том же кадре, от места
  удара, с половинной скоростью;
- при снятии плиты метится и сосед справа (висел передний торец);
- heal и отрисовка кусков разнесены на два прохода;
- куски рисуются ПОСЛЕ фона, порядок между собой — по убыванию y
  (compare_curr_objs для пары 0x80 сортирует наоборот);
- wall_pattern убран из ceil_over_kid_tile: у оригинала узор из
  draw_tile_bottom идёт в фон, а не в передний слой.

Хост-тесты: два новых набора, t_shadow (45 проверок) и t_jaffar (44).
TK_ROOMS 8 -> 24 — спецсобытия адресуют комнаты по реальным номерам.

Осознанные расхождения (docs/impl_diff.md): нет мигания Кида спрайтами
тени при слиянии, чит навигации не запускает бесшовный переход, чит K
убивает через штатный путь смерти.

Открытым остаётся разбор слоёв: MOB-CLIP-RIGHT, MID-OVERLAY-LAYER,
GUARD-FALLOUT-VICTORY, BG-ONCE (BUGS_OPEN.md / TASKS_OPEN.md).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 18:21:52 +03:00
snark13 5b6664ffce Зеркальные кадры персонажей — готовыми файлами, а не разворотом в рантайме
Замеры в MAME (такты Z80, кадр = 430 000) решили вопрос однозначно:
  загрузка 28 атласов Кида с HDD  — 27 160 092 такта (1.26 с), 970 003 на страницу;
  разворот ОДНОЙ страницы на месте — 12 645 582 такта (0.59 с), т.е. 771 такт/байт.
Даже идеальный вариант (копия accel'ом ~0.25 кадра + asm-реверс ~2.2 кадра)
дал бы ~1.05 млн на страницу — то есть в лучшем случае сравнялся бы с чтением
готового файла.  На 34 страницы: 1.5 с загрузки против 20 с разворота.

* Упаковщики пишут второй набор: vflip_cols/vflip_page в pop_pack_kid.py ->
  kid0_v.atl…kid27_v.atl, sword_v.atl; pop_pack_guard.py -> g0_v.atl…g4_v.atl
  ТОЛЬКО для обычного стража (на уровне 9 tbl_guard_type = 0; тень рисуется
  атласами Кида, скелет/визирь с переворотом не встречаются).
* pop_vflip.c схлопнулся до таблицы «страница -> зеркальная пара»; весь
  побайтовый разворот и аллокация EMM удалены.
* pop_vflip_load_all (банк 8) грузит набор ОДНИМ вызовом и откуда угодно —
  задел под загрузку ресурсов во время интро.  Зовётся при старте и при
  смене уровня, если это POP_UPSIDE_LEVEL; чит U на других уровнях поднимает
  набор лениво (страховка в главном цикле).
* Стартовый уровень стал параметром сборки: make LEVEL=N (-DFIRST_LEVEL).

Проверено в MAME на уровне 9: переворот мгновенный, pop_flip_screen = 897 263
такта (2.1 кадра) — это сама копия экрана, загрузки в кадре больше нет.
tests-host 6/6.
2026-08-12 23:17:22 +03:00
snark13 d7d18aef77 Зелье медленного падения (перо) + цвет пузырьков по типу зелья
L7-FEATHER: тип 3 (уровень 7, комната 1) больше не пустой TODO.

- pop_map.c: pop_feather — счётчик кадров эффекта (POP_FEATHER_FRAMES = 225,
  ванильный порог do_timers seg003:0517; привязку к звуку взять неоткуда).
  fall_accel — ускорение 1 / потолок 4 (seg006:057C) вместо 3 / 33.
  proc_get_object case 3: взвести эффект + зелёная вспышка на 3 кадра.
- pop_kid.c: опкод JMP_IF_FEATHER (0xF7) больше не пропускает адрес
  безусловно — под пером прыгает по нему, то есть seqtbl уводит падение и
  удар в ветки stepfloat/bumpfloat (плавные кадры, без урона).
- roomtest.c: pop_flash_red -> pop_flash_color (жёлтый/красный/ЗЕЛЁНЫЙ);
  сброс pop_feather в pop_start_level (seg003:189).
- Цвет пузырька по ТИПУ зелья (seg008:652), чего у нас не было вовсе:
  3/4 зелёный, 5/6 СИНИЙ, остальные красный.  Mono-блиттера с параметром
  цвета в libbgi нет, поэтому цвет запекается при упаковке: pop_pack_bg.py
  кладёт те же 7 кадров ещё дважды (id 30..36 зелёные, 40..46 синие),
  pop_potion_draw выбирает набор.  Атлас 23 -> 37 спрайтов (+423 Б).
- Синее зелье «−HP» приведено к оригиналу (seg006:1892): своей вспышки не
  ставит (красный кадр даёт общий flash_if_hurt — иначе экран красился
  дважды), а на уровне зелий забирает ПОЛОВИНУ запаса HP.

Такое зелье стоит уже на пройденном уровне 2 (комната 13, тайл (1,3)),
а также ур.8 комн.2 и весь ур.15 — до сих пор оно было красным.

Тесты: phys_feather_fall_is_slow_and_harmless (обычное падение с двух рядов
разгоняется и стоит HP, под пером скорость <= 4 и HP целое); tests-host
5/5 (1727 в [phys]), size-check OK.

Расхождения с ванилью — в docs/impl_diff.md (перо ловит только Кида;
синее зелье без своей вспышки).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 12:13:29 +03:00
snark13 9ecea17ba6 Уровень 6: переход на 7-й, портал, многокомнатное падение плиты
Всё проверено пользователем в MAME.

1. Переход 6 -> 7 падением.  leave_room (seg002:0504) для «вниз» на уровне 6
   из комнаты 1 даёт особый результат −2, главный цикл (seg000:0893) читает
   его как ++next_level; вход на 7-м (set_start_pos, seg003:0196) ставит Кида
   в комнату 17 и сразу переводит экран на комнату под ней.  Проверка ОБЯЗАНА
   идти раньше связи вниз: у комнаты 1 сосед снизу есть (шахта, комната 3).

2. BUG-BALCONY-RIGHT: правая половина портала не рисовалась.  id 12 стоял в
   FORE_ENV_IDS (в tile_table он fore_id ЗЕЛЬЯ), но зелье рисуется из
   chtab_1, а в chtab_6 под этим номером — правая часть арки балкона 32x62,
   идущая в backtable.  Спрайт уезжал в fore-атлас, env-страница получала
   дырку (idx 12: w=0 h=0).  Найдено трассой POP_TRACE_BT + чтением каталога
   .atl; дифф комнаты с оригиналом 1151 -> 375.

3. BUG-BELOWROW-WALL: жёлтые треугольники в шахте падения.  load_rowbelow
   (seg008:368) при отсутствующей комнате снизу подставляет tiles_0_empty
   колонкам 1..9 и tiles_20_wall только левому краю; у нас стеной забивались
   все 11 позиций.

4. BUG-MOB-MULTIROOM: честный mob_down_a_row (seg007:1387) — кусок переходит
   в комнату снизу и летит дальше.  Плита (1,7) комнаты 6 пролетает шахту
   комнаты 7 и жмёт кнопку (2,7) комнаты 11, открывая ворота комнаты 18.

5. BUG-MOB-STALE-PAGE: чистка следа куска стояла под гейтом «в отрисованной
   комнате» — улетевший вниз оставлял себя на одной из страниц навсегда.

6. BUG-CHAR-STALE-PAGE: тень застывала на двух страницах в РАЗНЫХ позах.
   Прямоугольник и valid пишутся внутри `if (w && h)`, а снимок состояния
   брался безусловно — при нулевом спрайте слот залипал «тихим».  Остаток
   (почему спрайт нулевой) заведён как SPRITE-ZERO-W0 в BUGS_OPEN.

NEXT_SESSION переписан: следующая задача — зацеп за край пола в свободном
падении (вход на уровень 7).  tests-host 5/5, size-check чист.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:01:58 +03:00
snark13 740cc6d652 Арка у ворот: спецслучай «Lattice + door A» (seg008:622)
Уровень 5, комната 12, тайл (0,9): чёрная дыра вместо арки.  У doortop нет
своей базы (base_id 0), и рядом с lattice_down оригинал рисует пару одним
спрайтом 6, опущенным на 3 px.  Ветки у нас не было.

Правка нужна В ДВУХ местах: pop_room.c (сама отрисовка) и
toolchain/render_room.py — pop_pack_bg.py собирает атлас по обходу
render_room, без этого спрайт 6 не попал бы в pop_env*.atl.

Найдено сверкой с оригиналом: prince megahit 5 --screenshot-level даёт карту
уровня, снимок MAME совмещается с клеткой комнаты, разница по яркости
показала расхождение ровно в (0,9) — 1892 пикселя до фикса, 995 после
(остаток = Кид, факел, статус-полоса).  Что рисует оригинал в тайле,
показала трасса POP_TRACE_BT в add_backtable: id=6 32x63 рядом с id=85 32x4.
Методика записана в BUGS_CLOSED.md и memory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 21:46:38 +03:00
snark13 1b2111f2a0 L4-MIRROR шаги 1-2: зеркало в атласе + постановка тайла по открытию двери
Задача заведена активной на доске по решению пользователя (palace был
отложен 2026-08-04, но уровень 4 уже гоняется в MAME и зеркало —
единственное, что мешает пройти его сюжетно).  Механика целиком сверена по
SDLPoP, таблица соответствий в TASKS_OPEN.

Шаг 1 — АТЛАС.  tile_table[0x0D] = база 75, фронт 77 (наша таблица
совпадает с SDLPoP байт в байт).  Тайла 13 НЕТ НИ В ОДНОМ уровне
статически: перебор всех 15 res200N.bin даёт ноль попаданий (санити
разбора: ур.1 без чомпера, ур.3 с 18, ур.4 с паласными 25..29).  Значит
render_room эти id не увидит и на месте зеркала был бы чёрный провал —
грабли memory pop_atlas_dynamic_ids.  Добавлены MIRROR_ENV_IDS = {75,77} в
pop_pack_bg.py, 77 ещё и в FORE_ENV_IDS.  Оба набора переупакованы:
fore 17 -> 18 спрайтов, все страницы EMM в пределах 16 КБ.

Шаг 2 — ПОСТАНОВКА.  place_mirror() в pop_trob.c по переходу
pop_leveldoor_open 0/2 -> 1 (условие оригинала, seg007:0457 — иначе тайл
ставился бы заново каждый кадр открытой двери).  Пишет тайл 13 в комнату 4,
колонку 4, ряд 0; если комната уже на экране — POP_RD_FLOOR на обе
страницы.  Банк 6 3450 -> 3518.

НЕ ПРОВЕРЕНО В MAME: нужно нажать плиту выхода на уровне 4 и дойти до
комнаты 4.  Отдельный вопрос к проверке — не устареет ли g_fg коллизии,
если игрок окажется в комнате 4 в момент постановки.

Дальше по плану: 4 (прыжок сквозь + рождение тени), 5 (клип тени),
3 (отражение — косметика, самое дорогое).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 21:26:58 +03:00
snark13 dbf9166836 Цвета 6 и 12 базовой палитры: игра правит их на старте, мы брали VGA-значения
Симптом (наблюдение пользователя): кирпичи дворцовой кладки правильного
цвета, а швы между ними — нет.

Корень шире, чем швы.  init_game_main (seg000:164) подменяет ДВЕ записи
16-цветной палитры сразу после загрузки:

	// (blood, hurt flash) #E00030 = red
	set_pal(12, 0x38, 0x00, 0x0C);
	// (palace wall pattern) #C09850 = light brown
	set_pal( 6, 0x30, 0x26, 0x14);

Подтверждено дампом палитры живого SDLPoP: PAL[6] = 48,38,20,
PAL[12] = 56,0,12.  У нас в таблице VGA16 стояли стандартные VGA-цвета
(42,21,0) и (63,21,21).  Цветом 6 рисуются швы дворцовой кладки
(blitters_46h_mono_6), цветом 12 — кровь чомпера и вспышка урона, так что
промах был не только в стенах.

Заодно исправлена вспышка урона в roomtest.c: было flash_bg(255,85,85)
(стандартный brightred), стало (224,0,48).

Проверка: ряд стен уровня 4 теперь совпадает с эталоном SDLPoP по всем
восьми цветам и их количествам один в один (5333/4013/3110/2065/1785/1372/
1075/447).  Остаточное различие значений — только наше масштабирование
6->8 бит: (v*255)/63 против v*4 у SDLPoP, то есть (194,153,80) против
(192,152,80).

ПОПРАВКА к f107711: там записано, будто рисунок кладки не совпадает с
оригиналом из-за замены PRNG.  Это неверно — POP_PRANDOM_EXACT по умолчанию
1, работает ассемблерный LCG оригинала, и совпадение счётчиков цветов это
подтверждает.  Расхождения по PRNG нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:32:19 +03:00
snark13 f10771194b Шаг C: дворцовая кладка стен (wall_pattern, паласная ветка)
Во дворце тело стены — не кирпичи-спрайты, а шесть СПЛОШНЫХ ЗАЛИВОК плюс
пять моно-разделителей поверх (seg008:1946).  Порт целиком:

- gen_palace_wall_colors (seg000:1942): 3 ряда × 4 подряда × 11 колонок = 132
  цвета, сид = номер комнаты, подряды 1/3 из 0x61..0x64, подряды 0/2 из
  0x66..0x69, соседние по горизонтали не повторяются.  Одиннадцать колонок,
  а не десять: заливки 3 и 5 берут цвет СЛЕДУЮЩЕЙ колонки.  Пересчёт на
  смене комнаты — там же, где сбрасывается кэш кладки (wall_pattern_reset).
  Таблица не static: writable-данные банка живут в _DATA/W2.
- Геометрия заливок дословно из add_wipetable(layer, left, bottom, height,
  width): прямоугольник = x..x+width-1, (bottom-height+1)..bottom.
- Пять prandom(2) на тайл кэшируются так же, как подземельные решения
  (wp_a/wp_b переиспользуются — наборы в одной комнате не сосуществуют).
  Сохранён квирк порядка: при which_part == 0 разыгрывается ОДНО значение, и
  нижний разделитель берёт ПЕРВОЕ из серии, а не пятое.
- Заливки режутся по окну fore-клипа: иначе легли бы поверх областей, которые
  в этом кадре никто не восстанавливает.  Вне fore-прохода — pop_cd_touch,
  потому что bar идёт мимо pop_blit_b.
- wall_fram_bottom / wall_fram_main во дворце НЕ рисуются (seg008:576, 711) —
  и в горячей половине слоя (pop_bg.c), и в холодной (pop_room.c).
- Упаковщик: дворцовые wall-id 3..17 пакуются силуэтом в цвете 6 общей
  16-цветной палитры (blitters_46h_mono_6).  В подземелье те же id —
  обычные кирпичи, поэтому mono только у паласного набора.

ИЗВЕСТНОЕ РАСХОЖДЕНИЕ: рисунок цветов не совпадает с SDLPoP попиксельно,
потому что наш prandom — 16-битный xorshift, а не LCG оригинала (замена
сделана раньше по бюджету кадра, prng_alternatives.md).  Совпадают
геометрия, диапазоны цветов и правило «соседние не повторяются».

Проверено в MAME: уровень 4 — песочный мрамор с разделителями, структурно
как эталон SDLPoP; уровень 1 не изменился.  tests-host 5/5.

Остаётся расхождение по двери уровня (мы заполняем проём плетёнкой целиком,
оригинал рисует несколько кусков лестницы) — отдельным шагом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:19:42 +03:00
snark13 0941ef1d90 Шаг D: паласные ветки отрисовки + потерянный верх ворот
Порт семи мест seg008, расходящихся по tbl_level_type, плюс общая дыра
порта, которая на дворце стала видна.

Паласные ветки (все — «в подземелье этого нет»):
- doortop_fram_top / doortop_fram_bot (seg008:413, 506): декоративная панель
  над воротами.  У шва она и есть тот «ковёр», которого не хватало.
- stripe_id соседа слева (seg008:486): орнаментная лента под окнами.  Она
  непрерывная, потому что stripe_id = 145 у пола, кнопок, зелья, loose,
  чомпера и меча; без неё лента шла кусками (только blueline).
- полоска на стене id 84 (seg008:510), при (modifier & 0x80) == 0.
- blueline_fram3: условие `num == !!level_type` — в подземелье пропускается
  num==0, в паласе num==1 (seg008:501).
- левая половина кнопки-opener без пола (id 148) — только подземелье
  (seg008:628).
- склянка зелья: id += 2 во дворце (seg008:747).
- remove_loose возвращает ТИП УРОВНЯ, и он ложится модификатором пустой
  клетки от упавшей плиты (seg007:846/1083).

Потерянный вывод (НЕ паласное расхождение, просто заметили здесь):
draw_tile_anim_topright (seg008:0568) не был портирован вовсе — верх ворот,
который рисует тайл НАД ними: маска 68 (mono, чёрным) + door_fram_top
[(modifier>>2) % 8] = 60..67.  Ids 60..68 в атлас не паковались.  Симптом —
чёрный клин над воротами; нашёлся сравнением с эталоном SDLPoP
(--screenshot) и трассой add_backtable.

Флаг тайлсета pop_palace вынесен в резидент (pop_tile.c): по нему расходятся
ветки в банке 7 (полная отрисовка), банке 2 (fore-проход) и банке 3
(модификатор пустой клетки) — читается напрямую, без трамплина.

Известное расхождение: модификатора ряда СНИЗУ у нас нет (pop_t_below —
только fg), поэтому паласная панель над воротами в комнате снизу не
рисуется.  Помечено в коде.

tests-host: 5/5 (добавлен include-путь до pop_bg_atlas.h и стаб pop_palace).
Проверено в MAME: уровень 4 совпадает с эталоном SDLPoP в рядах 0-1
попиксельно (кроме фазы пламени); уровень 1 не изменился.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:04:49 +03:00
snark13 97929b55d3 Уровень 4: второй тайлсет (дворец) — ассеты и переключение (шаги A и B)
Порт tbl_envir_ki[tbl_level_type[level]] (seg000:1108): оригинал под один и
тот же набор id грузит РАЗНЫЙ .DAT — VDUNGEON или VPALACE.

Упаковщик (toolchain/pop_pack_bg.py):
- аргумент набора: `pop_pack_bg.py dungeon|palace`.  Каскад каталогов —
  сначала свой набор, потом чужой фолбэком (в распакованном data/ res230/
  231/348 есть только в VDUNGEON, два десятка — только в VPALACE).
- ОБА набора пакуются по одному объединению id, поэтому раскладка
  id -> (страница, idx) общая и заголовок один: коду достаточно подменить
  имена файлов.
- PALACE_ENV_IDS: 78/80/82 (doortop_fram_bot), 81/83 (doortop_fram_top),
  84 (полоска стены), 145 (stripe_id) — их рисует только палас, render_room
  про них не знает.
- ENV_SHIFT 5 -> 4: с паласными кусками страница 2 переваливала за 16 КБ
  (16 996).  Цена — 10 страниц EMM на набор вместо 5, при 215 свободных.
- *tile.pal: 32 записи (env 0x50..0x5F + wall 0x60..0x6F) на набор.  Полная
  kid.pal не трогается — Кид, страж, меч и зелья в других слотах.

Движок:
- pop_level_type() (tbl_level_type, SDLPoP data.h:840): дворцовые уровни
  4, 5, 6, 10, 11, 14.  Живёт в pop_level.c, потому что по типу расходятся
  не только атласы, но и ветки отрисовки seg008, кладка стены и модификатор
  пустой клетки от упавшей плиты (remove_loose, seg007:0EB8).
- pop_bg_load(set): no-op при том же наборе, при смене выгружает старый
  (иначе текут 12 EMM-страниц) и правит 32 записи палитры в ОБЕ страницы
  дабл-буфера.  Зовётся на старте и на границе уровня, не в кадре.
- Путь к атласу склеивается на месте (bg_path): двадцать строк-имён в банке
  — лишние полкилобайта.

Проверено в MAME: уровень 1 (подземелье) рисуется как прежде; уровень 4
(-DFIRST_LEVEL=4) — дворцовые арки, окна, пол, решётчатая дверь уровня,
Кид не перекрашен.  Стены пока чёрные: паласный wall_pattern — шаг C.

tests-host: 5/5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 11:28:45 +03:00
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
snark13 d438a1d3da PoP roomtest: клинок в атласе целиком + раздельный heal накладных спрайтов
Меч в оригинале ОДИН на всех: и Кид, и страж рисуют клинок из chtab_0
(add_sword_to_objtable, seg006:1798).  Раньше sword.atl содержал только
кадры подъёма/ножен (sword_tbl 35..42), поэтому у стража меча не было
видно вовсе.

- pop_pack_kid.py пакует chtab_0 целиком (id 0..33, 5168 Б), индекс в
  атласе = id;
- pop_extract_kid_data.py вытаскивает sword_tbl (53 строки) в kid_data.h
  макро-инициализаторами — таблица ложится в один TU, а не в каждый;
- pop_sword_draw (pop_kid.c) — общая точка отрисовки клинка с полным
  условием оригинала (кадры 229..237 ИЛИ меч обнажён ИЛИ живой страж);
  зовут и kid_draw, и pop_guard_draw.

Раздельный heal накладных спрайтов.  Клинок и брызги урона раньше
объединялись в один прямоугольник с персонажем, а объединение почти вдвое
больше суммы двух (клинок уходит вперёд-вверх) — heal же стоит ровно по
площади.  Теперь у накладных свой прямоугольник и свой gfx_heal, а
объединение осталось ТОЛЬКО для окна fore-клипа: там это 4 сравнения без
рисования, но покрыть клинок обязано, иначе он полезет поверх столба.

Отладка: DEBUG_SWORD_ROOM — Киду выдаётся меч при входе в комнату 3
(там страж), чтобы не бегать за ним в комнату 15.

Бюджет (225 кадров, комната 3): 415 284 – 416 370 = 0.966–0.968 кадра.
Против 384 168 – 384 636 до этого шага, то есть +31 700.  Разложение по
фазам (медианы): process_trobs 83 190, pop_guard_draw 92 483 (спрайт
28 763 + клинок 23 820 + fore_over_char 39 906), kid_draw 59 089, fore
поверх Кида + борта 47 483, heal 40 068, логика 76 338, ввод 13 050.
Видно, что клинок 21x8 стоит почти как спрайт стража — это фиксированные
накладные расходы клипающего блита, а не пиксели; лечится noclip-путём
для column-major (см. gfx_blit_noclip_fast).  Отдельным шагом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 11:40:08 +03:00
snark13 af5f0a4638 PoP roomtest: отрисовка стража в резидент W3 + fore-окклюзия + off-by-one спрайта
Разгрузка W1/W2 перед ИИ стражей (вариант 2 из двух обсуждённых).

1. pop_guard.c разделён по окнам: состояние/логика (Guard, HP, enter,
   kill, load_frame) остаются в W1/W2 — их обязан видеть банк guards.c;
   ОТРИСОВКА уехала в новый pop_gdraw.c, собираемый как --w3 (резидент).
   Правило границы: резидент = только то, что рисует и зовётся
   исключительно из главного цикла.  Кадр стража стал глобальным
   (pop_gframe): заполняет логика, читает резидент.

2. Страж не окклюдировался передними гранями тайлов — рисовался поверх
   столба.  В оригинале любой Char это запись midtable, а foretable
   рисуется после всех midtable (draw_tile_fore, seg008:690), т.е. столб
   перекрывает всех одинаково.  Футпринт персонажа выделен из
   pop_fore_over_kid в char_footprint(), поверх него добавлен
   pop_fore_over_char() — слой fore + полоса потолка, без оверлеев поз
   виса/полёта/подъёма (у стража их нет; появятся — портируем
   redraw_at_char2 общим кодом, а не догадками).

3. Упаковщик стража: тот же off-by-one, что уже ловили у Kid.
   load_chtab_from_file(id_chtab_5_guard, 750) даёт images[0] = res751,
   а рисование индексирует images[frame.image] — значит image=N это
   res(751+N), а не res(750+N).  Из-за сдвига frame_166_stand_inactive
   рисовался как res767 (выпад) вместо res768 (стойка).

Проверено в MAME: страж в комнатах 3 и 21 стоит в правильной позе;
окклюзия подтверждена патчем Guard.x в живой сессии — при заходе за
столб спрайт корректно срезается его передней гранью.

Память: _CODE 26 149 -> 25 703, куча W2 805 -> 1245 Б, резидент W3
11 656 -> 12 819 (свободно 3565 Б), банк 1 236/16384 Б.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 23:14:21 +03:00
snark13 3dbad6120c PoP roomtest: страж — спрайты, таблица кадров и появление в комнате
Шаги 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>
2026-07-29 21:57:38 +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 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 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 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 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 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 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 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 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 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