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>
Три фикса, найденные разбором в 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>
Спуск Кида с кнопки (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>
Графика 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>
Фича — плита-ПОТОЛОК (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>
Порт 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>
- kbd_raw_sync: цикл вычерпывания SIO FIFO (не 1 байт/прерывание) —
фикс залипания клавиш; overrun-wipe сбрасывает только пострадавшие
клавиши, не модификаторы (typematic их не перечитывает).
- Гайд docs/kbd-games.md; заметка о Rx-overrun в docs/TODO.md; справочник
скан-кодов в docs/libc-reference.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Скролл региона video->video (неактивная страница -> активная, банк 0x50:
копия = скролл + heal цели):
- gfx_scroll_h / _bgi_scroll_rows_raw — горизонтальный, построчно без
страйдов (~54Т/строку), DI/EI бандами по 16 строк, h=0=>256;
- gfx_scroll_v / _bgi_scroll_cols_raw — верт. И/ИЛИ гориз. за один проход
без буфера (колонка = accel-burst LD A,A, STOP между read/write делает
промежуточный OUT Port_Y безопасным), банды по 16 колонок;
- _gfx_addr_shadow_base (адрес неактивной страницы) + gfx_rect_t.
Пример examples/scroll.
check_banks.py + sprinter-cc: отчёт раскладки памяти для ЛЮБОЙ модели
(W1/W2 код/данные, остаток кучи/стека, W3 при --w3, банки при --bank),
не только при --bank. Док docs/memory-management.md §10.
--w3 (резидентный код окна W3) + сопутствующее: crt0_banked W3_RESIDENT,
mkexe -W, tests/w3probe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Переходы в соседние комнаты по горизонтали (порт 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>
При пересечении нижней границы комнаты 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>
Отказ от 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>
Проваливающиеся полы в 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>
Дополнен точными данными из SDLPoP (сверено): кадровые таблицы
loose_fram_left/right/bottom, get_loose_frame, y_loose_land, delay=11;
триггеры с call-sites (check_press->make_loose_fall при стоянии на 11,
frame79-сверху; do_knock на жёстком приземлении; animate каждый кадр);
tile_is_floor(11)=1. Плюс подход интеграции в наш движок: общая
мутабельная копия комнаты pop_map<->pop_bg + per-page запекание пустоты
после падения; тонкое место — перерисовка динамического тайла в дабл-буфере.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Тап-бег с кромки [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>
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>
Убирает мерцание/тиринг при перерисовке слоёв (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>
Разбор SDLPoP (seg007 loose/trob/mob, seg008 draw_loose): хранение
состояния (tile 11 + curr_room_modif: 0 покой / 0x80.. тряска / 1..11
отсчёт падения), триггеры тряски (do_knock на приземлении → shake ряда)
и падения (make_loose_fall при стойке/зацепе на loose), падающий кусок
(mob → debris снизу, empty сверху), отрисовка по статусу (loose_fram_*
через get_loose_frame). Порядок реализации L1..L5 + что нужно в нашем
движке (динамический тайловый слой: room_modif[], trob-очередь, mob).
ПЛАН — не реализация.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Проблема из динамики: при подъёме [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>
Баг (из динамики): стоя на кромке [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>
Проблема из динамики: за стенами Kid не прятался (стена [2,9] должна
перекрывать). В PoP основная грань стены (wall_fram_main) добавляется в
FORETABLE (seg008:712) — перекрывает персонажа. У нас fore-проход
рисовал только fore_id, а у стены (0x14) fore_id=0.
fore_tile: для стены (code==20) рисуем WALL_FRAM_MAIN + wall_pattern(,,1)
поверх Kid (как draw_tile_fore), а не только fore_id. Проверено в MAME:
Kid прячется за правой стеной col9.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Порт SDLPoP seg003 redraw_at_char + seg008 set_char_collision: после
kid_draw передний слой (fore_id = foretable-кусок) тайлов ФУТПРИНТА
спрайта Kid перерисовывается ПОВЕРХ него — то, что по изометрии перед
персонажем (передние грани колонн/ворот/большой колонны/щебня). Стены и
факелы имеют fore_id=0 → остаются сзади (в статическом фоне).
- pop_bg: pop_fore_over_kid(obj_x,obj_y,w,h,dir) — считает футпринт
(char_x_left/right, col_from_x, y_to_row_mod4) рядов top..bottom ×
колонок left..right (≤2×2=4 тайла) и рисует fore_id каждого в
GFX_BANK_SPRITE (видео-ОЗУ; kid_heal восстановит из теневого фона,
fore в нём запечён pop_room_draw).
- pop_kid: kid_fp_obj_x/y/width/height — метрики последнего кадра
(obj_x ЛОГИЧЕСКАЯ, до ×8/7) для футпринта.
- roomtest: вызов pop_fore_over_kid после kid_draw.
Проверено в MAME: Kid, идя влево мимо колонны под навесом (row1 col3),
корректно уходит ЗА её переднюю грань (скрывается) и выходит с другой
стороны; задние колонны/факелы остаются сзади.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Порт SDLPoP seg004/005/006 «hang state»:
- pop_map: check_grab (зацеп за уступ в падении по Shift → seq_15 → вис),
can_grab/can_grab_front_above + tile-запросы над/за персонажем;
pop_jump_up_seq (check_jump_up: ↑ в стойке = чистый прыжок seq_28/14
ЛИБО прыжок-с-зацепом seq_8/24/16 за уступ выше → запрыгнуть на этаж);
pop_hang_* (climb_up seq_10/73, hang_fall seq_23/11, hang-у-стены);
pop_down_action (спуск seq_68 у края лицом от края / отступ / присед).
- pop_ctrl: control_hanging/can_climb_up/hang_fall, control_jumpup,
jump_up через pop_jump_up_seq, down_pressed через pop_down_action;
pop_ctrl_shift_held() для check_grab.
Ключевой фикс check_bumped (seg004 guard'ы): не бампить при action
hang_climb/hang_straight, на кадрах подъёма/спуска 135..148 И на кадрах
виса 87..99. Без последнего спуск (seq_68) на кадре frame_91 (action
ещё midair, act(hang_climb) идёт следующим опкодом) ловил отскок у стены
и рвал цепочку hang→hang_fall→seq_11, приземляя не туда.
Проверено в MAME (HDD-тест, покадровая трасса через мост): прыжок-с-
зацепом [row2 col4]→вис→подтягивание→[row1 col3]; спуск [row1 col3]→
[row2 col4] с корректной позицией у стены (x=122, совпадает с SDLPoP).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Клавиши 1/2/3 зовут gfx_set_fps_div(n) на лету (дефолт n=1 не меняет
поведение). Проверено в MAME: при n=2 FPS-метр стоит РОВНО на 024
(48.83/2) все 8 секунд без плавания — логический кадр = ровно 2 vsync'а,
скорость персонажей (пиксель/сек) постоянна. +808 Б (демо тянет код
делителя).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Логический кадр = ровно n кадровых интервалов (1=50/2=25/3=~16.7 fps);
при переполнении слота — выравнивание на ближайший фронт (без дрейфа
фазы, в отличие от наивного «жди n фронтов»).
Механика: фоновый счётчик _gfx_frame_tick инкрементит _gfx_frame_isr,
поставленный в СВОЙ слот цепи (irq_chain_add); gfx_set_fps_div(1) снимает
только этот слот (irq_chain_remove), не трогая хендлер приложения.
gfx_wait_vsync: ветка n>=2 (счётчик + halt) перед лучевым поллингом;
поллинг вынесен в static gfx_wait_vsync_beam (функция с хвостовым __asm
не должна иметь переходов через asm — SDCC не эмитит эпилог-метку;
ранний return делителя в чистом-C gfx_wait_vsync).
Файлы: common/_gfx_fps_state.c (данные), _gfx_frame_isr.c (ISR),
gfx_set_fps_div.c (сеттер, единственная ссылка на irq-механику → DCE).
Работает tiny/big/huge (цепочка all-modes); small для мелких программ
= EINVAL.
Проверено MAME (tests/fpsdiv): n=1/2/3 → 20/40/60 кадров на 20 wait'ов
(drift=0); n=2 с рендер-заглушкой ~1 кадр → период держится 2
(поглощение перерасхода, наивный путь дал бы ~60); huge идентично;
small = EINVAL graceful.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_irq_user → _irq_chain[4]+_irq_chain_n (W2-BSS); трамплин tr_frame
проходит слоты (один тяжёлый сейв на всю цепь, пустой слот пропуск).
API irq_chain_add (0/-1+ENOMEM) / irq_chain_remove(h); irq_install →
обёртка chain_add (EBUSY исчез), irq_remove() рвёт всю цепь; refcount
таблицы на первом/последнем слоте, мутации под IRQ_DISABLE. Лимит
4 кадровых + 1 CTC.
All-modes: трамплин copy-safe (только jr/djnz + литерал jp 0x0038),
_irq_table_ref копирует его в _irq_tramp_w2buf (W2) когда оригинал в W1
(small/huge), вектор → на копию; вокруг вызова хендлеров восстанавливает
базовую W1-страницу (_irq_app_w1_page = IN 0xA2). Хендлер может лежать
где угодно в плоском 0x4000-0xBFFF.
Проверено в MAME (tests/irqtest, 2 хендлера): tiny/big/huge — chain
h1=h2 → remove h2 → h1 жив/h2=0; huge = код в W1, remap работает.
Follow-up: CTC в small/huge = EINVAL (нужна W2-копия _irq_ctc_tramp);
small для мелких программ (BSS в W1) = irq_install EINVAL, safe.
Доки: im2_isr_design (цепочка), sprite-api §9е (делитель поверх цепи),
fast_ram §8. rpgprof: gfx_sprite_ysort в профиль (+230 Б baseline).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
wpiset на порт W1 (0xA2) + iff1 в dev-MAME: DSS перемапливает W1 при
ВКЛЮЧЁННЫХ прерываниях во время системных вызовов (файловые, загрузка
exe, видео; страницы 0xFE/FF/F3/0x50, PC ядра 0x15xx-0x2Exx) —
ограничение irq_install «только tiny/big» обосновано, handler в
W1-коде небезопасен принципиально. §9е дополнен фактом; в TODO —
дизайн цепочки irq-обработчиков (массив слотов в W2, chain_add/remove,
irq_install как обёртка; реализация по потребности).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Оптимизации A+B (профиль rpgwalk-15: активная часть кадра 410К → 307К
тактов из 430080; лимит спрайтов 16×16 на стабильные 48.8 fps: 14 → ~21):
- (B) sprite_t.src/stride — готовый адрес кадра: считают только
sprite_frame (теперь функция, одно умножение на СМЕНУ кадра) и тикер
(±an_step БАЙТ инкрементально); блит-ядра принимают src+stride,
img/sx/sy из сигнатуры ушли. Блит 177К → 146К на кадр.
- (A) тик 100К → 37.7К: tween переформулирован Брезенхэм → беззнаковый
DDA (mv_rem/mv_acc, «приехали» = rem==0 — без знаковых 16-бит
сравнений), tick_move и tick_anim — ручной asm (SDCC спиллит такие
функции в IX-фрейм ~100 обращений; C-реструктуризации не помогали —
проверено кодогеном). Биты an_flags переименованы по категориям
(_SPR_STRIP_HORZ, _SPR_PP_BACK).
Y-сортировка (gfx_sprite_ysort, идеи пользователя — 8-бит ключ,
персистентность):
- painter's algorithm по ключу {layer:8, clamp_y:8}; поле
sprite_t.layer (в КОНЦЕ структуры — asm-офсеты не сдвигает): слои
сцены в одном массиве/одном sprite_update;
- ПЕРСИСТЕНТНАЯ asm-таблица {key16, ptr16}: resort порядка прошлого
кадра (почти линейно), rebuild при смене arr/count; массив
приложения не трогается; ~28К/15 спрайтов (с нуля было 44К);
- компоненты YSORT_Y/YSORT_LAYER отключаемы независимо масками ключа
(без ветвлений в сортировщике); ВНИМАНИЕ: mode=1 значит Y-only,
полный порядок = YSORT_Y|YSORT_LAYER;
- funcptr-DCE: выключено = код и таблица не линкуются (rpgwalk −190 Б);
- ПРАВИЛО в sprite.h: два sprite_update на страницу запрещены (heal
второй группы стирает спрайты первой — ОЗУ-копия чистая).
Попутные фиксы:
- libbgi/Makefile: .rel зависят от заголовков (HDRS) — stale .rel со
старой раскладкой sprite_t молча ломал рантайм;
- rpgprof: --memory small (перерос tiny: BSS вылезал за W2 → мгновенный
«Unexpected application termination»; mkexe это пока не ловит);
- tests/spranim: проверки переведены на кэш src, добавлены T6 (reframe
после тикера) и T7 (Y-сортировка: порядок, слои, персистентный
resort, LAYER-only) — 7/7 PASS в MAME.
Доки: §9д — новый бюджет (19.5К/спрайт), §9е — ПЛАН FPS-делителя
(frame pacing, gfx_set_fps_div); TODO — дизайн цепочки irq-обработчиков.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Плашка слева-сверху (значение раз в секунду, рисуется на обеих
страницах — fps_draw=2, как в examples/space). ~39 fps на 8 ходящих
персонажах (кадр на грани 20 мс — частично двухvsync'овые кадры).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Демо на реальном арте (third_party/16x16-RPG-characters, bard):
conv_sprites.py режет PNG 192×128 (8 персонажей = блоки 3×4 кадров:
ряды вниз/влево/вправо/вверх × кадры маятника 0/1/2) в 8 вертикальных
лент по 12 кадров и строит bard.pal (слоты 0-15 EGA + цвета PNG с 16,
прозрачность → 0xFF). 8×12 кадров не лезут в одну EMM-страницу —
ДВА атласа по 4 персонажа (движок сам переключает страницы W0).
Палитра из файла: gfx_pal_fload + gfx_pal_sync. Смена направления =
sprite_anim(dir*3, dir*3+2, PINGPONG). Правила хождения (двухфазная
машина на sprite_moveto): до края → разворот 180° → случайная точка
(не меньше четверти экрана) → поворот ±90° → снова до края.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- gfx_pal_sync(): палитра страницы 1 := палитре 0 (все 256 записей,
чанками по 64 через общий буфер _gfx_pal_buf в W2) — обязательный
шаг дабл-буфера (грабли examples/space: без него флип на страницу 1
чёрный). examples/space переведён на хелпер.
- gfx_pal_fsave(pal, path): 256 записей × 4 Б (B,G,R,0 — родной формат
BIOS $A4) = 1024 Б.
- gfx_pal_fload(pal, path): принимает и усечённый файл (64 Б = палитра
16 цветов) — грузит сколько есть, остальные слоты не трогает;
возвращает число записей.
tests/palfile (MAME dev, все PASS): fsave; fload восстанавливает
испорченные слоты (n=256); усечённый файл на 2 записи чинит только их
(n=2, слот 2 не тронут); sync чинит испорченный слот палитры 1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Всё сразу: .atl с диска (mkatlas.py из генерённых лент) → atlas_load в
EMM-страницу (W0, ISR-стаб) → движок sprite_t.page → авто-анимации:
5 астероидов (ANIM_LOOP вращение + sprite_moveto к случайным целям
разных скоростей; по прибытии — взрыв ANIM_ONCE в точке + новая цель,
по SPR_ANIM_DONE взрыв прячется), маяк ANIM_PINGPONG; дабл-буфер,
FPS-метр, ESC. ~48 fps (vsync-кап).
Грабли по дороге (оба — прикладные, не библиотека):
- фон рисовался rand()'ом с разными последовательностями на страницах
→ мерцание звёзд; фикс — фиксированный seed на draw_space;
- НЕ была скопирована палитра страницы 0 → 1 (у каждой страницы своя,
см. gfx.h) — страница 1 показывалась чёрной, флип мигал
«сцена/чёрный»; fps-плашка теперь рисуется на ОБЕИХ страницах
(fps_draw=2 кадра при смене секунды).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Реализация §9г по требованиям пользователя:
- sprite_anim(first,last,speed,mode): ANIM_LOOP / ANIM_PINGPONG /
ANIM_ONCE (one-shot замирает на last), | ANIM_HORIZ для
горизонтальных лент. Смена кадра в тике = ±an_step к оси ленты —
без умножений (осевое смещение first считается в setup циклом).
- sprite_moveto(tx,ty,max_step,interval): Брезенхэм порциями
≤max_step вдоль большей оси (меньшая — err-аккумулятором,
нелинейные шаги Y сами собой), деления нет.
- Одновременность кадровой и tween — независимые поля/биты.
- Статус: sprite_anim_status() — битовое поле SPR_ANIM_ON/DONE +
SPR_MOVE_ON/DONE; sprite_anim_frame() — текущий индекс кадра;
sprite_moving().
- Стопы: sprite_anim_stop(frame | -1 = текущий);
sprite_move_stop(0 = замереть / 1 = прыжок в цель + DONE).
- Тикер _sprite_tick — проход 0 sprite_update через funcptr
_spr_tick_fn (DCE: без sprite_anim/moveto код не линкуется,
цена — один if на кадр; +40 Б программе с движком, sprite_t +18 Б).
tests/spranim (MAME dev, все PASS): LOOP/PINGPONG (разворот на границе
без удвоения краёв)/ONCE+DONE; пример (0,0)→(100,50), шаг 5,
интервал 4 → ровно 80 кадров, Y идёт 3/2/3/2; стопы обоих видов.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Решение: in-memory формат остаётся (getimage + sx/sy, ленты/сетки).
Рекомендация: кадры одного спрайта — одного размера с полной сеткой;
для спрайтов разных размеров — контейнерный формат .atl (каталог +
независимые getimage-ленты, паддинг невозможен по построению).
Формат — предложение, реализация по потребности первого приложения.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Диспетчеризация clip/noclip через указатели _gfx_blit_fn/_gfx_heal_fn
(common/_gfx_sprite_fns.c, дефолт clip): gfx_sprite_clip() — теперь
модуль, переключает указатели один раз; sprite_update/putsprite/
movesprite зовут через указатель — ветка if(clip) из горячего цикла
убрана (съедала половину выигрыша noclip). Программа без вызова
gfx_sprite_clip() noclip-ядра не линкует.
Замер dev-MAME (16 шаров, uncapped): clip 50 → noclip 60-61 fps
(+20-22%). Регресс tests/sprites (A/B PASS), size-check OK
(balls −237 Б, sprites −3177 Б — отвязались лишние ядра).
Фикс CPU-байта write-триггера (preread + EX AF,AF') снят: точная
dev-MAME эмулирует ПЛМ, подавляющую CPU-байт при активном burst'е —
подтверждено по байтам VRAM (tests/blitw col0 = GREEN через
read_vram MCP-моста). Для heal фикс был избыточен всегда (банк 0x50
перезаписывает dst[0]). Строки фикса оставлены закомментированными
в трёх leaf'ах на случай отличий реального железа; шапки и §9а/§9б
дизайна обновлены. НА ЖЕЛЕЗЕ ПЕРЕПРОВЕРИТЬ.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Спрайтовая графика поверх accel block-copy (docs/sprite-api-design.md):
- Ядро блиттинга: leaf'ы _bgi_blit_rows_raw (dst фикс, только src-страйд) /
_bgi_copy_rows_raw (getimage) / _bgi_heal_rows_raw (src==dst). DI один на
спрайт (санкция: малый спрайт под одним DI аудио не рвёт); src[0]-фикс
снят (точная MAME подавляет CPU-байт триггера записи — на железе
перепроверить; для heal был избыточен и снят безусловно).
- Общие bracket-free ядра _gfx_blit_full/_gfx_heal_full (полная ширина:
клип по экрану + split >256 для putimage) + лин _gfx_blit_sprite/
_gfx_heal_sprite (кадр ≤64, без split, 8-бит w/h) + noclip-варианты
(клип-кода нет → полный codegen-win). Имя *_full (не *_clip) — «clip»
двусмысленно (sprite-ядра тоже клипуют; различитель — ширина/split).
- Движок retained-модели <sprite.h>: sprite_init/update/flip + inline
move/frame/show/hide/touch; drawn[2] per-page внутри структуры; кадр —
двухпроходно heal ВСЕ -> блит ВСЕ под одной W3-скобкой/банком на проход.
- Флаг gfx_sprite_clip(on/off): приложение, само следящее за границами,
отключает клип (~+19% на анимации; диспетч пока через if — funcptr далее).
- putsprite/movesprite/gfx_blit/putimage(COPY)/getimage переведены на ядро.
Тесты: examples/balls (движок, дабл-буфер, boundary-тест клипа),
tests/sprites (RAM PASS, клип 4 края, атлас), tests/blitw (trig-leak),
tests/spriteclip (hardware-probe: железо НЕ режет за краем -> клип нужен),
tests/blitperf, tests/gfxbanks. size-baseline обновлён (53 программы).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Симметрично libbgi (bgi256/bgi256_safe): fast = -DLIBC_NOCHECK,
дефолт sprinter-cc; safe линкуется по --safe (флаг уже существовал).
Под LIBC_NOCHECK вырезаны ТОЛЬКО параметр-валидации:
- fgetc/fputc: NULL-check в горячей asm-обёртке (~11Т на каждый байт);
- fgets/fputs/fread/fwrite: NULL ptr/fp и EBADF на неверное направление
потока; ftell/fseek/ungetc/fclose: NULL fp; cputs: NULL s.
НЕ тронуты: критичный _fd_guard (9-й OPEN вешает DSS — в обеих
версиях), функциональная маршрутизация (консоль/направление/hold),
cold-path валидации (fopen/cbl_open/irq/settextmode — экономии ноль).
libc/Makefile — dual-build по образцу libbgi (build/fast + build/safe,
общий стейл-контроль). Корневой Makefile: в TESTS добавлены bgitest,
bgi_img, accfill, cblstream — раньше не собирались корневым make и
выпадали из size-check при чистой пересборке.
Дельты fast vs старая (safe-семантика): filetest -481, fbench -384,
solidt -180, errno -71, остальные -3..-9; роста нет. Проверено в MAME:
filetest fast и safe (--safe, sprinter_safe.lib) — прогоны идентичны.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Inline (чистый C99 `inline` без static, паттерн из libbgi/5de2f06):
textattr, get_text_attr, get/set_putch_raw_mode (conio.h; g_text_attr
и pc_raw_mode объявлены публично), isatty (unistd.h — сворачивается в
константу при константном fd). Модули удалены (5 шт).
«Толстые» кандидаты НЕ инлайнены — критерий проверен замером на 47
программах: feof/ferror/clearerr (тело ~12-15 байт с NULL-проверкой,
filetest +56 Б при инлайне) и textcolor/textbackground/set_text_attr
(RMW-маски, conio2 +13 Б) остаются модулями — при 2+ сайтах вызова
инлайн крупнее call+общее тело. Правило: инлайнить только тела
<= ~6 байт на сайте или сворачиваемые константами.
Дельты: solidt -94, hello -24, mouse -7, bios_text -7; conio2 +11
(textattr×5 — паритет, принято за скорость). z80.lib SDCC не содержит
feof/isatty — маскировки удалённых модулей нет, провал инлайна = ошибка
линковки. Проверено в MAME: conio2 (атрибутная матрица), filetest
(полный прогон). В size_baseline также вошли gfx_dbuf +608/gfx_demo
+25 — это первый чистый релинк run-рендера текста (ba09c0b), не inline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
setcolor/getcolor/setbkcolor/getbkcolor/getmaxx/getmaxy/getmaxcolor/
getx/gety/moveto/moverel/setfillstyle/graphresult (graphics.h) и
gfx_get_bank/gfx_set_bank/gfx_get_draw_page/gfx_get_visible_page
(gfx.h) определены inline в публичных заголовках; state-переменные
объявлены там же (хранилище прежнее — _bgi_state.c/_gfx_state.c).
Именно `inline` БЕЗ static: проверено артефактами (.asm) — SDCC 4.5
инлайнит вызов при всех наших флагах (--opt-code-size/--opt-code-speed/
--max-allocs) и не эмитит standalone-тело; `static inline` эмитил бы
мёртвую копию каждого аксессора в КАЖДЫЙ включивший модуль. Отказ
инлайнить = громкая ошибка линковки (все 47 программ слинковались).
Экономия ~30-40Т на вызов, минус 17 .rel; по _CODE размер-нейтрально
(сайт вызова ≈ телу). size_baseline: bgitest +267/accfill +528 — это
НЕ inline, а run-рендер текста из ba09c0b (draw_scaled потянул
vspan_raw+vfill256 и сам вырос) — цена за ~2-6× скорость текста.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Строка глифа сканируется на прогоны единичных битов; прогон n битов =
прямоугольник n*size×size → hspan/vspan-примитивы (одиночный пиксель —
plot_raw). Координаты инкрементальные (+= size за бит) — умножений на
пиксель нет. Прогон клипится до span'а — клип теперь работает и в
fast-сборке (span-raw диапазон не клипят). ~2× на size=1, ~6× на
size=4. Проверено в MAME (bgitest: масштабы 1..4 + VERT — пиксель в
пиксель).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
plot/read/hspan/vspan_raw читают D/E (y в DE), но не пишут — аннотация
задокументирована в _bgi.h. Честное измерение (полная пересборка с/без,
diff всех .asm в common/ и bgi256/): кодогенерация SDCC 4.5 НЕ меняется —
вызывающие держат локали в IX-фрейме и перезагружают DE перед каждым
вызовом, спасений DE вокруг вызовов не было. Оставлено как контракт на
будущее (изменение вызывающих/компилятора); при правке asm сверять
клоббер-лист.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bar(): rectfill-ветки выходили ранним return без _bgi_end() — W3
оставался замаплен на видеобанк после каждого bar() со сплошной
заливкой.
_bgi_read дублировал getpixel (та же композиция begin+read_raw+end);
единственный потребитель floodfill переведён на getpixel, модуль
удалён. getpixel.c: убраны мёртвые статики _gp_* (остались от
до-register версии).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>