39c324753207aa4c35f363ab0fae4f15a83d5988
11 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
de68eb5cec |
P1: чомпер перерисовывался неизменной позой — минус 110 802 такта
Позиция заводилась с НЕПОЛНЫМ диагнозом. Я приписал 190 260 тактов
пометке от факела (пламя лежит в ячейке правого соседа, то есть поверх
чомпера, и запекается каждый кадр). Правка по этому диагнозу не дала
ничего: 769 002 против 768 684.
Зонд pop_dbg_kind показал факт: все 312 перерисовок прогона — вид
POP_RD_CHOMP, полная, и ни одной от факела. Собственная пометка чомпера
просто перебивала пометку соседа.
Настоящая причина нашлась сверкой с animate_chomper (seg007:0448).
Оригинал заканчивает её так:
if ((curr_modifier & 0x7F) < 6) redraw_at_trob();
то есть перерисовывает чомпер только пока фаза меньше 6 — пять кадров из
пятнадцати. Это не оптимизация оригинала, а следствие таблицы поз:
chomper_fram1 = {3,2,0,1,4,3,3}, и с фазы 5 до конца круга поза одна и та
же. Мы метили тайл каждый кадр, пока trob жив, а живёт он всё время, пока
Кид в том же ряду — то есть платили полный draw_tile плюс heal 32x64 за
неизменную картинку в двух третях кадров.
Сделано:
1. пометка только при фазе < 6; на фазе 5 — обе страницы дабл-буфера
(она последняя рисуемая, её поза обязана лечь на обе; вторую страницу
пометка догоняет в кадре фазы 6, где поза та же — CHOMP_FRAM1[6] == 3);
2. новый вид POP_RD_CHOMP_ANIM -> pop_chomp_anim_draw: три блита графики
чомпера поверх свежего пламени, без heal и без остальных слоёв — порт
ветки redraw_frames_anim (seg008:0211), где оригинал делает ровно
draw_tile_anim_topright / draw_tile_anim_right / draw_tile_anim и
никакого wipe;
3. приоритет полной перерисовки над anim в pop_set_redraw: у оригинала
это два независимых счётчика и full побеждает, а у нас вид один на
тайл, и без проверки исход решал бы порядок trob'ов в списке.
Обе половины работают — замер даёт 40 % полных перерисовок и 60 % лёгких.
Работа 768 684 -> 657 882 (медиана), зелёная 294 510 -> 183 420. В 40 %
кадров цена прежняя: там поза реально меняется, это честная работа.
Циан не сдвинулся ни на такт, то есть надежда P3 (Кид перестанет будиться
каждый кадр) пока не оправдалась — метки продолжают его будить.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
272cf8f195 |
Разбор порядка отрисовки падающей плиты: найден корень, выбран вариант B
СИМПТОМ (пользователь, ур.11 к.14). Кусок, отвалившийся от плиты нижнего ряда, рисуется ПОВЕРХ соседней трясущейся плиты; при этом передний торец соседа лежит поверх куска — порядок противоречив в разных частях перекрытия. КОРЕНЬ. draw_mob (seg007:13E5) считает ряд объекта как y_to_row_mod4(y) = (y+60)/63 % 4 - 1. Из-за % 4 ряд 3 (кусок ушёл ниже комнаты) и ряд −1 (кусок у потолка) дают ОДНО значение −1. Оригинал прогоняет его через get_tilepos -> get_tilepos_nominus и получает тайл 30, а объекты с тайлом 30 рисуются в redraw_needed_tiles ПЕРВЫМИ, до всего обхода тайлов. Мы же передаём сырой −1 в мид-оверлей как «тайл объекта», и гейт `row > pop_bg_obj_row` читает его как «объект в последнем ряду обхода», то есть «объект поверх всего», и отбрасывает возврат соседа. Один и тот же −1 у нас значит «сверху», у оригинала — «снизу». Торец при этом виден потому, что приходит из ДРУГОГО слоя: draw_loose кладёт loose_fram_bottom в backtable и foretable, минуя ptr_add_table, — тело плиты обязан вернуть мид-оверлей, а его и выключает гейт. ЧТО ПРОВЕРЕНО ЗАМЕРОМ (журнал решений в pop_dbg_ovl/pop_dbg_pass, зонды ВРЕМЕННЫЕ и будут сняты): - на застывшем кадре: слот draw_y=194, r=−1, rt=2, оверлей позван, внутри отбрасывается гейтом; - пропуск оверлея на последнем кадре полёта ЗАКОНЕН: габарит куска уже ниже габарита тайла, перекрывать нечего; - в 13/23 кусок перекрывают 2-4 тайла (накопленно за полёт), включая СВОЮ клетку, — то есть «сосед справа» покрытие не исчерпывает; - перерисовок тайлов за кадр в 13/23: максимум 6, в покое 0. ТУПИКИ, чтобы не ходить второй раз. Клип объекта тут ни при чём: add_mob_to_objtable ставит clip.right = 40, но клип применяется только при chtab_flip_clip[chtab_id], а для chtab_6_environment там 0 — поле игнорируется и в оригинале. Пункт MOB-CLIP-RIGHT закрывается как несуществующий. Добавлять торец плиты в мид-оверлей тоже не надо: в foretable он уже кладётся из fore_tile. ВЫБРАН ВАРИАНТ B: классифицировать ряд объекта (вне 0..2 = корзина 30), отдавать оверлею ориентир «раньше любого тайла» и расширить набор перекрываемых тайлов на СВОЮ клетку. Переносить проход отрисовки не нужно — heal при этом не участвует, работа та же (оверлей = два блита в окне клипа), разница с узким вариантом A всего один-два оверлея на кусок. Заодно записана ОБЯЗАТЕЛЬНАЯ задача HEAL-WIDTH: ширины точечных heal'ов взяты по клеткам (64), а фактический след плиты — 58/57 (замерено по атласам: верх 32, правая грань 26 в подземелье и 25 во дворце). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7ae691b070 |
Fore потолка поверх падающей плиты: в разборе пометок не было ряда -1
Найдено пользователем (2026-08-17, уровень 1 комната 6): падающая плита (-1,5) перекрывает собой кромку потолка (-1,6), чего физически быть не может. Дыра архитектурная: pop_fore_needed обходил только ряды 0..2, ряда -1 в переднем слое не было вовсе. Сверено с оригиналом. redraw_needed_tiles (seg008:1B06) обходит ряды 2,1,0, а ПОТОМ отдельным проходом ряд 2 комнаты сверху (redraw_needed_above), и его draw_tile_fore кладёт куски в FOREtable. Падающая плита идёт в MIDtable (draw_mobs). draw_tables рисует back -> mid -> fore (seg008:1373), поэтому у оригинала кромка потолка оказывается поверх плиты сама собой. Порт: - pop_fore_needed: проход по ряду -1 добавлен и идёт ПОСЛЕДНИМ, как в оригинале. Свой набор пометок (rdfa/rdfa_pending) — как и у самих перерисовок ряда -1 (rda_*), это отдельный проход, а не 11-я колонка; - новый лист pop_ceil_fore_tile_b (pop_bg.c) — тот же redraw_needed_above, что уже рисовался над персонажем (ceil_over_kid_tile), плюс окно клипа ровно на полосу столбца и ov_mark (полоса идёт банком без тени, на второй странице её восстановит pop_fore_heal); - mob_mark_neighbour помечает ряд -1, пока кусок достаёт до кромки. Кромка живёт в трёх верхних строках поля (dby = 2 при клипе по POP_YOFF), спрайт куска занимает mob_y-16 .. mob_y, отсюда условие mob_y <= 18 — три кадра после отрыва (y = 2, 5, 11 при ускорении 3). Помечаются ОБА столбца, которые кусок накрывает по x (mob_x .. mob_x+62 = col и col+1); именно поэтому страдал сосед. По бюджету работа появляется только в эти три кадра на кусок и попадает в циановую фазу, где сейчас запас (270 тыс. из 400 тыс.). Замер на ур.13 — следующим шагом. 8 наборов tests-host зелёные. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
35b7cd5196 |
Фикс мерцания торцов: копия запечки требует ОКНА КЛИПА
Регресс от копии второй страницы (
|
||
|
|
18ee60eb69 |
mob_tick_one в file-scope + снятие временной оснастки замеров
зелёная пик 423 558 -> 414 456 работа пик 793 266 -> 783 798 ПЕРИОД логического кадра в каскаде: было 5-6 растровых, стало РОВНО 4 mob_tick_one: рабочие переменные в file-scope (было 16 байт кадра и 99 обращений `-N(ix)`, стало 17) — то же лечение, что у draw_tile и blit_b_clip. Снята временная оснастка из ГОРЯЧИХ путей: шесть вызовов pop_dbg_b1..b6 в pop_blit_b (по ~65 такта каждый на КАЖДЫЙ блит), pop_dbg_kind/m16 и подсчёт состава кадра в pop_redraw_needed, pop_dbg_m13..m15 в pop_ceil_shake_draw. Сами пустышки в pop_state.c оставлены — вставить их обратно на один замер дешевле, чем заводить заново; как это делается, записано в docs/perf_l13_room23.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f89b7dd0d2 |
Ускорение холостого хода: указательный обход вместо arr[i] в горячих циклах
Замер зелёного блока (комната 23 уровня 13, MAME, такты эмулятора) показал, что 86% его стоимости в ПОКОЕ — это pop_loose_tick, который не делает ничего. Причина — кодоген SDCC, подтверждена чтением .asm. С `int pos` и записью pop_loose_modif[pos] компилятор держал счётчик в IX-фрейме, каждую итерацию заново складывал 16-битный адрес элемента, клал его в локал и тут же вычитывал обратно парами `pop bc / pop hl / push hl / push bc`. 40 холостых итераций (30 тайлов + 10 потолков) стоили 27 936 тактов. То же в pop_loose_mob_tick: запись mobs[i] заставляла умножать i на sizeof(mob_t)=15 заново под КАЖДОЕ поле (.active/.clean/.x/.y), 14 пустых слотов — 35 790. Правка — обход указателем, счётчик uint8_t, пустые слоты отсеиваются в вызывающем цикле (а не гардом внутри mob_tick_one, до которого надо ещё дойти). Холостая итерация стала `ld a,(de) / or a,a / jp Z` — три инструкции вместо дюжины с обращениями к памяти. Результат (такты MAME, холостой кадр): циклы по тайлам 27 936 -> 9 852 (2,8x) pop_loose_mob_tick 35 790 -> 7 416 (4,8x) pop_loose_tick 70 866 -> 24 384 (2,9x) ЗЕЛЁНЫЙ БЛОК 82 242 -> 35 760 (2,3x) Банки ужались: BANK3 -90 Б, BANK7 -74 Б. Все 8 наборов tests-host проходят; комната 23 в MAME рисуется корректно. Плюс ВРЕМЕННАЯ оснастка замера (маркеры m9..m16, pop_dbg_kind) — она же показала, что пик зелёного блока сидит НЕ в тряске плит, а в запекании тайлов; разбор продолжается, оснастку снять перед закрытием темы. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
50e4eda2ad |
Падающие плиты: передний слой по пометкам, честные спрайты, замер кадра
Слой fore переведён на пометки ОБЪЕКТОВ вместо окна вокруг персонажа — порт set_redraw_fore/redraw_frames_fore (seg007:0550, seg008:214): pop_set_redraw_fore + pop_fore_needed + pop_fore_tile_b, пометки ставит падающий кусок вокруг себя (draw_mob, seg007:1147). Ключевая правка: пометки соседа считаются в ПРОХОДЕ ОТРИСОВКИ, а не в тике. Стояли в цикле тика — то есть по позиции ДО mob_tick_one, а кусок рисовался уже по новой; пока он летит внутри ряда, тайлы совпадают, но в кадр пересечения границы пометка указывала на прежний тайл и колонна кусок не перекрывала. Ровно один кадр, как и наблюдалось. Спрайты падающего куска: obj_id = 10 (add_mob_to_objtable, seg007:1170), части берутся по этому индексу — 70/74/72, а не 41/43/42 (плита В ПОКОЕ). Отсюда была «цельная ровная плита» вместо двух половин со сдвигом. Убрана подпорка «перерисовать соседний тайл поверх куска»: тащила на плиту чужой узор и окно и стоила по полной отрисовке тайла на кусок за кадр. Коридор heal сужен по высоте 32 -> 24 (реальный след спрайта 16 px). gfx_set_bank вынесен из потайлового цикла в обрамление прохода. Выяснено и зафиксировано: clip.right = 40 в add_mob_to_objtable — МЁРТВЫЕ данные. set_clip_rect вызывается под chtab_flip_clip[chtab_id], а для окружения флаг равен нулю — оригинал падающие куски не клипует вовсе. Портировать нечего; две попытки это сделать были ошибочны. Замер кадра (маркеры m1..m4, оставлены временно для проверки правок): покой 196 614 тактов (15,2 % кадра при периоде 1 290 000), во время падения гряды среднее 222 667, пик 1 428 990 — то есть 111 % кадра, шесть кадров подряд за пределом. Логика неизменна (88 020), растут фон (до 887 376) и спрайты (до 660 792). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5d31ed086f |
Резидент: pop_redraw + холодная половина pop_level в банки (902 -> 3525 Б кучи)
MEM-COLD3, шаг 1-2 из плана разгрузки резидента: * pop_redraw.c целиком -> банк 7 (к pop_room.c: он и есть единственный потребитель разбора пометок). Два модуля в одном банке линкуются в один сегмент — проверено на .map (BANK7 7928 -> 8556). * pop_level.c расколот по частоте вызова: горячая половина (чтение тайлов, связи, дверные таблицы, живые стражи — зовут все банки, местами на каждый тайл) осталась в резиденте, холодная (чтение файла уровня, разбор комнаты, рестарт, потабличные различия) уехала в pop_level_cold.c -> банк 8. Общее состояние объявлено в _pop_level.h: данные банковых модулей всё равно линкуются в общий _DATA, в банк уехал только КОД. Побочно найден баг кодогена SDCC: `return 1;` из ветки не кладёт 1 в A — до __banked он маскировался случайным ненулевым остатком в A. Репро и разбор — docs/bugs/sdcc-z80-ret-const-lost/, стаб t_char починен одним выходом через переменную. Аудит всех .asm roomtest: других мест нет. _CODE 25628 -> 23005, куча 902 -> 3525 Б. tests-host 6/6. |
||
|
|
dc0bd47368 |
L3-CHOMP: чомперы — анимация, отрисовка и смерть в челюстях
Порт SDLPoP: animate_chomper / start_chompers / start_anim_chomper / next_chomper_timing (seg007) -> pop_trob.c; draw_tile_anim + draw_tile_fore, ветка tiles_18_chomper (seg008) -> pop_room.c (низ/кровь/верх, backtable) и pop_bg.c (передний слой); check_chomped_kid / check_chomped_guard / chomped (seg004) -> pop_map.c. Состояние — в room_modif, как у пик и ворот: младшие 7 бит фаза 1..N, старший бит «перемололо кого-то» (кровь остаётся на тайле навсегда). Номер позы chomper_fram1 и передние куски лежат в РЕЗИДЕНТЕ (pop_tile.c): их читают обе половины слоя фона, а const-таблица банка из чужого банка не видна. start_chompers зовётся там же, где в оригинале: SEQ_UP/SEQ_DOWN в play_seq (pop_kid.c), start_fall и land (pop_map.c), вход в комнату (roomtest.c). Поэтому чомперы щёлкают только пока персонаж в ИХ ряду — так в оригинале. check_chomped_guard у оригинала отдельное тело (страж не проходит через check_collisions). У нас та же формула уже есть в get_row_collision_data, поэтому флаги ряда считаются во ВРЕМЕННЫЙ массив: coll_curr/above/below — это кадр Кида, из них move_coll_to_prev берёт прошлые флаги, затирание сломало бы ему бамп. Период смыкания — POP_CHOMPER_SPEED в pop_tune.h (15, как в оригинале). Заодно: устаревшая заглушка pop_fore_set_clip в tests-host была __banked, хотя функция давно переехала в резидент — всплыло при пересборке. Ассеты уже были упакованы (pop_pack_bg.py, 2026-08-07). Банк 6: 20.0 %, банк 7: 39.7 %, банк 3: 53.8 %. tests-host зелёные (5 наборов). Зубья в MAME рисуются; анимация и смерть — на ручной приёмке. |
||
|
|
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>
|
||
|
|
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> |