Пункт 1 плана стражей. play_seq был прибит к Киду, поэтому страж стоял на
захардкоженном кадре 166. Теперь как в оригинале: интерпретатор работает
с АКТИВНЫМ персонажем Char, а вокруг стоят loadkid/savekid и
loadshad/saveshad (порт seg006:809..825).
Почему копия, а не указатель: так в оригинале, и на Z80 это быстрее —
горячий цикл обращается к глобалу абсолютной адресацией, а 16-байтовое
копирование платится один раз на переключение персонажа, тогда как
указатель дал бы индексную адресацию в каждом обращении.
Сопутствующее:
- kid_t и pop_char_t слиты в один pop_char_t (pop_char.h): в оригинале
char_type один на всех, и без этого общий интерпретатор невозможен.
Kid получил поля room/charid/sword/alive — они и так нужны боёвке;
- load_frame выбирает таблицу кадров по Char.charid (у стража своя,
frame_tbl_guard с индексом frame + add_frame − 149, seg006:0293);
- cur_frame разведён на два кэша: страж тикает ПОСЛЕ Кида, и без этого
kid_draw брал бы кадр стража. savekid/saveshad раскладывают кадр по
своему персонажу;
- kid_set_seq пишет ИМЕННО Kid (его зовут pop_ctrl/pop_map вне окна Char,
иначе loadkid затёр бы), для окна Char добавлен pop_char_set_seq —
порт seqtbl_offset_char;
- в pop_map 9 голых play_seq() заменены на pop_kid_play() (load+play+save);
- страж входит в комнату через seq_77_guard_stand_inactive (seg002:0208),
а не через прибитый кадр.
Проверено в MAME: Kid бегает и упирается в стену как прежде, в комнате 12
плиты проваливаются со щебнем (правка задела 9 вызовов play_seq в
физике), страж в комнате 3 рисуется в той же позе, но теперь
curr_seq=0x19A9 и charid=2 — кадр получен прокруткой последовательности,
а не константой.
Бюджет (комната 3, 150 кадров): 380 292..381 282 тактов против
370 140..371 160 до правки, то есть +10 150 (+2.7 %). Основное — не
копии Char, а то, что страж теперь реально крутит интерпретатор каждый
кадр, а раньше стоял замороженным. 0.887 кадра, запас 48 718.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Раскладка (по подтверждённой пробником модели, tests/w3bankgfx):
- roomtest переведён на MEMORY=huge: та же small-раскладка резидента
(CODE в W1, DATA за ним) плюс банки кода в W3;
- guards.c собирается как --bank 1=guards.c — там будет ИИ и боёвка;
- СОСТОЯНИЕ стража живёт в W1/W2 (pop_guard.c): писучие статики
__banked-модуля линкуются в страницу банка и снаружи не читаются, так
что банк — только код;
- поля pop_char_t повторяют char_type оригинала (types.h:302), чтобы порт
seg005/seg006 ложился один в один.
Режим читов (порт cheats_enabled, seg000:111): глобальный флаг pop_cheats,
на время разработки включается в main. Реализован один чит — K (kill
guard, seg000:786): скелета не берёт, живому стражу ставит
guardhp_delta = -guardhp_curr и alive = 0. Обработка по фронту нажатия.
Остальные читы оригинала не портированы.
Проверено в MAME: приложение в huge-раскладке стартует, комната рисуется,
Kid бегает — то есть банкованный pop_guard_tick() зовётся каждый кадр
через трамплин и корректно возвращается; K не роняет приложение (стража
в комнате пока нет). Банк занят на 6 Б из 16384.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>