4737ec323c8617d8beb6699c784e2017e22b2ed1
83 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4737ec323c |
MEM-BANK5: pop_ctrl.c → банк 5; чит [/] подгонки Кида по X
Три правки едут вместе намеренно: n_banks и --bank обязаны меняться атомарно, иначе промежуточный коммит — зависание (см. ниже). MEM-BANK5. CODE и DATA делят одно 32-КБ пространство W1+W2, поэтому килобайт кода, уехавший в банк, — это килобайт, доступный данным. Кандидат выбран не по размеру, а по частоте вызова: диспетчер управления дёргается раз в кадр на персонажа и горячих банк→банк переходов не создаёт (в отличие от pop_level, чей pop_level_tile зовётся из банка 2 на КАЖДЫЙ тайл). _CODE 26 780 -> 24 662 Б (−2 118) куча 180 -> 2 298 Б банк 5 2 211 / 16 384 (13.5 %) Шина control_* (8 глобалов) переехала в pop_state.c. Сегодня она уцелела бы и в pop_ctrl.c — банки собираются без --bank-data, их писучие данные остаются в общем _DATA, — но это флаг сборки, а не свойство кода, а шину трогают уже три банка: 5 пишет с клавиатуры, 1 подаёт синтетический ввод ИИ (autocontrol_*, seg002), 3 читает через pop_ctrl_shift_held. Заодно pop_ctrl.c наконец включает собственный заголовок — раньше объявления жили прямо в нём. ГРАБЛИ, на которые наступили (стоили дольше самой задачи): n_banks в roomtest.c захардкожен, и его надо править вместе с числом --bank. С n_banks=4 и пятым банком crt0 выделил четыре страницы, _bank_pages[5] остался нулём, и первый же вызов pop_ctrl_init() через трамплин отобразил в W3 страницу 0 и прыгнул на 0xC874 в мусор — исполнение забрело в дисковый код DSS и осталось крутить чтение секторов. Симптом: загрузка ресурсов проходит целиком (open=45 — все атласы), комната и Кид успевают нарисоваться из enter_room, а HP и номер комнаты уже нет, и kid_tick не вызывается ни разу. Ровно предупреждение из шапки runtime/bank.s. Сверку n_banks с реальным максимальным индексом банка записал в docs/TODO.md (Auto-banking) — это должно быть ошибкой сборки. DBG-CHEATS: [ (0x54) и ] (0x5B) двигают Кида на пиксель (seg000:1828), по фронту нажатия, под pop_cheats. Нужны потому, что мост MAME теряет нажатия при быстрой отправке и подогнать Кида в позу скриптом нельзя — на это упёрлись BUG-LOOSE-2 и BUG-GATE-PASS-1. ROOMNAV больше не зовёт pop_trob_reset: reset обнуляет room_seen, то есть чит ОТМАТЫВАЛ МИР (открытые/закрытые ворота, нажатые кнопки). Навигация обязана только телепортировать. Проверено в MAME: старт уровня 1 рисуется полностью (комната, Кид, HP, номер), бег вправо и падение на второй ряд отрабатывают, ] даёт x+1 и [ даёт x−1 по одному нажатию, Shift+→ — осторожный шаг (x 131 -> 142, колонка 4 -> 5) и при удержании 90 кадров не срывается в бег (x 142 -> 151), то есть pop_ctrl_shift_held работает через границу банк 3 -> банк 5. Наборы под ucsim: geom 39, grab 53, phys 1723 — все зелёные. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cfc3602375 |
L2: падение с мечом, разбег-прыжок через 3 тайла, стартовое состояние ворот
BUG-FALL-SWORD-1 — start_fall (seg006:1044) был портирован не целиком: не хватало трёх веток выбора последовательности и уборки меча в ножны. Из-за seq_7 с set_fall(1,15) (дрейф 1 px/кадр) Кид с мечом уезжал примерно на тайл вбок; в оригинале это seq_81_fightfall — падение строго вниз. Сверено по логу SDLPoP: кадры 102..105 дают x = 155,157,159,160. BUG-RJUMP-1 — run_jump (seg005:0AA8) не выравнивал Кида по кромке пола перед толчком, а был заглушкой «полировка K3». Суммарный dx seq_4 — 62 px при тайле 14, то есть провал ровно в три тайла берётся ТОЛЬКО с кромки: без выравнивания Кид не перепрыгивал его никогда. Порт — pop_run_jump_align() в pop_map (беззнаковое сравнение оригинала = «сдвиг не попал в [-8,-1]»). На харнессе: было — толчок с x=165, кадр 44 в колонке 3 (провал); стало — 5 кадров добега, толчок с x=149, кадр 44 даёт x=87 col=1 row=1. Остальные 8 сценариев не изменились. BUG-GATEMOD-1 — load_alter_mod (seg008:198E) был портирован только для зелий, поэтому ворота с bg=1 («Open» по спецификации DAT, табл. 8) стартовали закрытыми. Добавлены ветки gate (1 -> 188) и loose. Ветка wall намеренно НЕ портируется: связи стен наш pop_bg считает по типам соседей в момент отрисовки. На уровнях 1-3 таких ворот всего двое (ур. 1 комн. 5 (0,9); ур. 2 комн. 13 (1,5)) — у остальных bg=2, а 2 и 0 ведут себя одинаково. Убран временный трассировщик pop_dbg_trace/pop_dbg_draw; pop_dbg_trap() оставлен как многоразовый инструмент. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3fe083331f |
tests-host: покадровый харнесс сценариев Кида (физика + зацеп)
Проверять физику Кида глазами в MAME дорого и ненадёжно: ошибка почти всегда не в одной функции, а в РАСХОЖДЕНИИ ТРАЕКТОРИИ через несколько кадров. Харнесс гоняет тот же кадр, что и главный цикл (pop_ctrl_tick -> kid_tick -> pop_phys_tick -> pop_loose_tick), и сравнивает трассу состояния с эталоном. - scene.c/.h — раннер: комната + стартовая поза + скрипт ввода -> трасса; sc_kid_at_x задаёт точный X (исход часто зависит от фазы внутри тайла). - stubs.c/.h — libc/libbgi/соседние модули; read() реально отдаёт kid_data.bin (иначе kdat_ok=0 и play_seq молчит — трасса замирает). - t_phys.c — 9 характеризующих сценариев, 1723 сверки (golden/). - t_grab.c — окно зацепа: существует, достижимо коротким шагом, не зависит от рисунка нажатий. - record_golden.py — снятие эталона по одному сценарию за прогон. - testkit/host-tests.mk — CODE_LOC настраиваемый, EXTRA_INC/EXTRA_CFLAGS. Именно харнесс дал доказательство, что физика зацепа у нас верна, и тем самым перевёл поиск BUG-GRAB-1 на клавиатуру. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
29d60665e0 |
docs: уточнение — при падении с мечом рендер не «кривой», а корректный
Отличие от остальных записей раздела «НЕ БАГИ»: там картинка кривая и совпадает с оригиналом лишь потому, что оригинал сам так рисует (порядок midtable). Здесь же спрайт перекрывает кромку ровно настолько, насколько персонаж за неё зашёл — геометрия и рендер согласованы. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
969f3f1b9a |
docs: падение при отходе с мечом — НЕ БАГ (сверено с SDLPoP)
Пользователь проверил в SDLPoP v1.24: оригинал падает с той же позиции, по той же траектории и с тем же видом кадра падения (голова/руки поверх кромки пола). Кадры совпадают один в один. Механика записана с числами: у стоек с мечом weight_x = 13-14 против 3 у обычной стойки, поэтому при взгляде влево точка веса уезжает на 13 px вправо от Char.x, и кромку персонаж переступает раньше, чем выглядит. Замер: x=151 -> dx_weight=164 -> колонка 7 (дыра) вместо 6 (пол). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3164065243 |
Клинок расширяет футпринт перерисовки на колонку (redraw_at_char)
Порт seg003:0430 «If char is holding sword, it makes redraw-area bigger»: при Char.sword >= sword_2_drawn футпринт расширяется на одну колонку в сторону взгляда (вправо при dir>=0, влево иначе). У нас char_footprint этого не делал, и клинок торчал на колонку дальше области, где перерисовываются передние грани: меч оставался поверх столба, а его след — на фоне. char_footprint получил параметр sword; pop_fore_over_char — тоже (страж машет мечом ровно так же). Ветка Кида берёт Kid.sword, ветка стража — Guard.sword. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e67117219f |
BUG-CTRL-FRAME-1: геометрия Кида считалась по кадру стража
cur_frame — один глобал на всех персонажей (как в оригинале), владелец — тот, кто последним прошёл load_frame. Последним в кадре тикает страж, поэтому к моменту control() Кида там лежал кадр СТРАЖА, а через kid_cur_dx/dy/flags по нему считается вся геометрия управления: dx_weight -> determine_col -> distance_to_edge_weight -> get_edge_distance -> выбор ветки в check_jump_up. Оригинал зовёт load_fram_det_col() (seg006:0144) сразу после loadkid/loadshad и ДО control() — play_kid_frame (seg000:1211) и play_guard_frame (seg000:1248). У нас этого не было. Замерено брейкпоинтом на pop_jump_up_seq: Kid x=156 col=6 кадр 15 (dx=0 weight_x=3) при кадре стража image17 (dx=-1 weight_x=8) дал curr_col=7 и distance=2 вместо 6 и 10 — то есть jump_up_plain (вернулось A=28, пустой прыжок) вместо «шаг назад на x=160 + зацеп». Предсказание по кадру стража совпало с намеренным до единицы. Отсюда же плавающее поведение: кадр стража меняется каждый тик, distance Кида скакал через порог 6 — то прыжок, то попытка зацепа с неверной X. И «голова Кида поверх плиты (1,6)» — не баг отрисовки, а следствие позы, которой в оригинале в этом месте не бывает. Фикс: pop_load_fram_det_col() (pop_kid.c) + вызовы в pop_ctrl_tick и pop_guard_tick. determine_col — только на ветке Кида: у нас он существует лишь для него (pop_map работает с Kid, а не с Char). Разбор с числами — bug_closed.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5353bdaaec |
docs: приёмка уровня 2 — первым приоритетом, карта содержимого уровня
Порядок работ по решению пользователя: L2-PASS -> L3-CHOMP/SKEL/CHKP. Причина техническая — чомперы лягут в банк 2, где живёт отрисовка, и чинить баги фона поверх свежей механики дороже. TASKS: запись L2-PASS с картой уровня 2, снятой с res2002.bin — стражи (5, комнаты 4/7/11/15/24), ловушки и зелья по комнатам, и декодированные из LINKLOC/LINKMAP цепочки «кнопка -> что открывает» (в т.ч. кнопка к.9 @1,1, открывающая дверь выхода в к.23). Плюс отдельный список того, что сделано именно в L2 и на уровне 1 не проверялось: большая склянка, меч с начала уровня, выход через дверь, респавн на своём уровне. bug_list: заведён раздел «Уровень 2» под список багов отрисовки, который пользователь подаст отдельно. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6aefee0cc7 |
docs: TASKS — цель «уровни 1-3 (подземелье)», разбор что нужно уровню 3
Palace (уровни 4+) отложен решением 2026-08-04. На доску вынесены три задачи уровня 3, снятые с данных и SDLPoP, а не с общих соображений: - L3-CHOMP: 5 чомперов (комнаты 5, 16×3, 22); шаблон как у пик/ворот, риск — банк 2 занят на 86.5%. - L3-SKEL: в данных уровня 3 стражей НЕТ ВООБЩЕ; единственный враг — скелет, и он спецсобытие check_skel (seg002:1044), а не страж из данных. Нужен новый атлас (data/SKEL, 29 файлов) — pop_pack_guard.py прибит к GUARD/. - L3-CHKP: чекпойнт (seg002:519 + seg003:141); hitp_beg_lev уже есть. Плюс инвентарь тайлов по уровням: ур. 3 вводит только chomper(18), palace-набор (lattice*) начинается с уровня 4 — отсюда и граница скоупа. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e36828ae6e |
L2: переход между уровнями и игра уровня 2
Порт levels_plan.md §2 (шаг 1) — машинерия смены уровня целиком. - Номер уровня стал состоянием: pop_current_level (порт current_level), pop_next_level больше не флаг, а НОМЕР; главный цикл срабатывает по расхождению next != current (порт play_level_2, seg003:0386). - pop_level_load_num(n): LEVELS\res20NN.bin (fallback a:\), старая EMM-страница отпускается только после успешной загрузки новой. На диск кладутся все 15 уровней (34 КБ). - Потабличные различия (data.h:840..848) — таблицы по 16 в pop_level.c: tbl_entry_pose, tbl_guard_hp (HP стража ушло из хардкода 3), tbl_guard_type (−1 = стражей на уровне нет), tbl_level_type. - find_start_level_door (seg003:02E6): на уровне 2 стартовый тайл — правая половина двери уровня, без modif=43 + add_trob(...,3) Кид материализуется внутри глухой створки. Тип 3 = дверь захлопывается за спиной, как в оригинале. - HP через уровень: hitp_beg_lev (seg003) — рестарт уровня откатывает HP к нему, пройденный уровень подтягивает его к hitp_max. Заодно большая склянка add_life (тип зелья 2: +1 к потолку HP до 10) — на уровне 2 она есть, комната 20. - have_sword = level >= 2 (play_level, seg003:106). - Чит Shift+L — следующий уровень (seg000:698). Проверено в MAME: уровень 1 → Shift+L → уровень 2 (комната 5, новые для нас тайлы 8/9 «большая колонна» на месте, дверь захлопнута, HP 3) → влево в комнату 4 со стражем → Shift+L → уровень 3. Вход в стартовую дверь уровня 2 по Up не срабатывает — как и должно (start_room-гард). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7ef007757b |
docs: BUG-SEAM-DRAW-1 закрыт (не воспроизводится), заведён BUG-GATE-PASS-1
BUG-SEAM-DRAW-1: Кид упирается в решётку и встаёт на x=61, curr_col=-1, room=1 — и в комнате 1 РИСУЕТСЯ, за решёткой. При x=61 он уже внутри системы координат комнаты 1 (obj_x = 6), straddle-смещение не требуется. Остаток теоретический: при x <= 60 спрайт ушёл бы за левую кромку; полное лекарство — довести S3, но поводов нет, заводить обратно только по живому наблюдению. BUG-GATE-PASS-1: однократное наблюдение — Кид стоял НА тайле решётки (0,9) комнаты 5, дождался закрытия, пошёл вправо и прошёл в комнату 1. Повторить не удалось: в том же месте при x=196/col=9 решётка держит штатно. Плоскость блокировки для колонки 9 — x=205, а «колонка 9» по m7 это x ∈ [191,205), то есть при curr_col==9 проход невозможен по построению; значит наблюдался x >= 205, и поведение может оказаться штатным (решётка закрылась за спиной). Гипотеза НЕ подтверждена, поэтому баг оставлен открытым со списком того, что снять в следующий раз, и с двумя запасными кандидатами на корень. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f63751edce |
docs: BUG-LOOSE-2 закрыт — проверено вручную
Падающий кусок теперь привязан к своей комнате и долетает после ухода Кида; подтверждено живым прогоном. Автоматикой гонка не воспроизводилась — мост MAME шлёт нажатия рывками, и «уйти раньше, чем долетит плита» через него не набиралось; это отмечено в записи вместе с указанием, что кейс стоит первым в плане host-тестов. Вторая волна прогона уровня 1 закрыта целиком: BUG-KBD-4, BUG-RESPAWN-2, BUG-DRAWORDER-1, BUG-LOOSE-2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a89b8b30c6 |
docs: BUG-DRAWORDER-1 закрыт; остаток «ноги поверх головы» — в НЕ БАГИ
Сверено в SDLPoP: Кид, стоящий колонкой правее лежащего трупа, и там рисуется поверх его головы. Значит порт верен, а артефакт врождённый: set_objtile_at_char приписывает персонажа РОВНО ОДНОМУ тайлу, ширина спрайта на выбор тайла не влияет, и всё, что стоит правее, перекрывает выступающую часть тела. Механизма «широкий объект в нескольких тайлах» в оригинале нет — сверено с draw_objtable_items_at_tile, sort_curr_objs и веткой tile_object_redraw == 0xFF (та про оверлеи пола). Закрытая запись перечисляет все четыре корня, которые пришлось снять по очереди: порядок по роли вместо тайла; своя регрессия с общим окном fore-клипа; колонка трупа из тайла вместо X; непортированная ветка actions_1_run_jump. Записано и то, как отличать остаток от этих багов, и цена «починки» остатка (тайл по центру габарита = отход от эталона). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
61d4255091 |
testkit: модульные тесты под ucsim_z80 + планы покрытия
Обвязка для быстрых тестов plain-C логики: секунды вместо прогона в MAME, без образа диска. ucsim_z80 идёт в комплекте нашего SDCC — новых зависимостей нет. ПОЧЕМУ ПОД Z80, А НЕ ХОСТОВЫМ GCC. У SDCC z80 int 16 бит, у хоста 32, и расходится это НЕ в объявлениях, а в выражениях: integer promotion повышает операнды до int независимо от того, объявлены они как uint8_t или uint16_t. Перевод кода на фиксированные типы разницу не убирает — убирает только исполнение с z80-семантикой. Побочно проверяется кодогенерация SDCC и модули с inline-asm, которых хостовая сборка не видит в принципе. Устройство: crt0_ucsim.s (SP, зануление, main, halt), tcheck.* (итог в структуру в ОЗУ), run_ucsim.py (гоняет ucsim, дампит tc_result, печатает отчёт), host-tests.mk (общие правила). Вывода через printf нет: тестовый бинарь линкуется без Sprinter-libc. Через ucsim-simif не идём — номера его команд плавают между версиями, halt + dump работают везде. Наборы лежат РЯДОМ с проверяемым кодом, обвязка общая: testkit/t_selftest.c — самопроверка (sizeof(int)==2) applications/PoP/roomtest/tests-host/ — движок PoP Первый содержательный набор — t_geom: сверяет рукописный asm-LCG из pop_geom.c с наивной 32-битной формулой на 128 шагах. Заявка «бит-в-бит как в SDLPoP» до сих пор держалась на комментарии. Тест проверен мутацией: порча эталонной константы даёт красный. Планы дальнейшего покрытия: docs/host-tests-plan.md — libc и libbgi (не начато) applications/PoP/docs/host_tests_plan.md — движок PoP Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b5d2a81ee3 |
L1: фиксы прогона уровня 1 — уровень мутабелен, стражи, loose-плиты, порядок
Одиннадцать наблюдений первого прогона свелись к шести корням, четыре
наблюдения второго — ещё к четырём. Разбор каждого — bug_closed.md.
Первая волна:
- BUG-LVLSTATE-1: уровень стал мутабельным (эталонная копия foretable для
рестарта, pop_level_set_tile вместо таблицы оверрайдов);
- BUG-RESPAWN-1: рестарт = load_level, тайлы возвращаются из эталона;
- BUG-DEATH-1: смерть от меча доигрывается (порт control_kid, seg006:0CD1);
- BUG-GATE-ANIM-1: ворота в отрисованной комнате перерисовываются
(POP_RD_GATE, порт draw_trob seg007:01E6);
- BUG-COLL-1: полный порт check_collisions/bumped (seg004) вместо поиска
стены только в колонке переднего края;
- BUG-STANDUP-1: убран лишний guard в bumped_floor — вставание у стены
роняло Кида сквозь пол.
Вторая волна:
- BUG-RESPAWN-2: рестарт возвращает и СТРАЖЕЙ (в оригинале play_level на
каждой итерации делает load_level + pos_guards);
- BUG-LOOSE-2: падающий кусок привязан к своей комнате и долетает после
ухода Кида (do_mobs крутит mobs[] независимо от drawn_room);
- BUG-DRAWORDER-1: порядок «Кид / страж» задаётся обходом тайлов
(redraw_needed_tiles: ряды 2,1,0, колонки 0..9), а не ролью персонажа.
По BUG-DRAWORDER-1 понадобилось три захода, и два первых были неполны:
1) сам порядок — но общее окно fore-клипа осталось стражьим, и Кид
нарисовался поверх передних столбов (kid_fore_clip_restore);
2) enter_guard брал curr_col из тайла, а leave_guard пишет туда
get_tilepos(0,row) — у запомненного ТРУПА колонка была 0 при
настоящей X. Теперь колонка выводится из X, как в оригинале;
3) ветка actions_1_run_jump в set_objtile_at_char оказалась не
«упрощаемой»: в беге тайл берётся из нижнего ряда и ЛЕВОЙ колонки
габарита, поэтому бегущий Кид уходит за объекты справа. Считается
для обоих персонажей — enter_guard ставит action=1 и стражу.
Проверено в MAME: зелья/меч/плиты переживают выход из комнаты и
восстанавливаются после смерти; кнопка room5 поднимает решётку; падение с
кнопки больше не роняет в комнату 6; убитый страж жив после respawn;
Кид проходит за телом стража. make size-check — роста нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ba37bd1133 |
BUG-DOOR-CLIP: обрезка силуэта правым косяком двери уровня
Симптом (нашёл пользователь сразу после L1-EXIT): при подъёме по лестнице за
дверью уровня силуэт Кида вылезал ПРАВЕЕ правого косяка проёма; по высоте
обрезка была корректна.
Причина — недопортированная половина clip_char (seg006:1231). Для кадров
двери оригинал ставит ДВА клипа, у нас был только первый:
obj_clip_top = leveldoor_ybottom + 1; // было
obj_clip_right = leveldoor_right; // не было
Отдельная ловушка: комментарий в SDLPoP говорит «frames 217..228», а КОД
проверяет >= frame_224_exit_stairs_8, то есть 224..228 — портировано по коду.
Fore-слоем это не лечится: створка и косяк уходят в оригинале целиком в
backtable (draw_leveldoor, все add_backtable), рисуются ПОД персонажем и
перекрыть его не могут. Единственный способ — срезать сам спрайт.
libbgi: gfx_blit_cols_part_w(..., uint8_t maxw) — обрезка СПРАВА у
колоночного блита. Для column-major это ровно уменьшение числа колонок, то
есть внутри ядра механизм уже был (так же клипается край экрана,
w = _bgi_maxx + 1 - x), наружу не выводился. Тело блита переехало туда,
gfx_blit_cols_part стал тонкой обёрткой (maxw=0) — тем же приёмом, каким
gfx_blit_cols уже обёрнут вокруг gfx_blit_cols_part. Работает и при flip:
первые maxw нарисованных колонок всегда ложатся в левую часть футпринта.
make size-check: роста нет.
PoP: pop_leveldoor_right / pop_leveldoor_ybottom (порт одноимённых глобалов)
пишет draw_leveldoor в pop_state — их читает clip_char из другого банка;
pop_clip_char_right() отдаёт границу, kid_draw превращает её в maxw и уводит
эти кадры с noclip-пути на общий. Прямоугольник heal (kid_lw) сужается тоже
— стираем ровно нарисованное.
Проверено в MAME: pop_leveldoor_right = 176, что есть ровно (draw_xh<<3)+48
для двери комнаты 9; pop_leveldoor_ybottom = 112 у закрытой створки и 69 у
поднятой — сходится с формулой оригинала. Отрисовку подтвердил пользователь
на живом подъёме.
Заодно: ROOMNAV остаётся включённым осознанно — это наш чит, которого в
оригинале не было, как и S/K/I; позже сведём в общий блок читов.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
e86f254b87 |
L1-START + L1-EXIT: старт по данным уровня и выход через дверь уровня
L1-START. Старт и оба рестарта (смерть, выпадение из уровня) сведены в pop_start_level() — порт start_level + do_startpos + set_start_pos (seg003). Комната/тайл/направление берутся из pop_level_start_*, направление инвертируется (~start_dir), поза входа — из tbl_entry_pose: у уровня 1 это падение внутрь (seq_7_fall) плюс нажатие кнопки room5(0,2), то самое, что захлопывает решётку за спиной. Жёсткие START_ROOM/COL/ROW убраны. Проверено в MAME: старт даёт room 1, col 0, падение на row 1 — как по данным. L1-EXIT. Ветка двери уровня из up_pressed + go_up_leveldoor (seg005:0482/ 0574): тайлы и геометрия — pop_leveldoor_enter() в pop_map, последовательность seq_70 — в pop_ctrl. Опкод 0xF1 END_LEVEL в play_seq инкрементит pop_next_level (порт next_level), главный цикл по нему перезапускает уровень — ровно та точка, куда levels_plan §2.2 подключит загрузку уровня 2. Открытость двери проверяется по modifier >= 42 (ветка fix_exit_door), а не по ванильному leveldoor_open: иначе можно войти в ещё ползущую створку. Отдельно стоило разбора: go_up_leveldoor сначала писал Char.x/Char.direction, и оба присваивания молча терялись — окно Char вокруг диспетчера возвращает назад только curr_seq и sword (pop_savekid_state). Направление оставалось «вправо», а все DX в seq_70 отрицательные, поэтому Кид уходил ИЗ проёма влево (поймано стоп-кадром). Геометрию персонажа в этом порте меняет pop_map, пишет в Kid — как pop_down_action и pop_jump_up_seq. Известный остаток — BUG-DOOR-CLIP в bug_list.md: нет обрезки силуэта правым косяком проёма (недопортирован obj_clip_right в clip_char); нужен вариант колоночного блита с ограничением ширины. ROOMNAV пока оставлен включённым — он нужен, чтобы попадать в комнату 9 для этой работы. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1146c57544 |
CLIP-1: heal Кида и стража — линейным ядром, когда клип не нужен
kid_heal/pop_guard_heal звали gfx_heal всегда, хотя рисуют ровно тот прямоугольник, который блит в большинстве кадров кладёт noclip-ядром. Все три места heal (+ heal_off фона) сведены к общему pop_heal_fast. Замер в MAME, счётчики totalcycles на входах kid_heal и kid_tick (вся группа heal за кадр), комната 1, Кид стоит: клипающее ядро 26 200 тактов/кадр (149 кадров) noclip 15 848 тактов/кадр (239 кадров) −10 352 такта, −39.5 %. A/B в одном прогоне: вторая половина снята с пропатченным в памяти условием (jr nz → jr), то есть на той же геометрии. Размер СУММАРНО −362 Б: _CODE +17, BANK2 −116 (свободно 2708 — это тесный банк из рисков levels_plan §5), BANK3 −263, BANK4 без изменений. Грабли по дороге: первым заходом хелпер был static inline в pop_bg.h — SDCC 4.5 И встраивает тело (181 Б) в каждый вызов, И оставляет копию в каждом TU, который видит заголовок. pop_guard_heal раздулся с ~60 до 663 Б, итого +1091 Б в _CODE и +636 Б в банке стража. Отсюда pop_draw.c: обычная функция в резиденте W1, из банков это прямой call без трамплина. pop_room_clip_borders оставлен клипающим осознанно (320 не лезет в 8 бит, гейт border_dirty редкий) — причина записана в коде. Проверено визуально: ходьба, прыжок, спуск, позиция за решёткой шва (straddle — там работает клипающий фолбэк) — артефактов нет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d552cbaca9 |
docs: bug_list — только открытые баги; закрытые → bug_closed.md
Три Critical'а (BUG-1 провал на row 1 при боковом переходе, BUG-2 ping-pong при возврате, BUG-3 окклюзия climb-up на кнопке) висели непроверенными с 2026-07-21. Прогнал в MAME: - BUG-1 не воспроизводится: room6 → кнопка (0,2) → открытая решётка → переход влево даёт room8, y=55, curr_row=0. Заодно снят и сам диагноз записи — репроекция Y при БОКОВОМ переходе не нужна: goto_other_room (seg002.c:390) меняет только x, наш check_leave делает то же. - BUG-2 не воспроизводится: шов room2↔room3, четыре пересечения с разворотом сразу после входа — комната меняется ровно раз на пересечение. - BUG-3 закрыт фиксом tile_code_drawn от 2026-07-28 (это дубль уже записанного «спуск с кнопки»); оговорка про непереснятый подъём — в bug_closed.md. bug_list.md теперь только открытое (BUG-CEIL-1/2/3, BUG-OCCL-1, T-1, T-2, таблица обхода 24 комнат) + индекс с якорями. bug_closed.md — закрытое вместе с разбором корней (odd-pixel char_x, подстановка тайла кнопки, баг кодогенератора SDCC), он и есть главная ценность архива. TASKS.md: кросслинки на открытые баги в шапке, в L1-TRIAGE, L1-PASS и «Отложено». Указатели в CLAUDE.md/README/room_model_plan/layout_plan_v2 переведены на нужный из двух файлов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6b4a3b6b41 |
docs: итог KBD-1 — что лечит плотный опрос и что осталось
Ручная проверка пользователем: стало значительно лучше, но редкие пропуски стрелок при зажатом Shift всё же ощущаются. Счётчики на 35 нажатиях подряд потерь не показали, то есть остаточная частота заметно ниже прежних ~15 %. Задача отложена до финальной полировки программы (решение пользователя) — для работы клавиатура пригодна. Записано, где именно осталась дыра, чтобы не начинать с нуля: idle-хук покрывает простой (~2/3 кадра), а в занятой трети DI-окно одного accel-прохода доходит до ~650 мкс при допуске FIFO ~300 мкс — пачка байт, целиком попавшая в такое окно, ещё может потерять байт. Порядок действий на возврат: вызовы между блитами занятой фазы, замер тем же счётным методом от 50 нажатий, и только потом рычаги вне нашего кода (Scan Code Set 3 через BIOS $EA — в MAME непроверяемо; общий m_irq_off_timer в драйвере). Заодно сняты оговорки «плотный опрос ещё не подтверждён замером» в kbd_raw.h и libc-reference.md — теперь там штатный рецепт через gfx_set_idle_hook. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4b498d171b |
libbgi: idle-хук в ожидании кадра; им лечится потеря нажатий с Shift
Причина потерь (замеры — applications/PoP/roomtest/TASKS.md, KBD-1): при
зажатом Shift PS/2 обрамляет расширенный код «фиктивным шифтом», нажатие
стрелки становится 5 байтами вместо 2, а импульс запроса прерывания здесь
теряется примерно в 44 % случаев — трёхбайтовый FIFO SIO переполняется, и
байт пропадает ДО чтения порта. Лечится только плотным вычерпыванием: раз
в ~0.5 мс. Столько времени есть даром — при пейсинге «3 растровых кадра на
логический тик» процессор проводит ~42 мс из 60 в gfx_wait_vsync, крутя
опрос луча и больше ничего не делая.
- gfx_set_idle_hook(fn) — что вызывать, пока gfx_wait_vsync ждёт луч.
Состояние в отдельном data-модуле (_gfx_idle_state.c), чтобы не тянуть
сеттер в программы, которые хук не ставят.
- Лучевой цикл зовёт хук в обеих фазах. BC (счётчик таймаута)
сохраняется, косвенный вызов — push адреса возврата + jp (hl), так как
`call (hl)` в Z80 нет; без хука это ret по нулевому указателю, порядка
двух десятков тактов в цикле, который и так сжигает время.
- Путь FPS-делителя не затронут: там ожидание через HALT.
- roomtest вешает на хук kbd_raw_poll.
Проверка в MAME счётчиками (брейкпоинты с { b@ADDR = b@ADDR+1 ; g } на
чтении порта 0x18 и на установке make-бита): 35 нажатий Shift+Home → 35
make, ноль потерь; до фикса было 9 из 10. Боевой сценарий: четыре Shift+→
подряд дали четыре осторожных шага (Kid.x 114 -> 147). _CODE +170 Б,
кадровый бюджет не затронут.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
b56f2b4582 |
libc/kbd: kbd_raw_poll + замер потери нажатий при зажатом Shift (KBD-1)
Симптом: при удерживаемом Shift часть нажатий стрелок не отрабатывает
(~15 % по наблюдению пользователя), без Shift потерь нет.
Переведено в числа: нажимается Home — тоже расширенная клавиша (тот же
E0-префикс и тот же «fake shift»), но игрой игнорируется, поэтому рельеф
комнаты на результат не влияет. Счётчики — брейкпоинты MAME с действием
{ b@ADDR = b@ADDR+1 ; g } на входе клавиатурной ветки трамплина, на чтении
порта 0x18 и на установке make-бита.
Что измерено (10 нажатий Shift+Home, дошло make):
игра идёт, опрос ВКЛ 9/10 игра идёт, опрос ВЫКЛ 9/10
игра ЗАМОРОЖЕНА (блитов нет вообще, длинных DI нет) 8/10
Обе исходные гипотезы отпали:
- длина наших DI-окон ни при чём (в замороженном кадре потерь больше);
- снятие di в accel-ядрах libbgi УРОНИЛО машину — режим «акселератор при
EI» из docs/new/06-accel.md §6.6 в этой прошивке недоступен.
Байт теряется НИЖЕ нашего кода: на 49 прочитанных байт пришлось только 28
входов в клавиатурную ветку, то есть ~44 % импульсов запроса прерывания не
обслуживается и трёхбайтовый FIFO SIO переполняется.
Потолок приёма измерен отдельной программой tests/kbdpoll (ничего, кроме
kbd_raw_poll в цикле): 25 нажатий -> 25 make, 250 байт из 250, ноль потерь.
Значит опрос лечит полностью, вопрос только в плотности: нужно раз в
~0.5 мс, а шесть вызовов за 60-мс кадр давали раз в 10 мс.
Поэтому вызовы из roomtest.c УБРАНЫ (они стояли там, где прерывания и так
разрешены, и дублировали трамплин — 9/10 с ними и без). Сама функция
kbd_raw_poll оставлена в libc: она корректна и нужна как основа плотного
опроса. В заголовке и в libc-reference — честная оговорка, чтобы её не
ставили в игровой цикл «на всякий случай» без замера.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
774b1cc7c4 |
docs(PoP): документация к актуальному статусу + план следующих уровней
Документы отстали от кода: PORT_PLAN писал «PoC не начат», хотя играется весь уровень 1, а четыре плана были исполнены целиком. - PORT_PLAN: таблица статусов по разделам; фазы 0-3 сделаны, 4-6 нет; риски §8 п.1/п.3 закрыты, п.2 переформулирован под реальный движок (спрайтовый движок для персонажей не используется, лимит «21 спрайт» неприменим), п.4 — найдено расхождение таймингов: оригинал считает логический кадр за 5 тиков при BASE_FPS=60 (83.3 мс, в бою 100 мс), а мы ждём три vsync (60 мс) — игра идёт примерно на 39 % быстрее эталона. - levels_plan.md — новый: машинерия перехода между уровнями, второй тайлсет (palace), потабличные различия и читы SDLPoP, которые окупаются сразу. Инвентарь тайлов снят прямо с res200N.bin: уровень 2 не требует ни одного нового ассета и ни одной новой механики. - roomtest/TASKS.md — новый: доска текущих задач с критериями готовности. - Удалены как исполненные и перекрытые кодом: clip_char_plan, double_buffer_plan, loose_floors_plan, size_optimization_plan. Его §8 (замеры скорости отрисовки) не был перекрыт — перенесён в layout_plan_v2 §9, чтобы не потерять цифры. - KID_PLAN / gates_spikes_plan — шапки «реализовано, оставлено справочником»; room_model_plan — «S1 сделан, остальное не срочно». - docs/README.md стал индексом с отметками актуальности. - ideas_backlog: зелье переворота экрана — оригинал переворачивает готовый буфер построчно, спрайты не трогает; по данным уровней тип 4 встречается только на уровне 9, до него механика не нужна. - examples/scroll: ссылка на удалённый план вела к неверному факту «теневая копия одна — общая»; заменено на подтверждённое «у каждой страницы своя». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2f3e854854 |
PoP roomtest: отрисовка стража — в собственный банк (банк 2 упёрся в потолок)
Банк 2 (pop_bg + pop_gdraw) подошёл к границе страницы вплотную: 16 021 из 16 384, свободно 363 байта. А расти ему ещё есть куда — тайлы поздних уровней, чомперы, зеркало, анимации смерти стража. pop_gdraw.c уехал в банк 4 (n_banks 3 -> 4): банк 2 16 021 -> 13 792 (84.2 %, свободно 2 592) банк 4 2 236 (13.6 %, свободно 14 148) Цена: pop_fore_over_char стал кроссбанковым, поэтому помечен __banked — один трамплин (~654 такта) за кадр, других вызывающих у него нет. pop_fore_set_clip уже был __banked, так что там ничего не изменилось. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
75a51fb1db |
PoP roomtest: выпивание зелья (уровень 1 — склянка здоровья)
Каркас предметов уже был (check_get_item/do_pickup/proc_get_object), пустой оставалась только ветка зелий. Порт seg005 get_item + seg006 proc_get_object: - pop_get_item_action теперь отдаёт 3 = «пить» и делает do_pickup с ТИПОМ зелья, который лежит в старших битах модификатора тайла (modif >> 3); - pop_ctrl на код 3 запускает seq_78_drink; - эффекты: тип 1 (здоровье) — +1 HP через hitp_delta и красная вспышка, причём как в оригинале только если HP не полные; тип 5 («злое») — −1 HP. Типы 2/3/4/6 (жизнь, перо, переворот, открыть ворота) — свойства поздних уровней, портируем вместе с ними. Вспышка фона получила цвет: меч даёт ярко-жёлтую (было), зелье — красную (flash_color оригинала; двух значений достаточно, других в игре нет). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b22cee3456 |
PoP roomtest: убитый страж остаётся мёртвым; меч только по подбору или читу S
Смерть стража теперь персистентна между входами в комнату — по механизму оригинала, а не отдельной таблицей «убит/не убит». В SDLPoP массивы level.guards_* лежат в ОЗУ и движок их ПЕРЕПИСЫВАЕТ: leave_guard (seg002:02F5) кладёт туда позицию/направление/мастерство, а у МЁРТВОГО ещё и curr_seq; enter_guard, увидев непустой seq_hi, поднимает стража прямо в этой последовательности и по кадру смерти (185/177/178) ставит alive = 1. У нас уровень лежит в EMM-странице только на чтение, поэтому в W2 добавлена живая копия — 6 байт на комнату (tile/dir/x/skill/seq_lo/seq_hi): - pop_guard_leave() в начале enter_room запоминает уходящего стража; - pop_guard_enter поднимает труп сохранённой последовательностью И сохранённой X (pos_guards пересчитывает её из колонки только при загрузке уровня, дальше ею владеет leave_guard — иначе тело прыгает в центр тайла). ГРАБЛИ: guards_seq_lo/hi в ФАЙЛЕ уровня не используются, там 0xFF во всех комнатах (оригинал чистит их в reset_level_unused_fields). Прочитав их как есть, я скормил интерпретатору curr_seq = 0xFFFF, и приложение зависало — бордюр оставался синим, цикл не доходил до vsync. Живая копия стартует нулями: 0 = «поднимать стандартной стойкой». Меч Киду больше не выдаётся автоматически: DEBUG_SWORD_ROOM убран, вместо него чит S (выдать меч). Штатный путь — подобрать с пола. Проверено в MAME: чит K убивает стража, уход из комнаты 3 и возврат — тело на месте, страж не воскресает. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2e90eaf7d7 |
PoP roomtest: окно Char больше не затирает правки pop_map (спуск с уступа)
Регрессия от окна Char вокруг control() (
|
||
|
|
3983fa4513 |
PoP roomtest: HP-учёт, индикаторы HP, чит бессмертия; фикс кэша кадра
Боёвка (порт seg002/seg006): - check_sword_hurting / check_hurting / check_sword_hurt / hurt_by_sword / take_hp через дельты; do_delta_hp сводит их раз в кадр; - парирование (justblocked), refractimer после ранения стража; - смерть по seq_71_dying — через неё же теперь работает чит K: страж действительно погибает, а не замирает на месте; - парные окна Char/Opp: loadkid_and_opp / savekid_and_opp / saveshad_and_opp. Индикаторы HP (порт draw_kid_hp / draw_guard_hp): Кид слева, страж справа. Перерисовка ТОЛЬКО при изменении числа и тогда на ОБЕИХ страницах дабл-буфера (счётчик hp_todo, иначе на второй странице осталось бы старое значение и мерцало через кадр); pop_hp_invalidate при входе в комнату, где фон перерисован целиком. Чит I — бессмертие Кида (нашего изобретения, в оригинале его нет). Перекрывает и путь «безоружного закалывают насмерть»: тот идёт мимо HP, и без этого чит бесполезен ровно там, где нужен. ДВА НАЙДЕННЫХ БАГА: 1. Полосу HP блитил row-major примитивом, а атласы Кида и стража хранятся COLUMN-major (ради бесплатного флипа) — марки выходили транспонированными. Теперь колоночный блит, стрелки как в оригинале. 2. Кэш кадра для ОТРИСОВКИ заполняли pop_savekid/pop_saveshad. Любое окно Char БЕЗ play_seq — а это оба окна боёвки — записывало Киду кадр, который принадлежал СТРАЖУ, и kid_draw искал этот image в атласе Кида, рисуя произвольную позу. Владельцем кэша стал load_frame: он один знает, чей кадр загружен (по Char.charid). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d014a3f577 |
PoP roomtest: ИИ стража — подход к Киду и боевые ветки диспетчера
Пункт 2 плана закрыт: страж не только замечает Кида, но и идёт к нему и дерётся. Порт по SDLPoP, диспетчер общий — ИИ выставляет те же control_*, что и клавиатура игрока. guards.c (банк 1), порт seg002: - autocontrol_guard_active (737) + kid_in_sight (0A93) + kid_armed (0AC1) + kid_far (09CB); - guard_advance / guard_block / guard_strike с таблицами вероятностей по 12 градациям мастерства (seg002:26..38), бросок prob > prandom(255); - move_2_backward / move_3_up / move_6_shift / move_down_back; - таймеры justblocked / kid_sword_strike / guard_refrac убывают раз в кадр в autocontrol_opponent, как в оригинале. pop_ctrl.c, порт seg005: control_with_sword (964), swordfight (0CDB), sword_strike, parry, forward_with_sword, back_with_sword. Ветвление у Кида и у соперника разное — соперник блокирует только на кадре 152, Кид ещё и по 153 (и тогда последовательность прокручивается сразу). pop_guard.c: guard_skill из данных уровня (12 градаций, вне диапазона -> 3), HP по get_guard_hp (extrastrength[skill] + tbl_guard_hp[уровень]), собственный сид бросков pop_fight_seed — иначе перерисовка стены сбивала бы решения стража. char_opp_dist переехал из банка в pop_kid.c: он нужен по ОБЕ стороны банковой границы — и ИИ, и диспетчеру боёвки. Проверено в MAME (комната 3): страж проходит комнату, встаёт в дистанцию и машет мечом, позы меняются. Урона пока нет — HP-учёт и check_hurt следующим шагом, без них бой не заканчивается. Бюджет В БОЮ (175 кадров): 412 224 – 421 068 = 0.958–0.979 кадра. В покое было 400 800. Запас в худшем кадре ~9 000 — тесно, но в один кадр укладываемся; оптимизация отложена сознательно. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |