4f7d9c05966a6fd8f69aab98bd6c42b3fbf2441d
54 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
37fc572cc3 |
PoP roomtest: LCG оригинала на ассемблере — точность без потери скорости
Возврат к БИТ-В-БИТ генератору оригинала по умолчанию (POP_PRANDOM_EXACT=1): по нему проще отлаживать и сверять картинку с эталоном. Чтобы это не стоило процента бюджета, сам шаг LCG переписан на Z80-ассемблере — единственное место в порте, где это сделано, с явного разрешения. Приём: 214013 = ((((1<<1)+1)<<2 + 1)<<4 + 1)<<10 - 3 — схема Горнера по РАЗРЕЖЕННОЙ записи константы. Вместо 12 сложений (по числу единиц в 0x343FD) — 17 удвоений, три сложения и одно вычитание; величина 3*s, нужная в конце, попадается по дороге на втором шаге. Проверка в ДВА этапа: - схема на хосте: horner(s) == s*214013+2531011 на 3 000 000 сидов; - сама asm-транскрипция на живой машине: breakpoint на pop_prandom, 11 последовательных состояний сида из MAME — каждый переход совпал с s*214013+2531011 бит-в-бит. Замер, комната 3, 175 кадров (медиана кадра / prandom->torch_draw): C, бит-в-бит (16-бит половины) 403 632 / 10 933 C, xorshift16 + шаг Вейля 397 986 / 7 927 asm, бит-в-бит 400 800 / 9 331 То есть asm вернул половину разрыва (2 832 такта за кадр), сохранив совместимость с эталоном. Ветка xorshift оставлена под -DPOP_PRANDOM_EXACT=0 как запасной ход — брать её имеет смысл, только если не хватит последних 2 800 тактов. Итог оптимизационного круга: 416 154 -> 400 800 (0.968 -> 0.932 кадра). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
99b430f2ed |
PoP/libbgi: убрана 32-бит арифметика, noclip для column-major, быстрый PRNG
Ревью на 32-бит сделан ПО ASM, а не по коду (искали и безымянные
временные): во всём приложении был ровно ОДИН 32-битный вызов —
__mullong в pop_prandom. Замер в MAME: 8 430 тактов на вызов, два
вызова за кадр. Прочие библиотечные вызовы 16-битные (__divsint 16,
__modsint 12, __moduchar 8, __divuchar 5).
1. pop_prandom. Состояние 32-бит -> две 16-битные половины. Два
генератора, выбор через POP_PRANDOM_EXACT:
- 0 (по умолчанию) — xorshift16 + шаг Вейля, без единого умножения;
- 1 — LCG оригинала бит-в-бит, посчитанный половинами (для сверки
картинки с эталоном).
8-битный RND Apple II (5*x+23 mod 256) НЕ взят: у LCG по модулю 256
вырождены младшие биты (бит 0 просто чередуется), а раскладка кладки
берёт как раз prandom(1) и prandom(4) — вместо шума вышла бы
правильная шахматка. Шаг Вейля ещё и убирает ноль как неподвижную
точку xorshift (сид кладки вполне может быть нулём).
Бит-в-бит эквивалентность half-word версии проверена на хосте:
70 000 сидов x 8 шагов + 7 крайних сидов x 2000 шагов.
Остаток 0..maxv: делитель степень двойки — маска вместо __moduint.
2. libbgi: gfx_blit_cols_part_noclip — column-major блит без клипа
(пара к gfx_blit_cols_part, как gfx_blit_part_noclip к
gfx_blit_part). Клипающий вариант платит ~5 622 такта подготовки на
КАЖДЫЙ вызов независимо от того, вылезает край (замер: подготовка
5 622 против 13 596 на сам accel-проход). Kid, страж и клинок
выбирают путь по pop_onscreen_cols. size-check: роста нет.
Бюджет (175 кадров, комната 3, медиана):
было (после клинка) 416 154 0.968 кадра
стало 397 986 0.926 кадра
Разница между генераторами, замер на одинаковой сборке:
xorshift16 + Вейль 397 986 prandom->torch_draw 7 927
бит-в-бит LCG 403 632 prandom->torch_draw 10 933
то есть точность обходится в 5 646 тактов за кадр (1.3 %).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
79ea473910 |
PoP roomtest: страж замечает Кида и достаёт меч (ИИ, шаг 1)
Первый самостоятельный кусок ИИ стража (план, пункт 2). В оригинале у соперника нет своего диспетчера: autocontrol_* (seg002) выставляет те же глобалы control_*, что и ввод игрока, а дальше исполняется общий control(). Инфраструктура под это встала прошлым коммитом, здесь — сама логика. Порт: - check_can_guard_see_kid (seg003:688) — луч видимости по ряду: стены и верхи дверей рвут его совсем, loose/чомпер/дыра/неподнятые ворота дают «вижу, но не пойду». В guards.c (банк 1); - Opp + loadshad_and_opp (seg006:841) и char_opp_dist (seg006:2135); - autocontrol_guard_inactive (seg002:710) + move_* (seg002:0706..); - ветки control(): control_guard_inactive (seg006:2123) и draw_sword (seg005:945) — соперник уходит сразу в seq_90 en garde; - pop_guard_tick перестроен по play_guard_frame (seg000:1246): окно Char/Opp вокруг ИИ, диспетчера и play_seq. По дороге: - Kid.alive не выставлялся (= 0 = «мёртв» в семантике оригинала), из-за чего луч видимости не мог сработать в принципе — ставим -1 в kid_init; - Kid.room не выставлялась вовсе; условие Kid.room == Guard.room всегда было ложным. Ставим в enter_room (полная модель Kid.room != drawn_room у шва по-прежнему впереди); - pop_tile_at — тайл текущей комнаты наружу из pop_map (луч видимости); - control_x/y/shift открыты в шину: ИИ заполняет оси как есть, без flip_control_x (его «вперёд» уже в системе персонажа). Проверено в MAME (комната 3, страж на tile 17): страж переходит из стойки 166 в 171 stand_with_sword, sword=2, can_guard_see_kid=2; ввод игрока не пострадал. Клинок отдельным спрайтом пока не рисуется — sword.atl содержит только кадры 229..237 (подъём меча Кидом), остальные строки sword_tbl приедут с боёвкой. Бюджет (225 кадров, комната 3): 384 168 – 384 636 тактов, 0.893–0.895 кадра, запас 45 364. Прошлый замер 382 584 – 383 064 — шаг стоил ~1 570. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cf06896dbd |
PoP roomtest: окно Char вокруг control() + общая шина ввода — база под ИИ стража
Инфраструктура пункта 2. В оригинале у соперника НЕТ своего диспетчера: autocontrol_* (seg002) выставляет те же глобалы control_*, что и ввод игрока, а дальше исполняется тот же control() (seg005:252). Значит перед портом ИИ надо было привести к этому обе половины: - control_forward/backward/up/down/shift2 перестали быть static в pop_ctrl.c — это общая шина синтетического ввода, объявлена в pop_ctrl.h вместе с POP_CONTROL_*; - control() переименован в pop_control() и работает с Char, а не с Kid; - ввод игрока обёрнут в окно Char (loadkid/user_control/savekid), как в play_frame оригинала. Ловушка по дороге (ввод отвалился целиком, Kid не двигался): макрос seqtbl_offset_char вёл на kid_set_seq, который пишет прямо в Kid, а следом savekid затирал Kid копией Char со старой curr_seq. В оригинале seqtbl_offset_char работает именно с Char — макрос переведён на pop_char_set_seq. kid_set_seq остался для вызовов ВНЕ окна (pop_map). Добавлен pop_savekid_state (Kid = Char без кадра): control() кадр не трогает, а cur_frame в этот момент принадлежит тому, кто последним крутил play_seq. Проверено в MAME: бег и упор в стену работают как прежде. Бюджет (комната 3, 125 кадров): 382 584..383 064 против 380 292..381 282, то есть +2 300 тактов на копии окна. 0.891 кадра, запас 46 936. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8bbc6b4d07 |
PoP roomtest: интерпретатор последовательностей стал общим (Char) — стражи
Пункт 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> |
||
|
|
ac9871c58c |
PoP roomtest: факел анимируется каждый логический кадр (TORCH_ANIM_DIV)
Делитель (torch_tick & 1) занижал скорость пламени вдвое: один логический кадр = 3 vsync и соответствует игровому тику оригинала, а animate_torch (seg007:03C1) меняет кадр КАЖДЫЙ тик. Теперь темп задаётся явной константой TORCH_ANIM_DIV (1 = как в оригинале, 2 = прежнее поведение), счётчик компилируется только когда он реально нужен. Побочный эффект важнее визуального: раньше половина кадров делала работу факелов, половина нет, и бюджет кадра «прыгал». Замер по 100 кадрам до правки: 349 008..371 262, разброс 22 254 такта (6.2 %). После: по 225 кадрам 370 140..371 160, разброс 1020 тактов (0.27 %) — каждый кадр стал худшим случаем, и цифре можно верить. Бюджет сейчас (комната 3, Kid + страж, статика): 0.861..0.863 кадра, запас 58 840 тактов до 430 000. Кроссбанковых вызовов 19 за кадр (~654 такта каждый = 12 400, 3.3 % кадра) — столько максимум вернёт батчинг; профиль вызовов снят breakpoint'ом на ___sdcc_bcall_ehl с печатью HL/E и раскладкой адресов по .map. Замечание по методике: мерить надо ПО МНОЖЕСТВУ кадров. Единичные всплески до 1.5 кадра, которые я сперва принял за проблему движка, оказались наводкой от прерываний мыши на хосте — при неподвижной мыши 225 кадров подряд без единого превышения. Проверено, что --w3 (он остался в sprinter-cc) кладёт в W3 только код и rodata: --dataseg ему не передаётся, глобал --w3 модуля лёг в общий _DATA — сюрпризов при возврате к резиденту не будет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1e6c377edc |
PoP roomtest: pop_bg и pop_map в банки; --dataseg BANKn стал опцией
Резидента --w3 больше нет: отрисовка (pop_bg + pop_gdraw) уехала в БАНК 2, физика/коллизия (pop_map) — в БАНК 3. Куча W1/W2 1294 -> 6750 Б. Что это разблокировало. Резидент был тупиком: из банка он недостижим ни прямо, ни транзитивно, поэтому pop_map (самый крупный модуль, 5.8 КБ) в банк было не увести — он зовёт mob-отрисовку. Проверено, что банк->банк РАБОТАЕТ: ___sdcc_bcall_ehl читает страницу окна портом 0xE2 и кладёт её на СТЕК своего кадра (runtime/bank.s), поэтому вложенность корректна по построению. Подтверждено в MAME цепочкой W1 -> банк1 -> банк2 -> банк1: nested=124 after=8, ровно ожидаемое. Значит развязка mob'а (самое рисковое место, loose-полы) НЕ понадобилась — pop_map зовёт pop_bg трамплином. Правила вызовов проверены на сгенерированном asm и записаны в memory sdcc_banked_call_rules: трамплин выбирает ОБЪЯВЛЕНИЕ (__banked), а не раскладка — даже внутри одного .c между __banked функциями он есть. Внутрибанковые функции оставлены непомеченными и зовутся напрямую, в т.ч. через границу файла (pop_gdraw -> pop_fore_over_char). sprinter-cc: --dataseg BANKn БОЛЬШЕ НЕ ставится по умолчанию. Раньше вся писучая память банкового модуля уезжала в страницу банка и снаружи не читалась (проверено на .map: глобал лёг по 0x0001C000) — грабли на каждом переносе. Теперь данные банков по умолчанию в общем _DATA (W1/W2, замаплен всегда), а прежнее поведение — по явному --bank-data. Замеры (комната 3, Kid + страж; кадр Sprinter в турбо = 430 000 тактов): до переноса 338 508 (0.79 кадра) + pop_bg в банк 2 347 100 (+2.5 %) + pop_map в банк 3 371 100 (+9.6 % к исходному, 0.86 кадра) Плата — трамплины (~654 такта на вызов, ~30 вызовов за кадр). При пейсинге в 3 кадра это 29 % логического кадра, но запас до ОДНОГО кадра всего ~59 000 тактов — под звук его надо возвращать (следующий шаг: батчить кроссбанковые вызовы, начиная с pop_redraw_needed). Профилирование бордюром включено по умолчанию (make PROF=0 выключает) и переведено на реально работающие биты: бит 0 (красный) у бордюра Sprinter ИГНОРИРУЕТСЯ, поэтому различимых состояний четыре и значения обязаны быть чётными — 0 чёрный (ждём vsync), 2 синий (логика), 4 зелёный (фон), 6 циан (спрайты). Раньше нечётные номера сливались и полосы не читались. Проверено в MAME: комнаты 1/3 рисуются как прежде, бег и коллизия работают, в комнате 12 плиты проваливаются со щебнем — то есть цепочка loose банк3 -> банк2 живая. Полосы бордюра в комнате 3: логика 73 строки, фон 64, спрайты 128, свободно 23. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f2093e0d89 |
PoP roomtest: fore-слой — окно клипа, кэш кладки; кадр 2.5 -> 0.78 периода
Жалоба: стойка неподвижных Кида и стража занимала больше полутора
кадровых периодов. Гипотеза «виноват __banked» ЗАМЕРОМ НЕ
ПОДТВЕРДИЛАСЬ: весь банковый вызов (трамплин + смена страницы + тело
pop_guard_tick + возврат) стоит 654 такта при бюджете кадра 430 000.
Как мерил (выборка PC бесполезна — мост MAME отвечает из фреймового
колбэка, все сэмплы падают в обработчик прерывания): breakpoint'ы MAME с
действием {printf totalcycles; g} на входах фаз главного цикла, разности
соседних меток = стоимость фазы. Плюс профилирование полосами бордюра
(make PROF=1, макрос PROF() в roomtest.c) для быстрого взгляда.
Замер комнаты 3 (Kid + страж), такты, кадр = 430 000:
fore поверх стража 431 964
fore поверх Kid 402 816 -> 78 % всей работы кадра
остальное 241 956
ИТОГО 1 076 736 = 2.5 кадра
Две причины, обе устранены:
1. Fore-слой рисовал ЦЕЛЫЕ тайлы, хотя существует ровно для того, чтобы
вернуть куски поверх спрайта — за его прямоугольником в видеопамяти и
так правильный фон. Введено ОКНО клипа (pop_fore_set_clip): спрайт
сообщает свой итоговый габарит (у Kid — с клинком, брызгами и
обрезкой clip_char), fore-проход режет по нему. Отсев трёхступенчатый:
тайл целиком (tile_in_fclip, до обращения к атласу), кусок по грубому
габариту (до gfx_w0_map — w/h лежат в EMM-странице), и точный клип в
blit_b. Для последнего добавлен libbgi-примитив
gfx_blit_part_noclip — пара к gfx_blit_noclip, но под-прямоугольник.
2. Оставшиеся 341 К после клипа оказались НЕ пикселями: 18 вызовов
pop_prandom за проход, ~10 700 тактов каждый (32-битный LCG:
__mullong ~8 000 + __moduint). Раскладка кладки тайла — чистая
функция (комната, ряд, колонка), то есть константа комнаты, а
wall_pattern пересчитывал её каждый кадр. Теперь кэшируются готовые
РЕШЕНИЯ (что рисовать и с каким смещением), 3 байта на тайл, сброс в
pop_room_draw. Порядок вызовов prandom воспроизведён один в один,
включая то, что значение метки берётся только при сработавшем условии.
Итог того же замера: fore поверх стража 37 464, поверх Kid 46 464,
кадр целиком 334 716 = 0.78 периода (было 2.5). Ускорение 3.2x, сами
fore-проходы — 10x.
Проверка отсутствия регрессии: попиксельная разность скриншотов комнат
1/2/3 до и после — отличаются ТОЛЬКО языки пламени факелов (анимация),
кладка и метки совпадают байт в байт.
Побочно: sprinter-cc научился пробрасывать -DNAME в sdcc.
Память: куча W2 1245 -> 996 Б (кэш кладки 120 Б), резидент W3 12 819 ->
14 512 (свободно 1872 Б — становится тесно), банк 1 236/16384.
ВНИМАНИЕ: make size-check показывает рост 7 программ, но эталон
docs/size_baseline.tsv отстал (последний раз принят в
|
||
|
|
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> |
||
|
|
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> |
||
|
|
1dc89b0f26 |
PoP roomtest: ресурсы по каталогам образа (BG/KID/LEVELS), цель make hdd
Все .atl лежали в корне диска рядом с exe — с атласами стражей корень зарос бы окончательно. Теперь ресурсы разложены по каталогам (8.3, как принято в DSS): BG\ фон (env0..4, wall, fore, pot) KID\ персонаж (kid0..27, kid.pal, sword, kid_data.bin) GUARD\ стражи (появятся здесь) LEVELS\ уровни (res2001.bin) make_hdd.sh принимает аргумент вида КАТАЛОГ:файл — создаёт каталог на образе и кладёт файл туда; без префикса файл идёт в корень. В Makefile roomtest появилась цель `make hdd`, которая собирает образ с этой раскладкой (раньше команда набиралась руками на 15 строк). Проверено в MAME: DSS открывает пути вида KID\kid0.atl — комната рисуется, Kid и факелы на месте, то есть все атласы, палитра, таблицы анимации и уровень грузятся из подкаталогов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e1ee447b7f |
PoP roomtest: каркас стражей в БАНКЕ + режим читов (K — убить стража)
Раскладка (по подтверждённой пробником модели, 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> |
||
|
|
ecf5ecfc14 |
PoP roomtest (план v2, шаг 2): общая геометрия в pop_geom
Сведены дубли, разъехавшиеся по модулям: - x_bump[20] был в pop_kid (uint8_t!) и в pop_map (int16_t) — теперь одна таблица int16_t; - y_land[5] — две копии; - y_to_row_mod4 — в pop_bg и pop_map; - 32-битный LCG оригинала (prandom) — в pop_bg и pop_trob; функция теперь одна, а СИДЫ остались раздельными (у раскладки кладки и у фаз факелов свои последовательности, смешивать нельзя — иначе поедет рисунок стен). Экономия по коду скромная (_CODE 24462 -> 24421, W3 11643 -> 11632: часть выигрыша съели межмодульные вызовы). Главное здесь другое: pop_geom лежит в W1/W2 и не трогает графику, то есть это тот самый «чистый» слой, который сможет звать __banked-код стражей (docs/layout_plan_v2.md §4, §5.2). Проверено в MAME: комната 1 после пересборки отрисована ПОБАЙТОВО так же, как до правки (0 различающихся пикселей в области комнаты) — значит последовательности PRNG и геометрия не поехали; Kid бегает. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4cffbc9aa4 |
PoP roomtest (план v2, фаза 1b): pop_map переведён на пометки перерисовки
Loose-полы, плита-потолок и щебень от приземления больше не зовут pop_bg — ставят пометки (pop_set_redraw / pop_set_redraw_above), которые разбирает pop_redraw_needed из главного цикла. Удалены самодельные счётчики loose_bake/loose_rest/ceil_rest/ceil_bake/land_bake: их роль (вторая страница дабл-буфера) теперь у счётчика страниц в пометке. Осталось ОДНО исключение: падающий кусок (mob) — spawn/tick/pos. Это движущийся ОБЪЕКТ, а не перерисовка тайла, и в оригинале он живёт отдельно (mobs + draw_moving), поэтому разделение его на логику и отрисовку — следующая фаза. Из-за него pop_loose_tick остаётся единственной функцией pop_map, которую нельзя звать из __banked-кода; вся коллизия, физика, кромки и предметы — то, что понадобится стражам — чисты от графики. Замер: _CODE 24718 -> 24462, _DATA 4301 -> 4219 (ушли rest-массивы). Проверено в MAME: комната 12, осторожный шаг на плиту — тряска, падение плиты, Kid проваливается на ряд 1, дыра и щебень отрисованы; на замороженном кадре обе страницы дабл-буфера побайтово совпадают в области изменений (7 строк). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
24bb724c22 |
PoP roomtest (план v2, фаза 1a): пометки перерисовки вместо прямых блитов
Порт архитектуры оригинала: логика анимации тайлов НИЧЕГО не рисует, она ставит флаг (set_redraw_full/set_wipe/redraw_20h/redraw_21h, seg007), а отрисовка идёт отдельным проходом redraw_needed (seg008:0178). У нас появился pop_redraw.c/.h: pop_set_redraw(tilepos, вид, страницы) + pop_set_redraw_above(col, ...) + pop_redraw_needed(), который зовёт главный цикл в слое фона (до kid_draw). Отличие от оригинала (наша платформа): счётчик пометки — это ЧИСЛО СТРАНИЦ дабл-буфера (обычно 2), а вид перерисовки хранится явно (heal+поверх или запечь фон), потому что у нас у каждой страницы своя ОЗУ-копия фона. В оригинале вид кодируется тем, в какой из таблиц redraw_frames_* стоит флаг. pop_trob переведён на пометки: пики, кнопки, дверь уровня. Его самодельные массивы spike_rest/button_rest/ldoor_rest/rest_pending удалены — их роль теперь у счётчика страниц в pop_redraw. Прямыми вызовами pop_bg осталось только пламя факела и пузырёк зелья: это не тайловая перерисовка, а покадровый оверлей; из-за них pop_process_trobs остаётся единственной функцией модуля, которую нельзя звать из банка. Зачем: из __banked-кода резидентная страница W3 недостижима транзитивно (docs/layout_plan_v2.md §2 R2), поэтому логика, которую будут звать стражи, не должна вызывать pop_bg. Проверено в MAME: комната 6 — Kid на кнопке-открывалке, решётка в шве поднимается; сошёл с кнопки — закрывается; упал в шахту на пики — пики выдвинулись и отрисованы (кадр 177). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
eef6eebd8c |
PoP roomtest: фикс чтения таблицы кадров — баг кодогенерации SDCC
Кадры Kid читались из EMM-страницы по НЕВЕРНОМУ адресу для индексов >= 52.
Запись
(const uint8_t *)(0x100) + (uint16_t)i * 5u
SDCC 4.5 собрал так: умножение честно в 16 битах (add hl,hl / add hl,bc),
а затем `ld c,l` + `inc b` — то есть взял только МЛАДШИЙ байт результата и
подставил старший байт константы. При i*5 >= 256 адрес уезжал на -256*k,
и cur_frame наполнялся чужой строкой таблицы: у кадров бега/шага/подъёма
пропадал бит FRAME_NEEDS_FLOOR — Kid «вкручивался» в пол и проваливался
вниз, последовательности кадров не соответствовали seqtbl.
Фикс: адрес считается в uint16_t (i*5 = i + i<<2, без умножения) и
кастуется один раз — сгенерированный код теперь сохраняет старший байт
(ex de,hl / inc d).
Коварство бага: тот же паттерн в pop_level.c (room_fg_ptr/room_bg_ptr,
links) компилируется ПРАВИЛЬНО — проверил все три места по .asm. Записано
в memory sdcc_z80_const_ptr_index_bug.
Проверено в MAME на замороженных кадрах: 10 выборок (кадры 4,10,15,49,50,
54,123) — cur_frame совпадает с таблицей во всех, включая те, что раньше
были испорчены.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
3de8c7500c |
PoP roomtest (план v2, шаг 4): в W3-резиденте остаётся только pop_bg
--w3 берёт ОДИН файл на флаг, поэтому запись "--w3 pop_trob.c pop_map.c" означала "W3 = pop_trob", а pop_map всё это время ехал в W1/W2 (в build-каталоге лежал осиротевший w3_pop_map.rel). Теперь список явный. pop_trob переведён в W1/W2: в W3 должно оставаться только то, что банк никогда не позовёт (из __banked резидентная страница W3 не видна ни напрямую, ни транзитивно — docs/layout_plan_v2.md §2 R2). pop_trob же стражам понадобится: в оригинале они тоже давят кнопки. Замер: W3 14376 -> 11643 (свободно 2008 -> 4741 Б), W1/W2 _CODE 21634 -> 24367 (куча 5315 -> 2582 Б). Освободившееся место в W3 — задел под шаг 3 (loose/потолок из pop_map, чтобы pop_map стал bank-safe). Проверено в MAME скриптом: комната рисуется, Kid бежит и тормозит (кадры 15 -> 10 -> 15), факелы анимируются (152 различающихся пикселя между соседними кадрами) — то есть pop_trob работает из нового окна. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
64ce6339eb |
docs: справочники по железу Sprinter + правка gfx_scroll_h
- docs/new/ — сводные справочники (архитектура, BIOS, DSS, память, графика, акселератор, IRQ, порты, ввод, звук, известные баги); - docs/Original/ — первоисточники, из которых они собраны (BIOS, Estex DSS, мануалы, описание акселератора), + Форум.doc/.docx в reference; - libbgi/common/gfx_scroll_h.c — обход бага скролла при ширине >256 (правка автора: шаг банды 255 и продвижение указателей на cw; старый вариант с 256 оставлен закомментированным с TODO); - удалён applications/PoP/roomtest/hang_variants.png — рабочая раскладка из разбора позы виса, в репозитории ей не место. Большие архивы (docs/extra ~568 МБ, docs/sources с вложенными git-репо ~68 МБ) в коммит НЕ включены — см. обсуждение. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
fb495ace42 |
PoP roomtest: вспышка фона стробом + красная вспышка урона (+ урон падения)
1. Жёлтая вспышка подъёма меча была ОДНОЙ длинной заливкой. В оригинале (seg003:0AFC flash_if_hurt / remove_flash_if_hurt) цвет ставится и СНИМАЕТСЯ в том же кадре — пока идёт flash_time, видно быстрый строб «жёлтый/чёрный». У нас теперь так же: цвет ставится в кадре и снимается после ПЕРВОГО gfx_wait_vsync из трёх (≈20 мс жёлтого, 40 мс чёрного — близко к оригинальным 2 тикам таймера). 2. Красной вспышки при потере HP не было вовсе. Порт второй ветки flash_if_hurt: если flash_time не активен, но в этом кадре hitp_delta<0 — do_flash(color_12_brightred) ровно на кадр. У нас триггер — pop_kid_hurt (он же рисует «брызги»). 3. Чтобы вспышке было от чего срабатывать, портирован урон падения из land() (seg005), которого у нас не было: <22 — мягко, <33 — −1 HP и seq_20 (2 этажа), иначе take_hp(100) + seq_22_crushed (насмерть). ВНИМАНИЕ: это меняет геймплей — падение с 2 этажей теперь отнимает HP, с 3+ убивает (как в оригинале). Проверено в MAME: pop_flash_time тикает 5→3→1→0 (строб), падение с ряда 0 на ряд 2 даёт hitp_curr 3→2 и кадр 109 (medium land). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
1c91c5f92a |
PoP roomtest: меч — отрисовка на полу, подъём по Shift, статус have_sword
Комната 15, тайл (2,2): меч не рисовался вовсе — в draw_tile не было
ветки tiles_22_sword (seg008 draw_tile_anim):
add_midtable(chtab_1, (modifier == 1) + 10, draw_xh, 0, draw_main_y - 3, ...)
Спрайты уже лежали в нашем pot-атласе (chtab_1 пакуется целиком, id 1..23),
так что понадобился только вызов. У нас предмет рисуется статикой в фоне,
а не в midtable: пока меч лежит, он не анимируется, а Kid и так поверх фона.
Подъём — порт цепочки оригинала:
- check_get_item/get_item (seg005:061F/073E) -> pop_get_item_action() в
pop_map (тайлы): стоя НА предмете отступить на тайл назад, подровняться
по кромке и присесть; из приседа над мечом — поднять;
- do_pickup (seg006:1671): тайл -> пол, pop_item_taken = tilepos+1;
roomtest запекает пол на ОБЕИХ страницах (pop_floor_bake) и пишет
per-room override, чтобы меч не воскресал при возврате в комнату;
- триггер — Shift (control_shift2) в стойке и в приседе, как в
control_standing/control_crouched;
- seq_91 pickupsword: опкод SEQ_GET_ITEM с аргументом 1 в play_seq теперь
зовёт pop_proc_get_object (seg006:16CB) -> pop_have_sword = 1.
Статус: pop_have_sword. Боевой режим (стойка с мечом, бой) НЕ делаем и
он тут не нужен: seq_91 сам заканчивается убиранием меча в ножны
(кадры 230..240) и возвратом в обычную стойку — как в оригинале до
встречи со стражем. Питьё зелий не портировано: над зельем Kid только
приседает (get_item возвращает «не обработано»).
Проверено в MAME (комната 15): меч виден на полу, Shift над ним даёт
присед -> кадр 229 «нашёл меч» -> ножны -> стойка; меч с пола исчезает,
pop_have_sword = 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
6769b6a01b |
PoP roomtest: loose-плита впереди = КРОМКА в get_edge_distance
Осторожный шаг к проваливающемуся полу проваливал Kid с первого же шага:
в pop_edge_distance не было ветки оригинала (seg004:067C)
if (tiletype == tiles_11_loose) goto loc_59FB; // CLOSER + до кромки
— плита читалась как обычный пол (EDGE_FLOOR, 11), и весь механизм
«проверки ногой» пролетал мимо. С веткой safe_step (seg005) отрабатывает
три фазы, как в оригинале: (1) укороченный шаг РОВНО до кромки плиты
(seq 29..42 по distance), (2) seq_44 testfoot — щуп ногой, плита трясётся
по SEQ_KNOCK_DOWN, Kid остаётся на месте (Char.repeat), (3) только третье
нажатие — шаг на плиту и падение вместе с ней.
Заодно портированы соседние ветки той же функции: верх двери лицом вправо
(проверяется ДО wall_type) и closer/меч/зелье (кромка, пока есть зазор).
Проверено вручную в MAME: все три фазы.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
5f5eefc9fc |
PoP roomtest: loose-плита поверх Кида (порт draw_loose + draw_tile_base)
Комната 12, вис и подтягивание на кромке loose-плиты над дырой от соседней упавшей: плита рисовалась ПОД Кидом — он лез на неё «с переднего края» вместо проёма. Не хватало двух кусков draw_tile: 1. draw_loose (seg008:0A38) кладёт нижнюю грань плиты (loose_fram_bottom) В ОБЕ таблицы — backtable И foretable, безусловно. Это единственный кусок тайла с таким поведением: у обычного пола bottom идёт только в backtable, а fore_id = 0. Добавлено в fore_tile (наш проход foretable по тайлам футпринта Кида); ceiling-случай это уже делал отдельно. 2. draw_tile_base (seg008:0A8E) подставляет id: у loose верх плиты берётся из loose_fram_left, у opener'а без пола слева — 148. В нашем midtable-оверлее (overlay_mid_tile) стоял голый tile_table.base_id, а у loose он 0 — верх плиты в оверлей не попадал, и поверх Кида ложилась только передняя грань. Перенесён draw_tile_base целиком. Проверено в MAME: кадр виса на кромке целой плиты (frame 89, x=95, col 2) — плита закрывает Кида, наружу торчат только пальцы, как в SDLPoP. Голова стоящего Кида поверх падающей НА НЕГО плиты — артефакт САМОГО оригинала (сверено с SDLPoP v1.24), не чинить: записано в bug_list.md (раздел «НЕ БАГИ») и в memory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
517d225091 |
PoP roomtest: полный порт bumped (seg004) — прижатие Y к полу + hardbump
Комната 5, прыжок в решётку: Kid оставался стоять на 6 пикселей выше
пола и без приземления-приседания. Шесть пикселей — это dy(-6) кадра
frame_25_standing_jump_10: удар обрывал standjump ровно между кадрами
25 и 26, парный dy(+6) не выполнялся.
От оригинального bumped() у нас был портирован только хвост (выровнять
X к грани + seq_47_bump). Добавлены недостающие ветки:
- bumped() — исход удара выбирается по тайлу, НА КОТОРОМ персонаж
оказался после отжатия (сквозь стену/верх двери это
соседняя клетка, для ворот/зеркала — сама клетка);
- bumped_floor — прижимает Y к y_land, ветка fall_y>=22 (только отжать
на 5) и выбор сиквенса по кадру: 24/25/40..42/102..106
-> seq_46_hardbump (отскок с приседанием), иначе 47;
- bumped_fall — удар выше пола на 15+ px (беззнаковое сравнение
оригинала) или не над полом: seq_45, в свободном
падении — только гашение fall_x.
Гард «уже в отскоке — не рестартить» (наш, от edge-триггера коллизий)
перенесён внутрь bumped_floor: выравнивание X/Y идёт всегда, рестарта
анимации нет. Проверено в MAME.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
fc69316c2c |
PoP roomtest: окклюзия падающей плиты соседним полом — ряд из координаты
Комната 1, плита (2,6): её правая грань (env 42, лежит целиком в ячейке 7)
оставалась ПОВЕРХ пола (2,7) и перекрывала его переднюю грань.
Причина: mob_render перерисовывал соседний тайл по m->row — а это
ЛОГИЧЕСКИЙ счётчик «сквозь какой ряд летим», который mob_down_a_row уводит
на ряд вперёд. Для плиты НИЖНЕГО ряда он сразу становится 3, и окклюзия
звала draw_tile(3, col+1) — ряда 3 нет, тайл рисовался за нижним краем
экрана, то есть пол соседа не перерисовывался никогда.
Оригинал берёт ряд ИЗ КООРДИНАТЫ куска, draw_mob (seg007:13E5):
tile_row = y_to_row_mod4(ypos); set_redraw2(tilepos справа)
top_row = y_to_row_mod4(ypos - 18); если отличается — ТОЖЕ пометить
То есть помечаются ДВА тайла справа: под низом куска и под его верхом, пока
кусок висит на границе рядов. Второго у нас не было вовсе — и именно он тут
решающий: при y=194 нижний ряд даёт -1 (полоса у потолка), а верхний — 2,
то есть настоящий пол (2,7).
Проверено в MAME покадрово (bp на pop_loose_mob_spawn + cmd gv; NB: главный
цикл ждёт ТРИ vsync на итерацию, один gv = треть игрового кадра): следов
плиты на полу (2,7) нет, передняя грань цела, потолочная полоса не тронута.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
08ebf09d4d |
PoP roomtest: пометить недостижимые комнаты уровня 1 (13, 18, 24)
Обход графа связей res2001.bin (links @1952) от стартовой комнаты 1: 13, 18 и 24 недостижимы — ссылки наружу у них есть, на них не ссылается никто (24: L->9, но у 9 R=0). Несимметричные ссылки ровно у этих трёх, у прочих 21 симметрия полная — признак выкинутых из компоновки комнат. В таблицу обхода добавлена пометка, чтобы не гоняться за призраками: рендер кромки читает колонку соседа ПО ССЫЛКЕ, а сосед этих комнат соседом себя не считает, так что странный шов там — свойство данных. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
08c6a8504f |
PoP roomtest: отладочный обход комнат (+/-) + TODO по редрою пик и idle-skip
ROOMNAV (одна строка #define в roomtest.c): '+'/'-' — следующая/предыдущая комната уровня ПО НОМЕРУ (1..24, с обёрткой), Kid ставится на первый пол, pop_trob_reset() возвращает пики/ворота в исходное. Нужен для обхода всех 24 комнат в поиске багов отрисовки, включая недостижимые обычным путём. Цена — 505 Б кода W1 (22816 -> 23321), они же вычитаются из кучи (4178 -> 3673 Б); W3 не тронут. Переводить приложение в huge ради этого не стали: смена модели памяти (банкованный W1 + трамплины) ради полукилобайта — плохой размен прямо перед прогоном комнат. Номер комнаты — ПОЛОСКАМИ в верхнем борте (слева десятки, справа единицы), а не outtextxy: текст тянет системный знакогенератор (_gfx_font_buf, 2 КБ статики) и сажает кучу до 576 Б. Цвет полосок 0x57, а не WHITE: палитра игровая, запись 15 в ней ЧЁРНАЯ (kid.pal 0x0F = 0,0,0) — из-за этого закомментированный ранее лейбл был бы невидим в любом случае. bug_list.md: раздел TODO (T-1 редрой пик по причине, T-2 idle-skip) + пустая таблица на 24 комнаты под результаты обхода. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
b106aba59b |
PoP roomtest: подъём/спуск на ОТКРЫТЫХ воротах (потерянная проверка openness)
Kid не мог залезть на тайл поднятой решётки: срывался обратно. can_climb_up (seg005:09DF) выбирает seq_73 («влез под решётку и съехал») только для ЗАКРЫТЫХ ворот — условие включает curr_room_modif[curr_tilepos] >> 2 < 6, а у нас его не было, и seq_73 играл при любой решётке над головой при взгляде влево. Та же дыра нашлась в спуске: down_pressed (seg005:482) запрещает climb-down с тайла ворот лицом влево тоже ТОЛЬКО пока они закрыты (|| curr_room_modif[curr_tilepos] >> 2 >= 6). Без этого Kid приседал вместо спуска даже под поднятой решёткой. Openness обеих проверок берётся общим хелпером gate_modif(col,row) (вынесен из gate_passable — умеет свою комнату и соседнюю через шов). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
6582154381 |
PoP roomtest: clip_char (верхняя обрезка спрайта) + подстановка нажатой кнопки
Спуск Кида с кнопки (room8, кромка (0,6)) рисовался неверно: Кид просвечивал в щель между кнопкой и ближним столбом, а ближняя рука была срезана до одного оторванного пикселя. Две независимые причины. 1. Не был портирован clip_char() (seg006:1749) — оригинал перед add_objtable обрезает спрайт персонажа сверху по y_clip[curr_row+1], когда тайл над головой стена или пол. Порт: pop_clip_char_top() (pop_map.c, метрики по set_char_collision) + новый примитив gfx_blit_cols_part() в libbgi (блит column-major с пропуском skip верхних строк; обрезка сверху бесплатна — колонка непрерывна в ОЗУ, сдвигается только старт). gfx_blit_cols стал тонкой обёрткой над ним. kid_heal чистит уже ОБРЕЗАННЫЙ прямоугольник, иначе стирается кромка пола над срезом. 2. climb_overlay_tile (порт draw_floor_overlay, seg008:1E3A) выбирал ветку по СЫРОМУ коду тайла, а get_tile_to_draw (seg008:240) подменяет нажатую кнопку на floor/stuck. Тайл-кнопка не проходил тест floor → уходил в draw_other_overlay, который кладёт поверх Кида ВЕСЬ тайл вместо узкой кромки floor_left_overlay[frame-137]. Фикс — tile_code_drawn(): одна подстановка на все слои (fore_only_tile/overlay_mid_tile перестали её дублировать, W3 −293 Б). Проверено покадрово в MAME (шаг gv + снимок): кадры спуска 148..138 чистые, после приземления на кнопке мусора нет. Заодно: gfx_heal_noclip() (пара к gfx_blit_noclip; heal 11658 -> 8982 тактов) и rest_pending в pop_trob — холостой проход по 30 тайлам только когда есть отложенные редрои пик/кнопок (pop_process_trobs 89-111К -> 70-92К тактов). START_ROOM временно = 8 (отладка спуска с кнопки), вернуть на 6/старт уровня. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
63a3b60524 |
PoP roomtest: плита-потолок, порядок слоёв по SDLPoP, фиксы окклюзии и Y
Фича — плита-ПОТОЛОК (loose ряда 2 комнаты сверху, комн.5 (2,5) над комн.6): - check_press (seg006:1683) портирован целиком: кадры виса 87..99 и подъёма 135..140 «нажимают» тайл, за который Kid держится; кадр 79 — удар снизу (с проверкой action, как в оригинале); стояние — тайл под ним; - ряд −1 в pop_map.get_tile = ряд 2 комнаты сверху (порт find_room_of_tile), состояние тряски/падения — pop_ceil_modif[10]; do_knock(-1) трясёт потолок; - падение: mob переведён на координаты оригинала (y_loose_land/y_something), спавнится в ряду −1, по move_loose находит пол → loose_land: debris + do_knock; heal коридора — по прошлой позиции ДЛЯ КАЖДОЙ страницы; - урон: check_loose_fall_on_kid + fell_on_your_head (seq_52/seq_22, −1 HP) и «брызги» draw_hurt_splash (kid image 218); - колодец наверх: leave_room «вверх» проверяется ПЕРВЫМ (иначе y≈248 при подтягивании ловил check_leave_below и ронял Kid из уровня); - персистентность: общий tile-override (щебень внизу, пустота в комнате сверху) вместо debris-only. Окклюзия — приведена к оригиналу (найдено трассировкой собранного SDLPoP): - draw_tile_right/anim_right/draw_loose кладут спрайты в add_backtable НАПРЯМУЮ, минуя ptr_add_table → правая грань соседа («шахматка» столба 93), blueline и грани loose/пик соседа ВСЕГДА под персонажем. draw_other_overlay поверх Kid даёт только: 42 при левом соседе-поле, base, свои пики, bottom (overlay_mid_tile); - порядок объектов: тайл Kid (set_objtile_at_char) и порядок обхода (ряды 2→0, колонки 0→9), внутри тайла — сортировка по y (sort_curr_objs); то же правило для падающей плиты (pop_loose_mob_draw_over); - fore-слой рисуется ПОСЛЕ оверлеев (foretable после midtable), иначе тёмный скос floor_left_overlay ложился поверх колонны; - fore_tile больше не рисует bottom_id (в оригинале он в backtable) — минус 2..4 блита за кадр; - полоса потолка поверх Kid = draw_tile_bottom(1)+draw_loose(1)+draw_tile_fore (arg_0=1 добавляет в foretable), остальное — под ним; - следы оверлея восстанавливаются на ОБЕИХ страницах (pop_fore_heal). Прочее: - control(): при action bumped/in_freefall управление игнорируется целиком (seg005) — иначе прерванный присед терял dy(1)+dy(1) из medland и Kid навсегда оставался на 2px выше пола; - check_bumped: стена ищется по колонке переднего края (а не curr_col) + гард разворота — Kid больше не проходит сквозь опущенную решётку шва; - pop_floor_bake для смены floor→debris (bake_empty стирал верх стены ряда ниже — чёрный прямоугольник); - в pop_room_redraw_seam_left восстанавливается полоса потолка над решёткой. Проверено в MAME (bridge); эталон — собранный SDLPoP из applications/PoP/SDLPoP. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
d1bc97c589 |
roomtest: обломки упавшего loose в комнате снизу (debris)
Loose-пол room1(2,6) при падении даёт кусок, который в оригинале улетает в комнату снизу (links.down) и приземляется на первый floor, превращая его в debris (порт move_loose/loose_land seg007). Для room1(2,6): room2(0,6)=empty пролёт → room2(1,6)=floor приземление → debris. Минимальная реализация (без анимации полёта куска, только персистентный результат — мини-версия P0 из docs/gates_spikes_plan.md): - pop_map: сигнал pop_loose_fell (tilepos+1 упавшего loose). - pop_level: pop_room_col_landing(room,col) — ряд первого floor сверху вниз (пустые ряды пролетаются), иначе -1. - roomtest: при pop_loose_fell вычисляет debris-приземление в комнате снизу → персистентный override (dbr_room/dbr_pos, живёт между переходами) → enter_room применяет поверх загруженных тайлов (floor→debris 0x0E). Проверено в MAME: провал loose room1(2,6) → debris в room2(1,6), сохраняется при повторных входах. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
bf7ddfd4da |
roomtest: L3 переходы вбок (право+лево) + план объектов (кнопки/гейты/пики)
Переходы в соседние комнаты по горизонтали (порт SDLPoP leave_room/ goto_other_room/find_room_of_tile), проверено в MAME (room2↔room3↔room9, room1↔room5 и т.д.): - pop_level: pop_room_load теперь извлекает и rightcol (col0 правого соседа), как leftcol — для коллизии/рендера правого шва. - pop_map: pop_map_set_edges(l,r,u,d, lcol_fg, rcol_fg) — связи + кромки швов. get_tile(col=-1)=lcol (col9 левого), get_tile(col=10)=rcol (col0 правого), с гейтом по связи (нет соседа → стена) — порт find_room_of_tile. check_leave (порт leave_room): передний край char_x_left<=54 / char_x_right>=201 (и обратные <=57 / >=198) → x∓140, сигнал pop_leave_dir (1=left,2=right). - roomtest: rcol-массивы; enter_room задаёт edges; обработчик pop_leave_dir переключает комнату по pop_room_link. - Фикс двойного перехода: без коллизии ЛЕВОГО шва Kid, войдя справа в соседа (curr_col=-1), стоял «в стене» → in_wall выбрасывал его на второй переход. Левый шов (get_tile(-1)=lcol) это чинит. docs/gates_spikes_plan.md — ПОДРОБНЫЙ самодостаточный план на след. сессию: интерактивные объекты (RAISE/DROP-кнопки, гейты, пики) + HP/смерть. Контекст текущего roomtest, форматы уровня (LINKLOC/LINKMAP @1440/1696), данные room6, декод связи кнопка(0,2)→гейт room8(0,9), триггер пик (check_spike_below), точные ссылки SDLPoP, фазы P0(персистентное per-room состояние+trob)/ S(пики+HP/смерть)/B(кнопки+гейты). NB: L3-вверх (climb-up в комнату сверху) ещё не сделан. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
7f3e32ddd6 |
roomtest: L2 — переход в комнату снизу (провал/спуск) + фиксы окклюзии
При пересечении нижней границы комнаты Kid переходит в комнату links.down, продолжая падать/спускаться (порт SDLPoP goto_other_room/leave_room). - pop_map check_leave_below (порт leave_room, seg002): триггер по Kid.y>=211 (не curr_row) — единый для провала И спуска-зацепа/climbdown; репроекция координат в кадр комнаты снизу (y-=189, curr_row=y_to_row_mod4) + сигнал pop_fell_out. Убран прежний преждевременный триггер y>=189 из do_fall (из-за него climbdown с curr_row=3 ждал y_land[4]=244 → пролёт через комнату). - roomtest: enter_room(room) — загрузка комнаты в рабочие массивы + отрисовка фона в обе страницы; обработчик pop_fell_out переключает комнату по pop_room_link(down) или респавнит (нет комнаты снизу). cur_room-трекинг. - pop_loose_reset (pop_map) + pop_loose_mob_reset (pop_bg): сброс loose-состояния (индексировано позицией тайла) при смене комнаты — иначе течёт в новую. Фиксы окклюзии при висе/спуске (сверено с SDLPoP): - other_overlay_tile: пустой тайл НЕ окклюдирует — draw_tile для него рисует лишь фоновую сетку (BLUELINE «силуэт кладки»), которая в оригинале уходит в y-сортируемый midtable ПОЗАДИ Kid; у нас клалась поверх. Пропускаем. - pop_fore_over_kid: порт set_char_collision (seg006:0723) — char_bottom_row = y_to_row_mod4(obj_y), обёртка -1 → ряд 3 (Kid НИЖЕ комнаты при спуске), а не ряд 0. Прежний кламп в 0 давал rT>rB → цикл окклюзии не выполнялся, нога рисовалась поверх пола. single-room: ряд 3 клампится в 2 (передняя грань пола). Проверено в MAME: провал вниз, спуск-зацеп/climbdown с кромки, окклюзия ноги. Известно (позже): осколки упавшего loose в комнате снизу (перенос mob), per-room modified-tile tracking (re-entry восстанавливает исходные тайлы), HP. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
21f978d441 |
roomtest: L1 — данные уровня из сырого res2001.bin вместо хардкода
Отказ от room1_data.h: уровень читается из сырого blueprnt DAT 1.0 (applications/PoP/SDLPoP/data/LEVELS/res2001.bin, формат — Table 6 POP-DAT). - pop_level.c/.h: уровень грузится ОДИН раз в отдельную EMM-страницу (данные с offset 0x100, ISR-стаб в первых байтах — страница безопасна для W0-маппинга, как атласы). pop_room_load(room) извлекает комнату в массивы приложения (W2): fg[30] (тайл-код = байт & 0x1F, верхние биты-модификаторы пока отброшены, как делал render_room.py), bg[30] (raw) + срезы соседей для кромок — правый столбец left-комнаты (leftcol), верхний ряд down-комнаты (belowrow). pop_room_link() — связи для будущих переходов (L2/L3). - Рабочая копия ТЕКУЩЕЙ комнаты — в обычной памяти (W2, мутабельная: loose→ empty); страница уровня маппится в W0 только на время извлечения. - Проверено в MAME: комната 1 рисуется идентично. Данные сверены байт-в-байт (fg&0x1F, bg raw, leftcol из room5 совпали); belowrow теперь из реального room2 (links.down=2), а не из прежнего частично-выдуманного хардкода. - Память (small): DATA до 0xA260, стек от 0xBFFF — запас ~7.5КБ; EMM-страница уровня вне W1/W2. NB: res2001.bin берётся из клона SDLPoP (data/ в .gitignore) — как и генерация атласов через pop_pack_bg.py; в наш репозиторий сырой уровень не тащим. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
36a5a5e194 |
roomtest: фикс провала сквозь пол при беге с кромки (порт start_fall/in_wall)
Тап-бег с кромки [1,3] вправо -> Kid проваливался в стену col2/3 -> респавн, вместо посадки на [2,4]. Сверено с SDLPoP seg006: - start_fall: frame 9 -> seq_7_fall (был ошибочно seq_19 как у 13); frame 13 -> seq_19 (лишний dx(1)). + хвост seg006:1099: тайл ПЕРЕД персонажем — стена -> Char.x = char_dx_forward(-1); - in_wall переписан аутентично (seg006:1292): выталкивает ВПЕРЁД из стены (delta+4) на соседний тайл, а не назад вглубь (наш старый через dist_from_wall_forward давал отрицательный сдвиг -> Kid уходил в стену col2 -> проваливался). distance_to_edge_weight + 6-delta/+4. Проверено в MAME (пользователь): бег с кромки [1,3] -> посадка на [2,4]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
237f780e0e |
roomtest: fore-окклюзия пола/стены над Kid (порт redraw_at_char/char2)
Kid — высокий спрайт: голова/руки торчат в ряд выше опорного, floor/wall там должны перекрывать его. Сверено с SDLPoP (seg003 redraw_at_char2 + seg008 draw_tile_fore/draw_other_overlay/draw_floor_overlay): - fore_tile (draw_tile_fore): стена рисует и WALL_FRAM_BOTTOM (нижняя грань-решётка), не только MAIN — руки при прыжке в потолок [2,7]->[1,7] уходят за стену; - диапазон рядов форсит включение ряда над опорным (y_to_row верха спрайта мог схлопнуться из-за +60-сдвига); - other_overlay_tile (draw_other_overlay): на КРОМКЕ пола (сосед слева пуст) перерисовать весь тайл поверх Kid — но ТОЛЬКО в позах захвата (78-79), виса (action 2/6), полёта (3/4), старта падения (bumped 102-106) и начала подъёма (135/136, до SEQ_UP на 141 Kid ещё в ряду ПОД полом). Гейтинг как в redraw_at_char2 — при стоянии/беге ложной окклюзии нет; - отладочный стоп-кадр: '1' заморозить / '2' продолжить (для разбора поз). Проверено в MAME (пользователь): прыжок-в-потолок, короткий прыжок, спрыгивание, подъём на [0,3] — окклюзия корректна. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
023b45eb85 |
roomtest: двойная буферизация (два экрана + флип), тумблер SPACE
Убирает мерцание/тиринг при перерисовке слоёв (Kid/fore/пол-оверлей): рендер всего кадра в скрытую страницу, tear-free флип на vblank. Инфра libbgi (gfx_set_draw_page/visible_page/wait_vsync) уже была. - фон комнаты рисуется в ОБЕ графические страницы (у каждой своя ОЗУ-копия — источник heal); палитра 0->1 уже синкалась gfx_pal_sync; - kid_heal/kid_draw: прямоугольник Kid запоминается ПО СТРАНИЦЕ (kid_l*[2]) — при чередовании страниц heal стирает пиксели своей страницы (прошлый Kid там был 2 логических кадра назад); - цикл: draw в back, 3x wait_vsync (пейсинг), gfx_set_visible_page(back); - SPACE (edge) — тумблер: off = однобуфер (draw==visible==0) для отладки. Мерцание при подъёме подтверждено устранённым в MAME (пользователь). План: applications/PoP/docs/double_buffer_plan.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
3c0baacbf6 |
libc/kbd: recovery по Rx-overrun SIO (залипание клавиш) + kbd_raw_sync
Симптом (интермиттентный): при отпускании shift+стрелка иногда стрелка залипает. Диагностика: на чистом одновременном release break-коды обрабатываются верно (проверено MCP) → drain-логика ISR корректна. Остаточное залипание = переполнение 3-байтного аппаратного FIFO SIO при пачке скан-кодов (F0 12 E0 F0 74 = 5 байт) во время длинных DI-окон → потерян break → залипание. Фикс: трамплин после drain читает RR1 SIO (бит5 = Rx Overrun), при overrun делает Error Reset (WR0=0x30) и взводит _kbdraw_overrun. Новый kbd_raw_sync() (звать раз в кадр) по флагу сбрасывает всё held-состояние _kbdraw_down (какой break потерян — неизвестно; реально зажатые перечитаются). pop_ctrl_tick зовёт kbd_raw_sync(). Буфер W2- трамплина 288→320 (трамплин 244 Б). ВНИМАНИЕ: путь overrun НЕ проверен детерминированно (баг интермиттентный, зависит от тайминга DI) — ТРЕБУЕТ ПРОВЕРКИ на железе/в длинной сессии. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
419d5e4eff |
applications/PoP/roomtest: фикс ухода Kid в стену при прыжке с кромки
Баг (из динамики): стоя на кромке [1,3]/[1,4] лицом вправо + Up, Kid телепортировался/застревал в стене col8. КОРЕНЬ (трасса MCP): jumpup(seq_14) на кромке → приземление над ямой → падение; на кадрах падения action=3 (midair) check_bumped был НЕ заглушён (guard покрывал только freefall/hang/climb-кадры). Kid дрейфовал к стене, get_tile вне рядов давал WALL, dist_from_wall_forward/x_bump[] — мусор с большим отрицательным сдвигом → Kid.x=char_dx_forward((int8_t)d) → underflow uint8 X (0→255) → долёт до col8 и застревание в стене. Фикс check_bumped: заглушить и в action MIDAIR (кадры падения), и при curr_row вне [0..2] (падение мимо пола — тайлы вне комнаты = WALL, bump-мусор; fell_out ловит do_fall). Проверено в MAME: на кромке Kid больше НЕ уходит в стену — чисто падает (в тесте сброс на старт). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |