e5179af9d84e78c2d63f97e5a6808fce9d1527bf
47 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
f4b4852d51 |
QuickSave F6/F9 в roomtest; bank_load_file/bank_save_file/gfx_w0_page_prepare; sprinter-cc: авто n_banks
roomtest: - QuickSave/QuickLoad (F6/F9): снапшот 'POPQ' v3 в POP.SAV/POP.BAK на HDD, транзакционная запись (POP.NEW -> rename, откат при ошибке), XOR-контрольная сумма payload'а; сериализация всех игровых переменных через W0-примитивы pop_qs_*; pop_qsave_process() на границе кадра вне Char-окон - pop_qsave_restore_room(): полная перезагрузка комнаты после загрузки (карта/края/швы, сброс bake-кэша, перерисовка обеих страниц, инвалидация кэшей спрайтов и HP) - сериализаторы в pop_map/pop_loose_mob/pop_trob/pop_guard_ai (+ восстановление инвариантов: mobs_live, trob_drawn, redraw) - immortal-чит 2 уровня: уровень 2 поглощает только малый урон Kid'а libc/libbgi: - bank_load_file()/bank_save_file() — резидентное файловое I/O в банк, без правила W3 (путь читается до переключения страницы) - gfx_w0_page_prepare(page) — подготовка W0-окна (IRQ/NMI-стабы) одной функцией; atlas_load.c и roomtest переведены на новые примитивы; ручные ISR-стабы удалены sprinter-cc / сборка: - --bank N=FILE.c: автогенерация n_banks (_n_banks_auto.c), ручные const n_banks удалены из тестов - roomtest/app.mk: ресурсы через stamp-файлы (.resource-stamps/) — один запуск упаковщика на группу вместо N под -B; HDD_PACK_ARGS |
||
|
|
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> |
||
|
|
a6e39070af |
Фиксированный логический кадр по лучу + режимы FASTEST/FAST/NORMAL
Период стал max(n, ceil(W)) вместо ceil(W)+2: три gfx_wait_vsync после работы отсчитывались от её КОНЦА, поэтому бюджет кадра был один растр. Теперь ждём от якоря начала кадра, и при n=3 бюджет 1 290 240 тактов. Счёт кадров — программный, по биту 5 порта 0xFE (положение луча), а не по кадровым прерываниям: те теряются в DI-окнах акселератора фазозависимо (замер: 0..2,8 %, на полной перерисовке три подряд). Условие точности одно — зазор между выборками меньше 64 512 тактов; точки выборки расставлены по замеру, а не на глаз. Режимы (pop_pace.h), клавиша P по кругу, дефолт FASTEST. Условие боя взято у оригинала буквально (SDLPoP seg003.c:363): Kid.sword == SWORD_2_DRAWN, а не «идёт бой». Проверено в MAME на 11/15: счётчик без недосчёта на 270 кадров, период ровно 3 растра на 302 логических кадрах (ни длиннее 3,1, ни короче 2,9), NORMAL даёт ровно 4, с вынутым мечом — ровно 5. |
||
|
|
d0de6dedf4 |
HEAL-WIDTH: heal чомпера ровно 32x60 — его точный след
Замечание пользователя: весь чомпер помещается в свой тайл, значит его
heal максимум 32x60. Проверено по каталогу атласа и подтвердилось:
нижняя челюсть 101/102 = 32x60 низом на dmy = 63*row+62, занимает
63*row+3 .. +62;
верхние челюсти дают ТОТ ЖЕ верх — подъём 0x25 при высоте 23, 0x2F при
13 и 0x32 при 10 все три упираются в 63*row+3;
кровь 114..118 шириной 6 рисуется на x+8, то есть внутри 32.
Было 64 «на всю высоту тайла» (плюс лишняя строка запаса от прошлой
правки) — стало ровно 60 от +3.
Заодно зафиксирован разбор структуры перерисовки чомпера: ОДИН heal на
тайл и ДВА блита (низ и верх). Объединить блиты нельзя — при раскрытых
позах нижняя часть маленькая (32x30, 32x21, 32x17) и между ней и верхней
челюстью разрыв: например, при позе 2 низ занимает +33..+62, верх
+3..+25, а строки +26..+32 пустые.
Проверено в MAME: чомпер рисуется чисто, хвостов от прежнего кадра нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f62989e358 |
HEAL-WIDTH: ширина heal'ов по фактическому следу из атласа
Задача была помечена обязательной. Габариты сняты из каталогов .atl, а
не «по клеткам на глаз»:
плита 41/69/70 = 32x13-14 43/73/74 = 32x3 42/71/72 = 26x15-16
чомпер 101/102 = 32x60 111 = 27x23 113 = 23x10
Отсюда два сужения:
pop_loose_shake_draw ширина 64 -> 58 (свой тайл 32 + правая грань 26,
во дворце 25)
pop_chomp_redraw высота 64 -> 61 (след 63*row+3..62: верх самого
высокого bot-кадра и низ на dmy;
верхняя челюсть при подъёме 0x32
и высоте 10 даёт ровно +3)
Замер 11/15: медиана не сдвинулась (437 484 — плит в комнате нет),
максимум 550 776 -> 549 816, то есть эффект только в кадрах перерисовки
чомпера и он мал, как и предсказал пользователь. Основной выигрыш от
сужения плиты (9,4 % площади) ждёт сцены 13/23 и требует отдельного
прогона на сборке LEVEL=13.
Пики не трогал: их таблицы кадров (POP_SPIKES_FRAM_LEFT/RIGHT) я по
атласу не разбирал, а сужать heal по догадке — прямой путь к
недочищенному хвосту.
Проверено в MAME: чомпер и факелы рисуются чисто, хвостов нет; хост-тесты
зелёные.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
de68eb5cec |
P1: чомпер перерисовывался неизменной позой — минус 110 802 такта
Позиция заводилась с НЕПОЛНЫМ диагнозом. Я приписал 190 260 тактов
пометке от факела (пламя лежит в ячейке правого соседа, то есть поверх
чомпера, и запекается каждый кадр). Правка по этому диагнозу не дала
ничего: 769 002 против 768 684.
Зонд pop_dbg_kind показал факт: все 312 перерисовок прогона — вид
POP_RD_CHOMP, полная, и ни одной от факела. Собственная пометка чомпера
просто перебивала пометку соседа.
Настоящая причина нашлась сверкой с animate_chomper (seg007:0448).
Оригинал заканчивает её так:
if ((curr_modifier & 0x7F) < 6) redraw_at_trob();
то есть перерисовывает чомпер только пока фаза меньше 6 — пять кадров из
пятнадцати. Это не оптимизация оригинала, а следствие таблицы поз:
chomper_fram1 = {3,2,0,1,4,3,3}, и с фазы 5 до конца круга поза одна и та
же. Мы метили тайл каждый кадр, пока trob жив, а живёт он всё время, пока
Кид в том же ряду — то есть платили полный draw_tile плюс heal 32x64 за
неизменную картинку в двух третях кадров.
Сделано:
1. пометка только при фазе < 6; на фазе 5 — обе страницы дабл-буфера
(она последняя рисуемая, её поза обязана лечь на обе; вторую страницу
пометка догоняет в кадре фазы 6, где поза та же — CHOMP_FRAM1[6] == 3);
2. новый вид POP_RD_CHOMP_ANIM -> pop_chomp_anim_draw: три блита графики
чомпера поверх свежего пламени, без heal и без остальных слоёв — порт
ветки redraw_frames_anim (seg008:0211), где оригинал делает ровно
draw_tile_anim_topright / draw_tile_anim_right / draw_tile_anim и
никакого wipe;
3. приоритет полной перерисовки над anim в pop_set_redraw: у оригинала
это два независимых счётчика и full побеждает, а у нас вид один на
тайл, и без проверки исход решал бы порядок trob'ов в списке.
Обе половины работают — замер даёт 40 % полных перерисовок и 60 % лёгких.
Работа 768 684 -> 657 882 (медиана), зелёная 294 510 -> 183 420. В 40 %
кадров цена прежняя: там поза реально меняется, это честная работа.
Циан не сдвинулся ни на такт, то есть надежда P3 (Кид перестанет будиться
каждый кадр) пока не оправдалась — метки продолжают его будить.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
6d7c1c8b6b |
P5: гейты холостого хода в pop_loose_tick — минус 33 840 тактов на кадре
Замер 11/15 показал, что loose-механика берёт 28 872 такта в комнате, где не анимируется ни одна плита и не летит ни один кусок. Раскладка зондами m9..m12: два цикла по тайлам 9 852, обход 14 слотов mob 12 090, поиск куска над головой Кида 5 868 (там ещё и банковый трамплин). Два гейта: loose_any (статик pop_map.c) — «идёт ли анимация плит». Ставят пять мест записи ненулевой фазы: make_loose_fall, ветка потолка в check_press, do_knock для обоих рядов и восстановление фазы из room_modif при входе в комнату. Снимает его сам цикл, по факту прохода, в котором не осталось ни одной живой фазы. pop_mob_busy (резидент pop_state.c) — «занят ли слот падающего куска» (active или дочистка clean). Ставит mob_alloc, снимает обход по факту пустой таблицы. В резиденте, а не в pop_room.c, потому что читает его pop_map из банка 3, а писучие статики банкового модуля наружу не видны. Гейт отвечает не на «есть ли в комнате плиты», а на «идёт ли анимация»: у лежащей плиты-потолка фаза нулевая, и крутить нечего (вопрос пользователя). Асимметрия намеренная — ложная единица стоит одного холостого прохода, ложный ноль стоит застывшей навсегда плиты, поэтому взвод стоит рядом с КАЖДОЙ записью, а снятие только по факту пустого прохода. Стало: 132 / 996 / 546, вся функция 28 872 -> 2 760. На кадре работа 801 768 -> 767 928. Ожидание по реестру было -28 000. Покрытие: новый phys_loose_gate_survives_room_change на пятое место взвода (фаза восстановлена входом в комнату) — единственное, которое не прогонял ни один тест, и дающее самый тихий отказ. Мутационная проверка: со снятым взводом тест падает (фаза 3 вместо 4). Заодно отладочный старт сразу в целевую комнату: make ROOM=15 POS=2 (дефолт), roomtest стартует в 11/15 с Кидом в (0,2). kid_init ставит x = x_bump[col] + TILE_SIZEX, а это левая граница СЛЕДУЮЩЕЙ колонки — с неё физика относила Кида в тайл чомпера, и он погибал на старте (найдено пользователем). Сдвиг внутрь на 2: колонку определяет весовая точка кадра, поэтому число снято замером, а не выведено геометрией. План работ между сессиями — docs/perf_registry.md §4: очередь позиций со статусами, текущий бюджет сцены, рецепт её воспроизведения и метод замера. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4db60c750f |
Вариант B: кусок с завёрнутым рядом рисуется под всем (корзина 30)
Порт правила оригинала, разобранного в
|
||
|
|
272cf8f195 |
Разбор порядка отрисовки падающей плиты: найден корень, выбран вариант B
СИМПТОМ (пользователь, ур.11 к.14). Кусок, отвалившийся от плиты нижнего ряда, рисуется ПОВЕРХ соседней трясущейся плиты; при этом передний торец соседа лежит поверх куска — порядок противоречив в разных частях перекрытия. КОРЕНЬ. draw_mob (seg007:13E5) считает ряд объекта как y_to_row_mod4(y) = (y+60)/63 % 4 - 1. Из-за % 4 ряд 3 (кусок ушёл ниже комнаты) и ряд −1 (кусок у потолка) дают ОДНО значение −1. Оригинал прогоняет его через get_tilepos -> get_tilepos_nominus и получает тайл 30, а объекты с тайлом 30 рисуются в redraw_needed_tiles ПЕРВЫМИ, до всего обхода тайлов. Мы же передаём сырой −1 в мид-оверлей как «тайл объекта», и гейт `row > pop_bg_obj_row` читает его как «объект в последнем ряду обхода», то есть «объект поверх всего», и отбрасывает возврат соседа. Один и тот же −1 у нас значит «сверху», у оригинала — «снизу». Торец при этом виден потому, что приходит из ДРУГОГО слоя: draw_loose кладёт loose_fram_bottom в backtable и foretable, минуя ptr_add_table, — тело плиты обязан вернуть мид-оверлей, а его и выключает гейт. ЧТО ПРОВЕРЕНО ЗАМЕРОМ (журнал решений в pop_dbg_ovl/pop_dbg_pass, зонды ВРЕМЕННЫЕ и будут сняты): - на застывшем кадре: слот draw_y=194, r=−1, rt=2, оверлей позван, внутри отбрасывается гейтом; - пропуск оверлея на последнем кадре полёта ЗАКОНЕН: габарит куска уже ниже габарита тайла, перекрывать нечего; - в 13/23 кусок перекрывают 2-4 тайла (накопленно за полёт), включая СВОЮ клетку, — то есть «сосед справа» покрытие не исчерпывает; - перерисовок тайлов за кадр в 13/23: максимум 6, в покое 0. ТУПИКИ, чтобы не ходить второй раз. Клип объекта тут ни при чём: add_mob_to_objtable ставит clip.right = 40, но клип применяется только при chtab_flip_clip[chtab_id], а для chtab_6_environment там 0 — поле игнорируется и в оригинале. Пункт MOB-CLIP-RIGHT закрывается как несуществующий. Добавлять торец плиты в мид-оверлей тоже не надо: в foretable он уже кладётся из fore_tile. ВЫБРАН ВАРИАНТ B: классифицировать ряд объекта (вне 0..2 = корзина 30), отдавать оверлею ориентир «раньше любого тайла» и расширить набор перекрываемых тайлов на СВОЮ клетку. Переносить проход отрисовки не нужно — heal при этом не участвует, работа та же (оверлей = два блита в окне клипа), разница с узким вариантом A всего один-два оверлея на кусок. Заодно записана ОБЯЗАТЕЛЬНАЯ задача HEAL-WIDTH: ширины точечных heal'ов взяты по клеткам (64), а фактический след плиты — 58/57 (замерено по атласам: верх 32, правая грань 26 в подземелье и 25 во дворце). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ed5615a95a |
Плиты нижнего ряда пропадали без кадров падения: draw_mob рисует и соседей
Уровень 11 комната 14: плита ряда 2 уходит в комнату снизу на первом же move_loose (спавн y=191 при границе ряда 188), а у нас кусок в чужой комнате не рисовался вовсе — плита исчезала мгновенно. Оригинал (seg007:13E5) рисует его ещё три кадра, выглядывающим из нижней кромки (+192), и симметрично из комнаты сверху (−189). У куска появилась экранная координата draw_y; по ней идут отрисовка, heal, порядок относительно Кида, пометки соседа и оверлей, отбор в проходе — по ней же, а не по комнате. Ссылки вверх/вниз кэшируются. Цена (13/23): зелёная 419 562 -> 427 242, работа 880 272 -> 888 984. Первый вариант стоил втрое дороже (445 248) из-за безусловной пометки по прошлой нарисованной позиции — у куска из соседней комнаты она в 192 пикселях, и объединение растягивалось на весь экран; гейт вернул 18 000. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
980d48c168 |
Пики оставались навсегда: запечка фона консервировала выдвинутый кадр
Уровень 7, комната 19. Кнопка в (0,5) при нажатии зовёт pop_floor_bake, а тот перерисовывает и правого соседа — пики (0,6) — в банке, который пишет в ОЗУ-копию. Кадр брался живой, и выдвинутая пика оставалась в фоне: дальше heal возвращал её каждый кадр. В (0,7) чисто, потому что окно клипа запечки кончается на x=219, а колонка 7 начинается с 224. Флаг pop_t_bake_rest («в фон кладём покой») для этого и был, но читал его только pop_loose_frame. Кадр пик считался по месту в ЧЕТЫРЁХ слоях. Добавлен pop_spike_frame — порт get_spike_frame (SDLPoP seg008:08A0), которого у нас не было, — и все четыре слоя переведены на него; заодно позу покоя в запечке стал отдавать pop_chomp_pose. Одна точка лечит все пять путей запечки, включая вход в комнату. Резидент +28 Б, скорость не затронута. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
89b603ae04 |
Дворцовый портал: Кид скрывался за кромкой раньше времени
Уровень 4: в анимации ухода на следующий уровень контур Кида обрезался не
правой гранью портала, а раньше — брался клип подземного проёма, а
дворцовый шире.
draw_leveldoor считал кромку как xh*8 + 48, без дворцовой поправки. В
оригинале строкой ниже стоит (seg008:1429):
if (custom->tbl_level_type[current_level]) leveldoor_right += 8;
Значение читает clip_char как правую границу клипа персонажа — отсюда
ранняя обрезка. Расхождение было осознанным и отложенным: в коде стоял
комментарий «+8 у palace-уровней — на уровне 1 не применяется», дворцовых
уровней тогда в порту не было. tbl_level_type[4] = 1, там и проявилось.
pop_palace выставляет pop_bg_load из того же tbl_level_type, что читает
оригинал, так что эквивалент дословный.
Попутно найдено и НЕ починено (заведено отдельным багом
LEVELDOOR-STARTROOM-WIPE): в той же функции оригинал в СТАРТОВОЙ комнате
кладёт затирающий прямоугольник вместо лестницы, со своей дворцовой/
подземной разницей 48/39 и сдвигом 2 px, а мы рисуем марш 144 безусловно.
Видно только при приподнятой створке входной двери, поэтому на обходах
уровней 1-4 не попалось.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
dd40c24e7a |
Чёрные бары поверх стража: заливка запечки уезжала за правый край экрана
Найдено пользователем (2026-08-17, уровень 2 комната 4): после падения плит
(1,7) и (1,8) поверх стража в (1,0) появляются два чёрных бара, большой и
маленький, и МЕРЦАЮТ — то есть живут только на одной странице дабл-буфера.
Пока плиты не упали, баров нет.
Диагноз пользователя оказался верным дважды — и в причине, и в механизме.
ПРИЧИНА. Падение плиты (1,8) помечает соседа (1,9), и pop_floor_bake заливает
там прямоугольник ШИРИНОЙ 60 от x = 288 — то есть 348, на 28 пикселей за
экран. Ширина «свой тайл + свес соседа» (40/60/64 при шаге тайла 32) верна
для любой колонки, кроме последней.
А заливка по контракту НЕ КЛИПУЕТ, и это правильно: проверка координат стоила
бы дороже самой заливки (шапка _gfx_recthfill256: «Клиппинга НЕТ, координаты
обязаны быть валидны»). Значит обрезать обязан ВЫЗЫВАЮЩИЙ — bar не трогаем.
МЕХАНИЗМ МЕРЦАНИЯ (объяснение пользователя). Страница 1 начинается на 320
байт дальше страницы 0, поэтому запись в страницу 0 с x > 320 попадает в
страницу 1 по x−320. Отсюда и «бар только на одной странице». Хуже того,
такая же запись НА странице 1 уходит уже за пределы обеих страниц — в то, что
лежит дальше (палитры и прочее), так что баг не только косметический.
ПОДТВЕРЖДЕНИЕ АРТЕФАКТОМ. Трасса всех вызовов pop_bar_black на прогоне:
BAR x=288 y=117 w=60 h=39 ret=D754 <- pop_floor_bake, 288+60 = 348
и независимо снятое расхождение страниц: РОВНО x 0..27 (348−320 = 28 px) при
y 117..155 — то есть в точности y этого бара (yb+26 = 117, высота 39).
ФИКС. Хелпер bake_w(x, w) обрезает ширину по правому краю (и отдаёт 0, если
прямоугольник целиком за экраном); применён во всех точечных запечках —
pop_floor_bake, pop_ceil_bake_empty, pop_loose_bake_empty. Обрезка ничего не
теряет: правее 320 восстанавливать нечего. Копия второй страницы (bake_copy)
берёт ту же обрезанную ширину, иначе запечка и копия разъехались бы.
Заодно, того же рода:
- pop_torch_wipe у факела в колонке 9 заливал x = 328, то есть ЦЕЛИКОМ за
экраном — теперь пропускается;
- pop_loose_bake_empty читал POP_COL_XH[col + 1] БЕЗУСЛОВНО, то есть у
последней колонки за концом массива (10 элементов); чтение убрано под
проверку.
Отсечено по дороге (проверками, не рассуждением): копия запечки не виновата
(сборка с выключенной копией — бары остались, проверил пользователь).
8 наборов tests-host зелёные.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
40f0d46542 |
Регресс тактов после четырёх фиксов: убран +130 000 в heal и +130 000 в циане
Замер на сцене каскада (ур.13 к.23) ПОСЛЕ багфиксов показал регресс, причём
в том числе в СИНЕЙ фазе, которую фиксы не трогали:
до фиксов после фиксов после этой правки
работа 783 798 1 038 528 873 930
синяя 142 830 275 538 142 830
зелёная 414 456 415 284 417 630
циан 269 538 479 628 379 482
период (растр.) 4 5 4 (один кадр 5)
Разбор синей зондом L (граница «ввод+heal» / «логика») сразу указал место:
логика 88 020 не изменилась, а «ввод+heal» вырос 55 000 -> 187 500. Это
pop_fore_heal чистит ОБЪЕДИНЁННЫЙ ov_mark: кромка потолка (полоса y 0..8) и
оверлеи соседей (64x64 на тайл) от шести кусков сливались в прямоугольник на
полкомнаты.
Правки, каждая с замером:
1. Оверлей соседа рисуется в ОКНЕ = прямоугольник самого куска. Смысл
оверлея — перекрыть кусок, а что лежит вне него, и так нарисовано
правильным фоном. Даёт три вещи разом: пикселей в разы меньше; куски
тайла, не задевающие кусок, отсеиваются предфильтром pop_blit_b даром (это
и есть вертикальный гейт, отдельного не надо); ov_mark становится НЕ НУЖЕН
— всё нарисованное лежит внутри heal-коридора куска, а он чистится каждый
кадр. Для этого заведён ov_suppress.
2. Кромка потолка — тоже без ov_mark: её куски рисуются от dby = 2, то есть
занимают строки 0..2, а помечает её только кусок и только пока достаёт
(mob_y <= 18) — значит она внутри его же коридора.
3. Предусловия перенесены в РЕЗИДЕНТ, до вызова в банк 2: pop_mob_overlay_tile
и разбор пометок живут в других банках, и каждый вызов платит межбанковый
трамплин (~8 900), а условий всего два дешёвых чтения тайла
(pop_tile_code — резидент, ~1 100). Гейты: тайл соседа не пуст и его
габарит пересекается с габаритом куска; для полосы потолка — тайл полосы
не пуст (а он там чаще всего пуст: кусок существует ровно потому, что
плита из своей клетки выпала, и пустой тайл в переднем слое не рисует
ничего).
ОТРИЦАТЕЛЬНЫЙ РЕЗУЛЬТАТ, откачен: сдавать пометки ряда −1 одной МАСКОЙ
вместо до двенадцати отдельных вызовов (один трамплин вместо двенадцати) —
873 930 -> 879 276, то есть хуже. Пометки ставятся редко, а цена копилки и
разбора маски платится всегда.
Итог: все три секции в бюджете 400 000 (синяя 158 880, циан 379 482), зелёная
417 630 — она была такой и ДО фиксов, это не регресс. Период вернулся к 4
растровым кадрам; ровно один логический кадр из 54 идёт за 5 (работа 873 930
при пороге 860 000).
Цена четырёх багфиксов по итогу: +90 000 работы за кадр (+11 %), из них весь
рост в циане.
Тестовый образ теперь всегда собирается LEVEL=1 (решение пользователя:
переход на нужный уровень он делает сам).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
6a91b9c688 |
Сосед поверх падающей плиты: не хватало set_redraw2 из draw_mob
Найдено пользователем (2026-08-17, уровень 1 комната 12, стоп-кадр):
плита (0,2) отвалилась и накрыла собой переднюю кромку пола (0,3).
Разбор по стоп-кадру: mobs[0] = {x 64, y 68, row 1, col 2, defer 1}, то есть
кусок в тайле (1,2), а страдает (0,3). Тайл (0,3) в комнате 12 — КНОПКА
(0x0F), и у неё fore_id = 0. Проверено по tile_table оригинала
(seg008.c:43) — там ровно то же, наша таблица совпадает байт в байт. То есть
через ПЕРЕДНИЙ слой эту кромку вернуть нельзя, и вопрос «может, у кнопки в
оригинале есть fore?» закрыт: нет.
Механизм в оригинале другой — draw_mob (seg007:1149) ставит соседу ДВЕ
пометки:
set_redraw2(tilepos, 1); // draw_other_overlay -> MIDtable
set_redraw_fore(tilepos, 1); // draw_tile_fore -> FOREtable
Мы портировали только вторую. А перекрывает кусок именно ПЕРВАЯ:
draw_other_overlay кладёт части соседа в midtable (seg008:1507/1512), тогда
как кусок сидит в objtable, и тот вливается в midtable при обходе СВОЕГО
тайла. Обход идёт ряды 2,1,0, колонки 0..9 — тайл (0,3) идёт ПОЗЖЕ тайла
(1,2), значит его оверлей ложится поверх куска.
Условие тоже сходится: draw_other_overlay рисует, когда тайл СЛЕВА пуст, —
а (0,2) стал пустым ровно потому, что плита оттуда и упала.
Порт: сам draw_other_overlay у нас уже есть (other_overlay_tile, применялся к
персонажу через redraw_at_char2), ему не хватало только ориентира «тайл
объекта». Новый вход pop_mob_overlay_tile подставляет тайл куска в
pop_bg_obj_* (с сохранением и возвратом — их читает ещё и mob_tick_one) и
зовёт other_overlay_tile; вся проверка порядка обхода остаётся внутри него.
Зовётся сразу после mob_render: списков у нас нет, порядок эмулируется
последовательностью вызовов.
По бюджету работа появляется только когда сосед слева пуст, то есть в кадрах
сразу после падения плиты, и попадает в циановую фазу (там запас).
8 наборов tests-host зелёные.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
7ae691b070 |
Fore потолка поверх падающей плиты: в разборе пометок не было ряда -1
Найдено пользователем (2026-08-17, уровень 1 комната 6): падающая плита (-1,5) перекрывает собой кромку потолка (-1,6), чего физически быть не может. Дыра архитектурная: pop_fore_needed обходил только ряды 0..2, ряда -1 в переднем слое не было вовсе. Сверено с оригиналом. redraw_needed_tiles (seg008:1B06) обходит ряды 2,1,0, а ПОТОМ отдельным проходом ряд 2 комнаты сверху (redraw_needed_above), и его draw_tile_fore кладёт куски в FOREtable. Падающая плита идёт в MIDtable (draw_mobs). draw_tables рисует back -> mid -> fore (seg008:1373), поэтому у оригинала кромка потолка оказывается поверх плиты сама собой. Порт: - pop_fore_needed: проход по ряду -1 добавлен и идёт ПОСЛЕДНИМ, как в оригинале. Свой набор пометок (rdfa/rdfa_pending) — как и у самих перерисовок ряда -1 (rda_*), это отдельный проход, а не 11-я колонка; - новый лист pop_ceil_fore_tile_b (pop_bg.c) — тот же redraw_needed_above, что уже рисовался над персонажем (ceil_over_kid_tile), плюс окно клипа ровно на полосу столбца и ov_mark (полоса идёт банком без тени, на второй странице её восстановит pop_fore_heal); - mob_mark_neighbour помечает ряд -1, пока кусок достаёт до кромки. Кромка живёт в трёх верхних строках поля (dby = 2 при клипе по POP_YOFF), спрайт куска занимает mob_y-16 .. mob_y, отсюда условие mob_y <= 18 — три кадра после отрыва (y = 2, 5, 11 при ускорении 3). Помечаются ОБА столбца, которые кусок накрывает по x (mob_x .. mob_x+62 = col и col+1); именно поэтому страдал сосед. По бюджету работа появляется только в эти три кадра на кусок и попадает в циановую фазу, где сейчас запас (270 тыс. из 400 тыс.). Замер на ур.13 — следующим шагом. 8 наборов tests-host зелёные. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
35b7cd5196 |
Фикс мерцания торцов: копия запечки требует ОКНА КЛИПА
Регресс от копии второй страницы (
|
||
|
|
18ee60eb69 |
mob_tick_one в file-scope + снятие временной оснастки замеров
зелёная пик 423 558 -> 414 456 работа пик 793 266 -> 783 798 ПЕРИОД логического кадра в каскаде: было 5-6 растровых, стало РОВНО 4 mob_tick_one: рабочие переменные в file-scope (было 16 байт кадра и 99 обращений `-N(ix)`, стало 17) — то же лечение, что у draw_tile и blit_b_clip. Снята временная оснастка из ГОРЯЧИХ путей: шесть вызовов pop_dbg_b1..b6 в pop_blit_b (по ~65 такта каждый на КАЖДЫЙ блит), pop_dbg_kind/m16 и подсчёт состава кадра в pop_redraw_needed, pop_dbg_m13..m15 в pop_ceil_shake_draw. Сами пустышки в pop_state.c оставлены — вставить их обратно на один замер дешевле, чем заводить заново; как это делается, записано в docs/perf_l13_room23.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5ef721e340 |
Вторая страница запечки — КОПИЕЙ с первой (зелёная 546 900 -> 423 558)
Идея пользователя: то, что пишется в ДВЕ видеостраницы, на вторую можно копировать, а не считать заново — тем же приёмом, что при перевороте экрана (зелёное зелье), только без зеркала. Точечные запечки ставятся с pages = 2, срабатывают два кадра подряд (по разу на страницу дабл-буфера) и оба раза считают одно и то же. А после первого раза нужный прямоугольник уже лежит в ОЗУ-копии первой страницы: gfx_copy_page берёт источником ОЗУ-копию НЕактивной страницы (то есть ЧИСТЫЙ фон — спрайты рисуются банком без тени и в копию не попадают), а приёмник обновляет и в видео-ОЗУ, и в ОЗУ-копии. запечь щебень (RD_FLOOR) 179 914 -> 107 844 в среднем (копия ~35 000) запечь колодец (RDA_CEIL_GONE) 138 318 -> 80 810 в среднем (копия ~17 500) зелёная пик 546 900 -> 423 558 работа пик 916 458 -> 793 266 Слот на тайл (bake_pg / bake_pg_above) помнит, НА КАКОЙ странице сделана первая запечка. Копируем только если первая была на ДРУГОЙ странице и дабл-буфер включён; иначе честно пересчитываем. Такая проверка выдерживает и переплетение двух запечек в одном кадре, и однобуфер (чит SPACE), и смену комнаты (pop_bake_forget в enter_room — номера тайлов повторяются). Проверено: 8 наборов tests-host зелёные. Мерцания через кадр нет — шесть снимков подряд после каскада попиксельно совпадают в поле, различаются ТОЛЬКО полосы профилировочного бордюра (снимок ловит разную строку растра); пользователь подтвердил визуально. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
af189a1d92 |
Композит куска: точный габарит + ИСПРАВЛЕНИЕ размеров в b2da0b8
ПОПРАВКА К
|
||
|
|
9a50ab20d7 |
Коридор heal падающего куска — по высоте композита (-12 тыс. на кадр)
Держали константные 24 строки «на всякий случай», хотя собранный композит знает свою высоту: у дворца 16 строк, у подземелья 19. Теперь коридор = высота композита + две строки сверху и одна снизу (mob_heal_up/mob_heal_h ставит mob_spr_build). На самой дорогой операции кадра, помноженной на шесть падающих плит: pop_loose_mob_tick 168 600 -> 156 762 зелёная пик 557 706 -> 546 948 работа пик 931 782 -> 920 862 Плюс ТРЕТЬЯ проверка окна клипа в pop_floor_bake — снова хуже (179 914 -> 188 417), и теперь ясно ПОЧЕМУ: детальный зонд по каждому блиту показал, что клипованный путь стоит +1 500 такта на КАЖДОМ из 7,6 блитов (+11 400), а режет он только редкие высокие куски (в трассе нашлись два: 34 878 -> 25 872 и 28 818 -> 24 090, всего -13 700), которых в среднем по 12 перерисовкам нет. Запись в коде: больше не пробовать. Заодно снят детальный профиль pop_floor_bake (одна перерисовка, 179 914): вход 2 382 pop_bar_black 60x39 (вкл. pop_cd_touch) ~15 500 контекст draw_tile #1 13 584 4 блита тайла #1 50 718 диспетчер между блитами #1 11 388 контекст draw_tile #2 13 584 4 блита тайла #2 ~54 000 диспетчер между блитами #2 11 388 хвост 6 474 Итог по сцене от базового замера: работа 1 437 150 -> 920 862 (-36%) зелёная 805 000 -> 546 948 (-32%) циан 631 800 -> 273 108 (-57%, в бюджете) Проверено: 8 наборов tests-host зелёные; в MAME кадр с четырьмя плитами в воздухе чистый (коридор сузился — следов нет), итоговая картинка прежняя. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b2da0b85b5 |
Композит падающего куска: циан в бюджете (632 000 -> 273 078)
Три части падающей плиты (env 70/74/72) складываются в ОДИН getimage-блоб
при загрузке тайлсета, и кусок рисуется одним блитом вместо трёх. Блоб
лежит в обычной памяти (W2), поэтому вызов идёт без atlas_image и без
gfx_w0_map/unmap — ещё ~1 350 такта. Прозрачность соблюдена: части
ПЕРЕКРЫВАЮТСЯ (74 и 70 обе от mob_x), поэтому композит собирается
попиксельно с пропуском 0xFF, то есть точно как три прозрачных блита.
Мотив: у блита ~8 800 такта постоянных накладных против ~5 000 на пиксели.
Шесть кусков в воздухе = 18 вызовов = 258 708 такта, больше половины
цианового блока. Резервный путь на три блита оставлен (mob_spr_ok).
циан пик 435 180 -> 273 078 (цель 400 000 — ВЫПОЛНЕНА)
работа 1 037 250 -> 931 782
зелёная 558 498 -> 557 706 (не затронута, ею занимаемся дальше)
Заодно НАЙДЕН И ПОФИКШЕН БАГ КОРИДОРА heal. Снятые из каталогов реальные
габариты частей оказались другими, чем в комментарии, И РАЗНЫМИ у тайлсетов:
подземелье 70 = 32x16 74 = 26x15 72 = 26x16
дворец 70 = 32x13 74 = 25x15 72 = 31x13
То есть во ДВОРЦЕ (уровни 4-6, 10, 11, 13, 14) кусок достаёт до mob_x+62, а
коридор heal был mob_x-4 .. mob_x+59 — правые три пикселя не стирались
никогда. Четыре пикселя слева при этом чистились впустую: левее mob_x
кусок не рисует ничего. Коридор стал mob_x .. mob_x+63, площадь та же.
Высоту (24 строки при следе 19) НЕ сужаем: 24 — это след подземелья плюс
две строки поля с каждой стороны, «сужение по палаццовому следу» сломало бы
подземелье.
Ещё три правки того же захода:
- pop_blit_b: аргументы в file-scope. Третий и дальше SDCC передаёт стеком,
и каждое чтение шло через `-N(ix)` — 76 обращений. Стало 11.
- pop_loose_mob_tick: пометки всех кусков одним пакетом (было по 4 502 такта
на кусок). mobTk 176 772 -> 168 600.
- pop_floor_bake: окно клипа проверено ВТОРОЙ раз (после того как
blit_b_clip подешевел) и снова хуже — 182 124 -> 188 460. Причина
записана в коде: у этого тайла клипа нет, блиты идут быстрым путём, а окно
их уводит в клипованный и при этом не отсеивает ни одного куска и не
режет ни одной строки. Не пробовать в третий раз.
Новый лист pop_mem_b (резидент): блит блоба из обычной памяти с теми же
клипом полосы у потолка и окном перерисовки, что у pop_blit_b.
Проверено: 8 наборов tests-host зелёные; в MAME кадр с летящими плитами и
итоговая картинка совпадают с прежними.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
a3c473d910 |
Оптимизация каскада плит: -27% работы за кадр (пять правок с замерами)
Сцена — ур.13 комната 23, шесть плит с потолка (docs/perf_l13_room23.md).
Все замеры сняты одним и тем же прогоном «ESC -> зонды -> roomtest».
было стало
работа за кадр 1 437 150 1 051 332 -27%
зелёная (фон) 805 000 575 730 -28%
циан (спрайты) 631 800 438 546 -31%
синяя 142 830 142 830 в бюджете
Цена одной перерисовки:
RDA_CEIL_GONE (запечь колодец) 251 335 -> 137 975 -45%
RD_FLOOR (щебень на посадке) 198 805 -> 182 124 -8%
RDA_CEIL (дрожащая плита) 48 785 -> 43 543 -11%
C1. Пометки «фон трогали» в mob_render ПОДАВЛЕНЫ (pop_cd_mute): коридор
куска уже помечен одним вызовом в pop_loose_mob_tick, а три блита метили
подмножества того же прямоугольника по 4 502 такта. Плюс пометка в
mob_spawn_copy — кусок, рождённый внутри тика, свою мог не получить
(слот выдаётся с начала таблицы, то есть уже пройденный). -55 тыс.
G2. Контекст тайла — file-scope, а не локали draw_tile (порт
load_curr_and_left_tile, seg008:0339). В функции 57 вызовов, и каждое
живое через вызов значение спиливалось: 26 байт кадра и 513 обращений
`-N(ix)`. Стало 33 обращения, кадра нет, банк 7 -703 Б. -55 тыс.
G1. Окно клипа для точечной перерисовки (pop_t_win_set/clear). В
pop_ceil_bake_empty куски ряда 0 высотой 63 px рисовались целиком, хотя
восстановить надо девять строк: блит стоил 21 447, стал 7 619. -90 тыс.
В pop_floor_bake окно попробовано и ОТКАЧЕНО (там нет клипа, блиты шли
быстрым путём, а окно уводило их в blit_b_clip и не отсеивало ничего:
187 266 -> 198 279) — вернуться после G3, запись в коде.
G3. blit_b_clip: байтовый габарит + file-scope вместо локалей. Одного
байтового габарита НЕ ХВАТИЛО (кадр остался, 211 -> 173 обращений) —
значений, живых через шесть вызовов ядер libbgi, больше, чем регистров.
С file-scope: 51 обращение, кадр 22 -> 12 Б. Клипованный блит подешевел.
C4. Падающий кусок КЛИПУЕТСЯ сам, вместо чистки бортов после. Раньше он
рисовался в борт целиком и взводил border_dirty, а pop_room_clip_borders
стирал две полосы 320x28 — 150 978 тактов в каждом кадре, пока хоть один
кусок торчит выше поля (гряда 13-го рождается ровно у потолка, y=2), то
есть почти весь каскад. Стало 1 722. -138 тыс.
Заодно blit_b_oversize больше не ходит через blit_b_clip (у того габарит
теперь байтовый) — рисует напрямую gfx_blit_part. Это путь под
полноэкранные подложки интро/финала (320x200, docs/perf_l13_room23.md §5).
Проверено: 8 наборов tests-host зелёные; в MAME каскад рисуется корректно
(кадр с летящими плитами, чистые борта, итоговая картинка как до правок).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
b27b31318a |
Разбор draw_tile: модель цены блита + пакетная пометка pop_cd_touch
Замер (MAME, комната 23 ур.13) разложил цену одного gfx_blit_noclip
регрессией по 339 блитам, высоты 3..63:
такты = 8791 + 198.2 * h + 5.96 * (w * h)
Модель ложится на весь диапазон (32x3 -> 9 891, 32x13 -> 13 887,
32x60 -> 32 073), разброс внутри размерной группы — ТРИ такта.
Выводы:
- 5.96 на байт — предел железа: байт идёт через акселератор дважды
(burst src->память акселератора, burst ->экран), по 3 такта на проход
при системном клоке 21 МГц. Ускорять передачу нечем.
- 198 на строку — цикл _bgi_blit_rows_raw; при w=32 это половина
построчной цены.
- 8791 на ВЫЗОВ — крупнейшая статья, НЕ объяснена. Проверено, что это не
W3-скобка (в обеих половинах по 5 инструкций) и не прерывания (разброс
3 такта). Один тайл = 5-6 спрайтов ~ 107 000 тактов, из них ~50 000 —
постоянные накладные вызовов. Это и есть главный резерв.
Заодно: pop_cd_touch собирается в ПАКЕТ на тайл (pop_cd_batch_begin/end,
скобка в draw_tile) вместо вызова на каждый кусок — раньше 2 866 тактов
на спрайт, 15% цены блита. ВНИМАНИЕ: выигрыш замером НЕ подтверждён —
в захваченных кадрах скобка не сработала (блиты шли из холодной отрисовки
комнаты, мимо draw_tile). Правка безопасна по построению: объединение
прямоугольников может пометку только расширить, не сузить.
Снятые сегодня неверные утверждения (чтобы не всплыли):
- «клипованный блит дороже полного» — артефакт сравнения разных выборок
спрайтов; 32x60 с клипом до полосы стоит 7 236, то есть клип работает;
- «блиты идут программным циклом со скоростью ldir» — нет, ядро на
акселераторе, см. модель выше;
- «отложить запекание на кадр» — НЕЛЬЗЯ: запекание пишет ОЗУ-копию фона,
из которой heal восстанавливает; отложенное даёт призрак плиты.
Все 8 наборов tests-host проходят. Оснастка замера временная.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f89b7dd0d2 |
Ускорение холостого хода: указательный обход вместо arr[i] в горячих циклах
Замер зелёного блока (комната 23 уровня 13, MAME, такты эмулятора) показал, что 86% его стоимости в ПОКОЕ — это pop_loose_tick, который не делает ничего. Причина — кодоген SDCC, подтверждена чтением .asm. С `int pos` и записью pop_loose_modif[pos] компилятор держал счётчик в IX-фрейме, каждую итерацию заново складывал 16-битный адрес элемента, клал его в локал и тут же вычитывал обратно парами `pop bc / pop hl / push hl / push bc`. 40 холостых итераций (30 тайлов + 10 потолков) стоили 27 936 тактов. То же в pop_loose_mob_tick: запись mobs[i] заставляла умножать i на sizeof(mob_t)=15 заново под КАЖДОЕ поле (.active/.clean/.x/.y), 14 пустых слотов — 35 790. Правка — обход указателем, счётчик uint8_t, пустые слоты отсеиваются в вызывающем цикле (а не гардом внутри mob_tick_one, до которого надо ещё дойти). Холостая итерация стала `ld a,(de) / or a,a / jp Z` — три инструкции вместо дюжины с обращениями к памяти. Результат (такты MAME, холостой кадр): циклы по тайлам 27 936 -> 9 852 (2,8x) pop_loose_mob_tick 35 790 -> 7 416 (4,8x) pop_loose_tick 70 866 -> 24 384 (2,9x) ЗЕЛЁНЫЙ БЛОК 82 242 -> 35 760 (2,3x) Банки ужались: BANK3 -90 Б, BANK7 -74 Б. Все 8 наборов tests-host проходят; комната 23 в MAME рисуется корректно. Плюс ВРЕМЕННАЯ оснастка замера (маркеры m9..m16, pop_dbg_kind) — она же показала, что пик зелёного блока сидит НЕ в тряске плит, а в запекании тайлов; разбор продолжается, оснастку снять перед закрытием темы. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
50e4eda2ad |
Падающие плиты: передний слой по пометкам, честные спрайты, замер кадра
Слой fore переведён на пометки ОБЪЕКТОВ вместо окна вокруг персонажа — порт set_redraw_fore/redraw_frames_fore (seg007:0550, seg008:214): pop_set_redraw_fore + pop_fore_needed + pop_fore_tile_b, пометки ставит падающий кусок вокруг себя (draw_mob, seg007:1147). Ключевая правка: пометки соседа считаются в ПРОХОДЕ ОТРИСОВКИ, а не в тике. Стояли в цикле тика — то есть по позиции ДО mob_tick_one, а кусок рисовался уже по новой; пока он летит внутри ряда, тайлы совпадают, но в кадр пересечения границы пометка указывала на прежний тайл и колонна кусок не перекрывала. Ровно один кадр, как и наблюдалось. Спрайты падающего куска: obj_id = 10 (add_mob_to_objtable, seg007:1170), части берутся по этому индексу — 70/74/72, а не 41/43/42 (плита В ПОКОЕ). Отсюда была «цельная ровная плита» вместо двух половин со сдвигом. Убрана подпорка «перерисовать соседний тайл поверх куска»: тащила на плиту чужой узор и окно и стоила по полной отрисовке тайла на кусок за кадр. Коридор heal сужен по высоте 32 -> 24 (реальный след спрайта 16 px). gfx_set_bank вынесен из потайлового цикла в обрамление прохода. Выяснено и зафиксировано: clip.right = 40 в add_mob_to_objtable — МЁРТВЫЕ данные. set_clip_rect вызывается под chtab_flip_clip[chtab_id], а для окружения флаг равен нулю — оригинал падающие куски не клипует вовсе. Портировать нечего; две попытки это сделать были ошибочны. Замер кадра (маркеры m1..m4, оставлены временно для проверки правок): покой 196 614 тактов (15,2 % кадра при периоде 1 290 000), во время падения гряды среднее 222 667, пик 1 428 990 — то есть 111 % кадра, шесть кадров подряд за пределом. Логика неизменна (88 020), растут фон (до 887 376) и спрайты (до 660 792). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9e1a55bdc3 |
Падающие плиты: спрайты падающего набора, замеры слоёв, анализ midtable
Спрайты: у падающего куска obj_id = 10 (add_mob_to_objtable, seg007:1170), и три части берутся из таблиц по этому индексу — 70/74/72, а не 41/43/42. Мы рисовали плиту В ПОКОЕ, отсюда «цельная ровная» вместо двух половин со сдвигом правой на пиксель. Подпорка «перерисовать соседний тайл поверх куска» убрана: она тащила на плиту чужой узор и окно, да ещё стоила по одной полной отрисовке тайла на кусок за кадр. Клип объекта (clip.right = 40) НЕ портирован — единицы поля не выяснены, буквальные 40 пикселей срезают задний угол плиты. Цена отказа — правая грань куска и передние части чужих тайлов его не перекрывают (MOB-CLIP-RIGHT, BUGS_OPEN.md). Коридор heal сужен по высоте 32 -> 24: реальный след спрайта 16 px. Ширина оставлена 64 — из неё используются 62, экономить нечего; заодно в комментарии зафиксировано, что «выравнивание блока акселератора» ничем не подтверждается и требует проверки артефактом. docs/midtable_analysis.md — разбор слоёв оригинала и замеры: - логический кадр 1 289 526 тактов; - pop_redraw_needed 978 тактов (0,08 %); - fore-проход 128 778 тактов (10 %) на ОДНОГО персонажа, но выполняется лишь на 4 % кадров — гасит пропуск неизменившегося персонажа; - максимум объектов на одном тайле 2 (пара из loose_fall), значит сортировка внутри тайла бесплатна. Вывод: единственный риск порта objtable — сохранится ли окно клипа fore-прохода; до разбора redraw_frames_fore выбирать вариант рано. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
c9aa6d9c16 |
Переворот: убрать перерисовку комнаты — стирать только запечённое пламя
Полная перерисовка (прошлый коммит) чинила лишние языки огня, но стоила 6 245 477 тактов = 14.5 кадра (замер MAME) против 2.1 кадра у копии акселератором — пользователь увидел сильное подтормаживание на U, причём в обе стороны. Оказалось, чинить надо ровно одну вещь: пламя факела запекается в ОЗУ-копию (pop_torch_draw — heal'а нет, следующий кадр непрозрачно накрывает предыдущий), и отражение утаскивало его в зеркальную позицию, где накрывать уже нечем. Новая pop_torch_wipe(row,col) стирает запечённый кадр пламени и возвращает фон покоя (bar + draw_tile своей ячейки и правого соседа — канвас 16x18 в ячейке правого соседа, как в seg008:560). pop_flip_screen снова отражает копией, но ПЕРЕД этим проходит 30 тайлов комнаты и чистит факелы (коды 19 и 30) на обеих страницах. Замер после правки: 904 247 тактов = 2.1 кадра — то есть чистка стоит ~7 000 тактов, а переворот вернулся к скорости копии (ускорение 6.9x против перерисовки). tests-host 6/6. |
||
|
|
0be0502388 |
L9-INVERT: зеркалятся ВСЕ заливки слоя фона (шов, сосед loose, оверлеи)
Продолжение фикса решётки: первый грep пропустил заливки, где setfillstyle и bar стоят не соседними строками. Переведены на pop_bar_black шов (col0, бары ворот соседа) и сосед в loose_bake_empty; в pop_bg.c универсальная заливка слоя фона (оверлеи кромки/полосы) зеркалится той же POP_FLIP_TOP. Голых bar в слое фона больше не осталось (проверено грепом). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c60415a322 |
L9-INVERT: чёрные плиты слоя фона тоже зеркалятся (баг решётки)
Найдено пользователем: поднимающаяся решётка анимировалась неправильно, а в углу (0,0) оставался чёрный прямоугольник — заливки шли голым bar по НЕПЕРЕВЁРНУТЫМ экранным координатам. Формула переворота вынесена в pop_bg.h (POP_FLIP_TOP) — одна на весь порт, и добавлен pop_bar_black: чёрная плита банком 0x50 с учётом переворота и с пометкой области (bar идёт мимо pop_blit_b). На неё переведены все четыре стирания слоя фона: решётка, полоса потолка, floor_bake, loose_bake_empty. Проверено в MAME: перевёрнутая комната чистая, артефакта в углу нет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
e4ef489872 |
L6-SHADOW: тень роняет решётку + кромка ряда ниже
1. L6-SHADOW (seg002:0090 + seg002:1064). Тень встаёт при каждом входе в комнату 1 уровня 6 и делает ОДИН осторожный шаг (Shift+вперёд) в тот кадр, когда Кид прыгает к решётке (Kid.frame == 43, Kid.x < 128): встаёт на closer (1,1) и роняет решётку (1,2), за порожек которой Кид цепляется. do_init_shad переписан под таблицы init_shad_5/6 — первые 7 полей Char, как memcpy(&Char, source, 7) у оригинала. Проверено в MAME: «Кид прыгнул, зацепился, Тень сделал шаг, решётка упала». Тем самым закрыт остаток GUARD-PHYS — нажатие плиты НЕ-Кидом работает. 2. BUG-BELOWROW-WALL. При отсутствующей комнате снизу load_rowbelow (seg008:368) подставляет tiles_0_empty колонкам 1..9 и tiles_20_wall только левому краю; у нас стеной забивались все 11 позиций, и её topright лез жёлтыми треугольниками под пустые тайлы нижнего ряда (видно в шахте падения, комната 3). Комнаты 1 и 3 уровня 6 сверены с картой SDLPoP: расхождения только силуэты персонажей. tests-host 5/5, size-check чист. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
8cac51d592 |
Убраны три последних /63 %4 в pop_room.c — вызов pop_y_to_row
mob_tick_one (927) и mob_render (976/977) считали `(y+60)/63 % 4 - 1` вручную, хотя pop_y_to_row — точный эквивалент этой формулы на всём int16_t (включая усечение деления к нулю для отрицательных). В asm это были три пары __divsint+__modsint, ~16 200 тактов (3,8 % кадра) — только пока кусок плиты в полёте, то есть в самом тяжёлом кадре. В банке 7 теперь ноль __divsint. Эквивалентность закреплена тестом geom_y_to_row_matches_formula: перебор −400..400 против исходной формулы (вызовы разбросаны по трём банкам, соблазн написать деление «по месту» возвращается). tests-host: [geom] 39 -> 840, все 5 наборов прошли. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4718bff767 |
Уровень 4: skip по маске тайлов + порт loose_land, ворота 0xFF, пламя в фон
ОПТИМИЗАЦИЯ. Метка «фон трогали» была одним union-прямоугольником на страницу, и три факела комнаты склеивались в полосу x 40..280 на всю комнату — Кид, стоящий между крайними факелами, терял пропуск перерисовки и каждый кадр платил полным fore-проходом. Теперь это маска тайлов (uint16_t pop_cd_dmask[2][3]: бит = колонка, слово = ряд, набор = страница), проверка — три AND через резидентный pop_cd_hit. Гранулярность тайла — это гранулярность оригинала (redraw_frames_anim[tilepos], set_wipe; подтайловое уточнение там только по высоте, wipe_heights). Замер на (1,7): циан 233 515 -> 24 781, работа за кадр 517 609 -> 306 553, период 4 -> 3 растровых кадра. BUG-LOOSE-BUTTON-1. Упавшая плита не нажимала кнопку. Три слоя: порт loose_land не звал trigger_button вовсе; pop_room_col_landing считала площадкой только чистый пол, а у оригинала их семь (пол, пика, обе кнопки, зелье, оба факела); сигнал приходил в момент ОТРЫВА плиты, из-за чего ворота начинали открываться, пока она ещё в воздухе. Нажатие в оригинале ОДНО, но с button_type = tiles_14_debris — это «открыть НАСОВСЕМ» (modifier 0xFF), и кнопка съедается. Посадка в комнате снизу переехала на новый сигнал pop_loose_exit (взводит mob_tick_one, когда кусок ушёл ниже поля). BUG-GATE-FF-1. 0xFF был перегружен: сторожевое «тайла нет» в gate_modif и живое «открыто навсегда» из trigger_gate. gate_passable заворачивал Кида в воротах, нарисованных открытыми. Мёртвая ветка убрана. BUG-TORCH-CHOMP-1. Чомпер (0,7) комнаты 23 healит x 224..255 / y 30..93 и стирал пламя факела (0,6), которое рисуется в ячейке правого соседа. Фикс — запекать пламя (GFX_BANK_NORMAL): у факела все девять кадров на общем канвасе 16x18 и непрозрачны, протухнуть в ОЗУ-копии нечему, а heal чомпера сам возвращает огонь и кладёт челюсти поверх — z-порядок как в оригинале. Пузырёк зелья так нельзя (ползёт вверх, нужен heal) — остаётся в SPRITE. Заведено открытым: died_on_button (seg007:776) не портирован — нужен тайл tiles_5_stuck в атласе. Проверено пользователем в MAME (уровень 4: плита 16(1,1) -> кнопка 17(0,1) -> ворота 23(0,9); комната 23 с чомпером и факелом); make -C tests-host — все 5 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6a824dd7ab |
Закрыты BUG-GUARD-IX-1 и BUG-GATE-SEAM-ROW1 (уровень 4)
BUG-GUARD-IX-1 — «зависание» в бою со стражем. Не зависание: главный цикл крутился, а отладочная локаль frozen сама становилась ненулевой. Корень — у main затирался IX (0xBFFA -> 0xBF00), и все его локали адресовали живой стековый мусор. Затирал check_chomped_guard: coll_row() пишет по flags + scan_off, длину берёт из win_lo/win_hi, а scan_off выставляла только coll_scan_prepare() из пути Кида. Ряд стража писался по смещению Кида длиной стража и при Киде у правого края комнаты вылезал за flags[13] — прямо в сохранённый IX. Фикс: coll_scan_prepare() в начале get_row_collision_data(); заодно чинится расчёт (scan_left0 задаёт x колонок, чомпер-коллизия стража считалась по координатам Кида). На уровне 1 не проявлялось: бой идёт левее середины, запись оставалась внутри массива — данные были неверны молча. BUG-GATE-SEAM-ROW1 — решётка в шве не анимировалась. Плита комнаты 1 открывает ворота комнаты 8 в (1,9), видимые через левый шов, а change-driven редрой смотрел только m[9] (ряд 0) и перерисовывал жёстко draw_tile(0,0). На уровне 1 та же связка работала лишь потому, что решётка соседа стояла в (0,9). Фикс: сигнатура по всем трём рядам, маска изменившихся рядов в seam_rows (переживает оба кадра дабл-буфера), pop_room_redraw_seam_left(rows) перерисовывает только помеченные. Плюс ряд выше (changed | changed>>1): верх решётки (draw_tile_anim_topright, seg008:0568) рисует тайл над-справа от ворот, без этого чёрный треугольник над ними оставался статичным. Проверено пользователем в MAME (бой в комнате 18; анимация решётки в стартовой комнате), детекторы IX висели без починки и не сработали; make -C tests-host — все 5 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
89400ff1d4 |
Тайлсет дворца: палитра применялась до kid.pal и затиралась ею
Симптом (эталон SDLPoP против нашего кадра, уровень 4): дворцовая геометрия рисовалась подземельными красками — сине-серые арки вместо песочных, бирюзовая дверь уровня вместо кремовой, сланцевый пол вместо коричнево-розового. Бирюза и зелень — это dungeon-слоты 0x5E (0,117,76) и 0x5F (0,165,157). Причина в порядке старта: атласы (и вместе с ними палитра тайлсета) грузятся ДО initgraph, потому что тот снимает DSS-страницу W0. А kid.pal — ЕДИНАЯ игровая палитра, собранная из VDUNGEON (pop_pack_kid.py build_palette), — читается ПОСЛЕ initgraph и затирает слоты 0x50..0x6F. pop_bg_pal_apply() возвращает 32 записи текущего набора; зовётся сразу за gfx_pal_sync(). На смене уровня палитра по-прежнему едет внутри pop_bg_load — там initgraph давно позади. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
4d4323fc54 |
L3-CHOMP: передние зубья чомпера — блит pop_fore_b и ветка в draw_tile
Две ошибки в одном месте. 1) Кадры 106..110 и передняя кровь 119..123 упакованы в pop_fore.atl (FORE_ENV_IDS в pop_pack_bg.py), а блитились через pop_env_b — id уходил в env-страницу 3 по индексу 10, где записи нет, и передний слой не рисовался вовсе: Кид, стоящий ЗА челюстями, был виден целиком. 2) В draw_tile была только backtable-часть; у оригинала фронт чомпера рисует отдельная ветка draw_tile_fore (через таблицу он не идёт — у записи 0x12 fore_id нулевой), поэтому передние зубья появлялись лишь там, где по тайлу прошёлся fore-проход персонажа. |
||
|
|
dc0bd47368 |
L3-CHOMP: чомперы — анимация, отрисовка и смерть в челюстях
Порт SDLPoP: animate_chomper / start_chompers / start_anim_chomper / next_chomper_timing (seg007) -> pop_trob.c; draw_tile_anim + draw_tile_fore, ветка tiles_18_chomper (seg008) -> pop_room.c (низ/кровь/верх, backtable) и pop_bg.c (передний слой); check_chomped_kid / check_chomped_guard / chomped (seg004) -> pop_map.c. Состояние — в room_modif, как у пик и ворот: младшие 7 бит фаза 1..N, старший бит «перемололо кого-то» (кровь остаётся на тайле навсегда). Номер позы chomper_fram1 и передние куски лежат в РЕЗИДЕНТЕ (pop_tile.c): их читают обе половины слоя фона, а const-таблица банка из чужого банка не видна. start_chompers зовётся там же, где в оригинале: SEQ_UP/SEQ_DOWN в play_seq (pop_kid.c), start_fall и land (pop_map.c), вход в комнату (roomtest.c). Поэтому чомперы щёлкают только пока персонаж в ИХ ряду — так в оригинале. check_chomped_guard у оригинала отдельное тело (страж не проходит через check_collisions). У нас та же формула уже есть в get_row_collision_data, поэтому флаги ряда считаются во ВРЕМЕННЫЙ массив: coll_curr/above/below — это кадр Кида, из них move_coll_to_prev берёт прошлые флаги, затирание сломало бы ему бамп. Период смыкания — POP_CHOMPER_SPEED в pop_tune.h (15, как в оригинале). Заодно: устаревшая заглушка pop_fore_set_clip в tests-host была __banked, хотя функция давно переехала в резидент — всплыло при пересборке. Ассеты уже были упакованы (pop_pack_bg.py, 2026-08-07). Банк 6: 20.0 %, банк 7: 39.7 %, банк 3: 53.8 %. tests-host зелёные (5 наборов). Зубья в MAME рисуются; анимация и смерть — на ручной приёмке. |
||
|
|
a25ce58869 |
DRAW-COST шаг 1: пропуск неизменившегося персонажа — 210% -> 116% кадра
Персонаж, у которого с прошлой отрисовки ЭТОЙ страницы дабл-буфера не изменился ни один вход отрисовки, а фон в его прямоугольнике не трогали, уже нарисован правильно: heal, блит и fore-проход пропускаются целиком. Не спецкейс «мёртвый страж», а общее правило — покрывает и труп, и стоящего Кида, и ждущего стража. Механизм: снимок входов по страницам (pop_cdraw.c, cd_sig/cd_quiet) + позиционная метка «фон трогали вот здесь» (pop_cd_touch в резидентном pop_tile.c, зовёт сам pop_blit_b). Решение перепроверяется перед отрисовкой, а pop_char_draw страхуется собственным heal — если тик всё-таки сдвинул персонажа, прошлый кадр стирается там. Слоты рядом (32 px) — перерисовываем оба, иначе heal соседа выест кусок из «тихого». Метка обязана быть ПОЗИЦИОННОЙ: с флагом «фон трогали хоть где-то» выигрыш был ровно нулевым — факелы анимируются каждый кадр и гасили пропуск для всех сразу (597 684 такта, как без оптимизации). Замеры (MAME, брейкпоинты по totalcycles, бюджет кадра 430 000): комн. 1.3, труп стража, Кид стоит: 210 % -> 116 % (500 772 такта), ноль вызовов pop_heal_fast за кадр, весь фон — 2 блита (44 136); комн. 1.1, Кид стоит вдали от факелов: 404 112 (94 %), цикл 4 -> 3 кадра. Узкое место сместилось на ЛОГИКУ: 60 % кадра уходит на тик персонажей, которые СТОЯТ, ещё 28 % — на loose_tick + process_trobs в комнате без единой ловушки. Разбивка и план — TASKS_OPEN.md#draw-cost. |
||
|
|
14190f0210 |
MEM-BANK2 шаг 3: холодная половина слоя фона в банк 7 (90.4% -> 35.8%)
pop_bg.c разрезан по ЧАСТОТЕ вызова, а не по размеру:
pop_bg.c (банк 2) горячее fore-проход, оверлеи, кладка, клип
pop_room.c (банк 7) холодное draw_tile, точечные перерисовки, mob,
загрузка атласов
Стык — три тонкие __banked-обёртки (wall_pattern, wall_pattern_reset,
draw_gate_back): тела остаются непомеченными, поэтому горячий fore-проход,
зовущий wall_pattern до девяти раз за кадр, платит ноль, а трамплин
достаётся только холодному пути — ~40 вызовов на вход в комнату, 26 000
тактов = 0.06 кадра РАЗОВО. Общее состояние (pop_loose_modif, pop_ceil_modif,
obj_row/obj_col) писучее, лежит в _DATA/W2 и видно обеим половинам.
Заодно удалена мёртвая potion_bubble (169 Б).
Грабля: n_banks объявляет само приложение (roomtest.c), а не sprinter-cc.
Восьмой банк без правки константы линкуется молча, _bank_pages[7] остаётся
0xFF, и программа встаёт намертво до первого кадра. Диагноз снят дампом
_bank_pages из MAME.
Замер (ALLOCS=3000): BANK2 11 942 -> 5 872, BANK7 6 035; _CODE и куча не
тронуты (22 556 / 4 301).
Проверено: tests-host зелёные; построчная сверка pop_bg.c+pop_room.c против
дорефакторного pop_bg.c — ни одной строки логики не пропало; 8 комнат в MAME
до/после совпали попиксельно по активному экрану (различия только в фазе
анимации факелов и кадре Кида); живой прогон с переходами комнат, боем и
воротами сверен контрольным запуском HEAD-бинаря.
DRAW-COST поднят в приоритете: замер пользователя (ур.1 комната 3, страж
убит, Кид стоит) — синяя 80%, зелёная 20%, циан 110%, итого ~210 %
кадрового периода В ПОКОЕ.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|