ТАЙМИНГИ. Пользователь заметил, что заклинание Джафара не совпадает с
музыкой. Замер в 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>
Замеры в 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.
Разгрузка 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>
Шаги 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>