From a5773ab6542157f8f934d5f1cf6be0f47a47dd28 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 29 Jul 2026 18:55:36 +0300 Subject: [PATCH] =?UTF-8?q?docs(PoP):=20=D0=BF=D0=BB=D0=B0=D0=BD=20v2=20?= =?UTF-8?q?=E2=80=94=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0=20=D0=B8=20=D1=80=D0=B0=D1=81=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B0=20=D0=BF=D0=BE=20=D0=BE=D0=BA=D0=BD=D0=B0?= =?UTF-8?q?=D0=BC/=D0=B1=D0=B0=D0=BD=D0=BA=D0=B0=D0=BC/=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=86=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новый документ applications/PoP/docs/layout_plan_v2.md по свежему замеру (коммит 1214785): точные размеры окон/модулей/функций/данных, уточнённая модель банкинга и пошаговый план. Главное уточнение против v1: из __banked-кода резидент W3 недостижим — и транзитивно тоже (bank -> pop_map -> pop_bg сломается). Отсюда целевая раскладка: W3-резидент = графика, которую зовёт только главный цикл; W1/W2 = ядро, достижимое отовсюду (включая банки); банки = новая холодная логика (стражи/боёвка). Проверено по libbgi: скобка _bgi_begin/_bgi_end сохраняет и возвращает ТЕКУЩУЮ страницу W3, поэтому примитивы libbgi можно звать и из банка; нельзя лишь открывать скобку из кода, лежащего в W3. Крупнейшие цели: kid_data.h (3745 Б таблиц в _CODE) -> EMM-страница с портом load_frame/cur_frame; вынос loose/потолка из pop_map в W3 (делает pop_map bank-safe); дедуп геометрии в pop_geom.c; разгрузка _DATA. Попутная находка: --w3 принимает ОДИН файл на флаг, поэтому в Makefile "--w3 pop_trob.c pop_map.c" кладёт в W3 только pop_trob, а pop_map едет в W1/W2 (в build-каталоге остался устаревший w3_pop_map.rel). Co-Authored-By: Claude Opus 5 --- applications/PoP/docs/layout_plan_v2.md | 370 ++++++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 applications/PoP/docs/layout_plan_v2.md diff --git a/applications/PoP/docs/layout_plan_v2.md b/applications/PoP/docs/layout_plan_v2.md new file mode 100644 index 0000000..1e4bbe9 --- /dev/null +++ b/applications/PoP/docs/layout_plan_v2.md @@ -0,0 +1,370 @@ +# roomtest — план v2: размер кода и раскладка по окнам/банкам/страницам + +Статус: **план для отдельной сессии**, составлен 2026-07-29 по свежему замеру. +Заменяет `size_optimization_plan.md` (v1, 2026-07-21): часть его пунктов уже +сделана, часть опиралась на неверную модель банкинга. Документ самодостаточный +— рассчитан на старт с пустого контекста. + +Повод: перед стражами и боёвкой (новый код ~5–8 КБ) надо понять, куда он +поместится, и заранее развести код так, чтобы банкованные модули не упёрлись в +ограничения окна W3. + +--- + +## 0. Что уже сделано из v1 (не повторять) + +- `--opt-code-size` и `--max-allocs 100000` **уже включены по умолчанию** в + `bin/sprinter-cc` (v1 §2.1 закрыт, выигрыш получен). +- Лишние блиты переднего слоя убраны (v1 §7 п.0): `fore_tile` больше не рисует + `bottom_id`, `_CODE` −388 Б. +- `gfx_blit_noclip` в libbgi (v1 §8 шаг 1): фоновые блиты в 2.9× дешевле. +- `--w3` как резидент окна 3 реализован и обкатан (memory `w3_resident_code`). + +--- + +## 1. ЗАМЕР (сборка 2026-07-29, коммит 1214785) + +Команда: `--memory small --gfx 256 --w3 pop_trob.c pop_map.c --w3 pop_bg.c`. + +### 1.1 Окна + +| Область | Занято | Свободно | Примечание | +|---|---|---|---| +| W1+W2 `_CODE` | 24 881 Б | — | 0x4100…0xA231 | +| W1+W2 `_HOME`+`_GSINIT`+`_DATA`+`_BSS` | ~4 240 Б | — | до 0xB2E4 | +| **W1+W2 куча** | 0 (никто не malloc'ит) | **2 076 Б** | 0xB2E4…0xBB00 | +| W1+W2 стек | — | 1 279 Б | 0xBB00…0xBFFE | +| **W3 резидент** | 14 376 Б | **2 008 Б** | 0xC000…0xF828 | +| EMM-страницы | 37 атласов + 1 уровень | ~215 страниц свободно | `sprinter_emm_budget` | + +**Итого запаса до стены: ≈ 4 КБ** (2 КБ в W1/W2 + 2 КБ в W3). Стражи туда +не влезут. + +### 1.2 Код по модулям (точно, из `.rel`) + +``` +W1/W2 (_CODE 24 881): W3 резидент (_W3CODE 14 376): + pop_kid 6 963 pop_bg 11 643 + pop_map 6 157 pop_trob 2 733 + roomtest 2 583 + pop_level 1 425 + pop_ctrl 1 145 + crt0 333 + libc+libbgi ~6 275 +``` + +### 1.3 Крупнейшие функции/данные (из `.lst`) + +``` +pop_bg : draw_tile 3080, other_overlay_tile 1146, wall_pattern 944, + mob_render 720, mob_tick_one 661, overlay_mid_tile 498, + fore_only_tile 409, climb_overlay_tile 391, tile_table 371 +pop_map : check_loose_fall_on_kid 674, check_bumped 567, jump_up_or_grab 413, + get_tile 266, do_knock 243, check_press 238, check_leave 212 +pop_kid : kid_seqtbl 2310 + kid_frames 1205 + kid_seq_off 230 = 3745 Б ДАННЫХ + (в _CODE!), собственно кода ~3.2 КБ +roomtest : enter_room+main ~2.1 КБ +pop_level: room_bg_ptr 1084 (+ 515 Б таблиц LINKLOC/LINKMAP в _DATA) +``` + +### 1.4 `_DATA` (3 710 Б) + +``` +pop_trob 963 (room_modif[24][30] = 720 + trobs + rest-массивы) +pop_level 515 (копии LINKLOC/LINKMAP уровня) +pop_map 163, roomtest 141, pop_kid 130, pop_bg 115, pop_ctrl 13 +libc: _irq_state 818, _kbdraw_state 515, _gfx_pal_buf 256, прочее ~200 +``` + +### 1.5 Находки замера (мелкие, но чинить) + +1. **`--w3` берёт ОДИН файл на флаг.** В `Makefile` написано + `--w3 pop_trob.c pop_map.c --w3 pop_bg.c`, и это значит «W3 = pop_trob и + pop_bg», а `pop_map.c` компилируется как обычный исходник в W1/W2. Судя по + `.sprinter-cc-roomtest/w3_pop_map.rel` (устаревший артефакт), когда-то + pop_map был в W3. **Решить осознанно** (см. §4) и записать явно: + `--w3 pop_trob.c --w3 pop_bg.c`. +2. `libc` тянет `_irq_state` 818 Б + `_kbdraw_state` 515 Б в `_DATA`. + `__irq_vec_buf` (513 Б) — таблица векторов IM2; `__kbdraw_down` (512 Б) — + битмап клавиш на 512 скан-кодов. Оба можно ужать (см. §5.4), это ~0.7 КБ + в самом дефицитном окне. + +--- + +## 2. ПРАВИЛА ПЛАТФОРМЫ, ОТ КОТОРЫХ ПЛЯШЕТ РАСКЛАДКА + +Это главное, что изменилось по сравнению с v1: модель банкинга уточнена по +`bin/sprinter-cc` (справка `--w3`/`--bank`) и по коду libbgi. + +**(R1) Резидент W3 (`--w3`) и банки W3 (`--bank`) делят одно окно.** +Резидент лежит на своей странице 0xC000…0xFFFF; трамплин на время вызова +`__banked` подменяет страницу W3 на банковую и возвращает резидентную назад. + +**(R2) Из банка резидент W3 НЕДОСТИЖИМ — и транзитивно тоже.** +Пока исполняется банк, резидентной страницы в адресном пространстве нет. +Значит нельзя не только `bank → pop_bg()`, но и `bank → pop_map() → pop_bg()`. +**Это ключевое ограничение при выборе, что делать банком.** + +**(R3) Резидент → банк работает** (через трамплин в W1), резидент → W1/W2 — +тоже. + +**(R4) Графические примитивы libbgi звать можно откуда угодно.** +`_bgi_begin` читает текущую страницу W3 из порта 0xE2, а `_bgi_end` её +возвращает — то есть скобка корректна и из банка, и из резидента. Нельзя +только одно: **звать `_bgi_begin`/`_bgi_end` ИЗ кода, который сам лежит в W3** +(после подмены страницы исчезнет исполняемый код — проверено, белый экран). +Поэтому `pop_bg` (резидент W3) обязан пользоваться готовыми примитивами +(`gfx_blit*`, `bar`, …), а батчинг скобки на весь `draw_tile` (v1 §8 шаг 1) +для него **невозможен** без переноса самого `draw_tile` в W1/W2. + +**(R5) `--w3` кладёт в W3 код И rodata модуля** (`--codeseg/--constseg +W3CODE`), а писучие статики оставляет в `_DATA` (W2). То есть `const`-таблицы +переносятся в W3 бесплатно вместе с модулем (так уже лежит `tile_table` 371 Б). + +**(R6) Данные в EMM-странице читаются, только пока страница в окне.** +`gfx_w0_map(page)` / `gfx_w0_unmap()` — окно W0 (0x0000…0x3FFF), первые 0x100 +занимает ISR-стаб. Так уже работает `pop_level`. Цена — пара `OUT` на +маппинг, поэтому годится для «пачками», а не для чтения по байту в горячем +цикле. + +--- + +## 3. ЧТО ДЕЛАТЬ НЕЛЬЗЯ (анти-паттерны, чтобы не потерять время) + +- **Нельзя банковать `pop_bg`.** Он вызывается из pop_map, pop_trob, roomtest, + pop_kid — то есть из главного цикла на каждом кадре; плюс он сам держит + `tile_table` и всю отрисовку. Банк дал бы трамплин на каждый блит. +- **Нельзя банковать `pop_map`, пока `pop_map` зовёт `pop_bg`** (R2). Сейчас + зовёт: `pop_loose_tick` и компания (~30 вызовов графики). +- **Нельзя тащить `kid_frames` в EMM «в лоб»**: он читается несколько раз за + кадр из коллизии (`kid_cur_dx`/`kid_cur_flags` → `dx_weight`, + `char_x_forward_edge`, …). Нужен кэш кадра (см. §5.1) — иначе маппинг + страницы окажется в горячем пути. +- **Нельзя «причёсывать» семейство `draw_tile` ради экономии, не имея + пиксельного теста.** Мы неделю выравнивали слои по SDLPoP; любой рефактор + этой зоны проверять диффом страниц (заморозка кадра клавишей `1` + сравнение + VRAM обеих страниц, приём из memory `mame_mcp_bridge`). + +--- + +## 4. ЦЕЛЕВАЯ РАСКЛАДКА + +Принцип: **W3-резидент = «толстая графика, которую зовёт только главный цикл»; +W1/W2 = ядро, которое должно быть достижимо ОТОВСЮДУ (включая банки); банки = +новая холодная логика (стражи, боёвка, будущие уровни)**. + +``` +W1/W2 (всегда отображено) W3 резидент (стр. 0xC000) Банки W3 +────────────────────────── ───────────────────────── ───────── +libc + libbgi pop_bg (отрисовка тайлов) guards.c +pop_kid (интерпретатор+рисование) pop_trob (анимации тайлов) fight.c +pop_map (коллизия/физика/предметы) pop_loose.c (loose+потолок) debug/roomnav +pop_geom (общая геометрия/тайлы) enter_room-часть roomtest? +pop_ctrl (ввод/диспетчер) +roomtest (главный цикл) +``` + +Почему так: + +- **`pop_map` остаётся в W1/W2** — его зовут и главный цикл, и (в будущем) + банк стражей; в W3 его класть нельзя именно из-за R2. Для этого из него надо + вынести графическую часть (loose/потолок) — она уезжает в W3 к `pop_bg` + (§5.3). После выноса `pop_map` становится **чистой логикой без единого + вызова графики** — тот самый bank-safe API. +- **`pop_kid` остаётся в W1/W2**: `play_seq`/`kid_set_seq`/`Kid` нужны и + стражам (у стражей ТА ЖЕ seqtbl), а `kid_draw` зовёт только libbgi (R4). +- **`pop_trob` остаётся резидентом**: его зовёт только главный цикл, и он сам + зовёт `pop_bg` — идеальный житель W3. +- **Банк стражей не зовёт ничего из W3.** Рисование стражей — либо через + libbgi напрямую (R4), либо (лучше) резидентный `guard_draw()` в W1/W2 рядом + с `kid_draw`, а банк только считает состояние. Тот же приём мы уже + используем для `pop_item_taken`/`pop_loose_fell`/`pop_ceil_fell`: банк + выставляет флаг — резидент рисует. + +--- + +## 5. ПЛАН РАБОТ + +Порядок выбран так, чтобы каждый шаг был проверяем отдельно и давал место +следующему. + +### Шаг 1. `kid_data.h` (3 745 Б) → EMM-страница + порт `load_frame` — **самый большой выигрыш** + +Сейчас `kid_seqtbl` (2310) + `kid_frames` (1205) + `kid_seq_off` (230) лежат в +`_CODE` окна W1/W2 — это 15 % всего дефицитного пространства. + +Как переносить: + +1. `pop_extract_kid_data.py` дополнительно пишет `kid_data.bin` (те же три + таблицы подряд, фиксированные смещения). +2. Грузим её в отдельную EMM-страницу тем же способом, что уровень + (`pop_level_load` — готовый образец), хэндл держим в `pop_kid`. +3. **Порт `load_frame` (seg006) и глобала `cur_frame`** — в оригинале ровно + так и сделано: раз за тик кадр копируется в структуру, а весь остальной код + читает `cur_frame`, а не таблицу. У нас `kid_cur_dx()/kid_cur_flags()` + станут чтением из `cur_frame` (5 байт в `_DATA`). +4. `play_seq` оборачивается в один `gfx_w0_map(kid_data_page)` … `unmap` на + вызов (в тике, не в отрисовке — конфликта с атласом в W0 нет). + +Выигрыш: **−3 745 Б из W1/W2**, цена — один маппинг страницы за тик и 5 байт +`_DATA`. Дополнительный бонус: `load_frame`/`cur_frame` — шаг К СХОДСТВУ с +оригиналом, а не отход от него. + +Риск: сломать `play_seq` (сердце анимации). Проверка: прогон по комнатам с +эталонными позами (вис, подтягивание, прыжки, подъём меча). + +### Шаг 2. Модуль `pop_geom.c` — дедуп + bank-safe фундамент + +Сейчас продублировано между модулями: + +| что | где | сколько | +|---|---|---| +| `y_to_row`/`y_to_row_mod4` | pop_bg + pop_map | 2 копии | +| `char_dx_forward` | pop_kid + pop_map | 2 копии | +| `x_bump[20]` | pop_kid (uint8) + pop_map (int16) | 20 + 40 Б, РАЗНЫЕ типы | +| `y_land[5]` | pop_kid + pop_map | 10 + 10 Б | +| `tile_is_floor` | pop_map (+ проверка кодов в roomtest) | 2 места | +| 32-битный LCG `prandom` | pop_bg (`prandom`) + pop_trob (`trob_prandom`) | 2 копии по ~60 Б + 2 сида | + +Собрать в один W1/W2-модуль `pop_geom.c`: таблицы `x_bump/y_land/dir_front/ +dir_behind`, `y_to_row`, `char_dx_forward`, `get_tile_div_mod(_m7)`, +`tile_is_floor`, `prandom`. Выигрыш прямой — сотни байт (оценка 250–400 Б), +но главное — **это и есть тот «чистый» API, который потом сможет звать банк** +(R2): вся геометрия оказывается в W1/W2 по определению. + +Осторожно: `prandom` у pop_bg и pop_trob — РАЗНЫЕ последовательности с разными +сидами (стены vs фазы факелов). Объединять функцию можно, **сиды — нет**: +передавать сид указателем/по индексу, иначе поедет раскладка кладки. + +### Шаг 3. Вынести loose/потолок из `pop_map` в W3 + +`pop_map` — единственный модуль W1/W2, который зовёт графику, и делает это +ровно в одном логическом блоке: `pop_loose_tick` + `check_press` + `do_knock` + +`fell_on_your_head` + `check_loose_fall_on_kid` + плита-потолок (~1.4–2 КБ). + +Вынести их в `pop_loose.c`, собираемый `--w3` рядом с `pop_bg`/`pop_trob`. +Тогда: + +- `pop_map` = чистая логика (bank-safe, R2 соблюдён); +- W1/W2 худеет ещё на ~1.5–2 КБ; +- W3 растёт на столько же — а место там появится после шага 4. + +### Шаг 4. Перебалансировка резидента W3 + +После шага 3 в W3 будет тесно (14.4 + 2 ≈ 16.4 КБ > 16 КБ). Разгружаем: + +1. **`wall_pattern` (944 Б) + `mob_render`/`mob_tick_one` (1381 Б)** — кандидаты + на переезд в W1/W2: их зовёт только `pop_bg`/`pop_loose`, но сами они уже + пользуются только libbgi (R4), значит из W1/W2 работают и остаются + достижимыми из банка. +2. `tile_table` и мелкие const-таблицы pop_bg (371 + ~300 Б) можно унести в + EMM-страницу **уровня** (там ~13.8 КБ свободно) — но только если чтение + происходит под уже замапленной страницей. Сейчас `draw_tile` читает + `tile_table` ВНЕ W0-контекста → потребуется явный маппинг на тайл. **Не + делать раньше замера**: 30 тайлов на входе в комнату × map/unmap — терпимо, + а вот в покадровых редроях (пики/loose/кнопка) — уже горячий путь. +3. Если и этого мало — `enter_room` (~2.1 КБ, зовётся только при смене комнаты) + переносится в резидент W3 или в БАНК (он вызывается из главного цикла = + резидента, значит банк допустим по R3). + +### Шаг 5. Данные + +1. **`room_modif[24][30]` = 720 Б** (pop_trob, `_DATA`). Нужен произвольный + доступ каждый кадр (анимации, ворота) — в EMM не годится. Но 24 комнаты × + 30 байт хранятся ЦЕЛИКОМ, хотя одновременно живут modif'ы только текущей + комнаты и соседей по швам. Вариант: хранить полный массив в EMM-странице + уровня, а в `_DATA` держать кэш на 2–3 комнаты (свою + левого/правого + соседа) с записью обратно при смене комнаты. Выигрыш ~600 Б, цена — + аккуратность на швах (кнопка в одной комнате открывает ворота в другой). + **Делать последним** — это самая «тонкая» правка по семантике. +2. **`dl1[256]`+`dl2[256]` = 512 Б** (pop_level, `_DATA` — копии LINKLOC/ + LINKMAP уровня) — читаются при нажатии кнопки + и при отрисовке нажатой кнопки. Кандидат на чтение прямо из страницы + уровня (она и так маппится) — но проверить, что `pop_doorlink2` не зовётся + из отрисовки в тот момент, когда в W0 атлас. Выигрыш ~500 Б. +3. **`_kbdraw_down[512]` 512 Б** (libc): проверено — это **байт на скан-код** + (`libc/kbd/_kbdraw_state.c`), хотя комментарий называет его битовой картой. + Упаковка в биты даёт −448 Б, но добавляет сдвиг/маску в ISR-трамплин и в + `kbd_raw_down`. Трогать осторожно: raw-клавиатура уже дважды была + источником залипаний (memory `kbd_raw_fifo_drain`, + `kbd_overrun_wipe_modifiers`) — правку сопровождать прогоном docs/kbd-games. +4. **`__irq_vec_buf` 513 Б** (libc IM2): таблица векторов обязана быть + выровнена и полна — не трогать. + +### Шаг 6. Контракт банка стражей (проектируется ДО написания кода) + +Когда дойдём до стражей: + +- `guards.c` собирается `--bank 1=guards.c`, режим `huge` (или `big` с + `BANKED=W1`, если W3 окажется тесен для трамплинов). +- **Банк зовёт только:** `pop_map` (чистая логика после шага 3), `pop_kid` + (`play_seq`, `kid_set_seq`, `cur_frame`), `pop_geom`, libc/libbgi. +- **Банк НЕ зовёт:** `pop_bg`, `pop_trob`, `pop_loose` (резидент W3) — ни + прямо, ни через промежуточные функции. Нужна отрисовка — выставляет флаг, + рисует резидент (идиома `pop_item_taken`). +- Первым делом — **пробник** (`tests/` или `--bank` на пустышке): банк зовёт + `pop_map`-функцию, та зовёт libbgi-примитив; убедиться в MAME, что скобка + W3 корректно возвращает банковую страницу (R4) — это проверка модели, а не + веры в неё. + +### Шаг 7. Мелкий дедуп в `pop_bg` (после того, как появится тест страниц) + +- Пять функций-редроев (`pop_spike_redraw`, `pop_loose_shake_draw`, + `pop_floor_bake`, `pop_button_redraw`, `pop_leveldoor_redraw`) отличаются + только прямоугольником heal, банком и набором тайлов — свести к одному + параметризованному хелперу (оценка −150…250 Б). +- `env_b/wall_b/fore_b/pot_b` — четыре одинаковых обёртки над `blit_b` + (оставить: экономия единицы байт, читаемость дороже). +- `overlay_mid_tile` / `fore_only_tile` / `climb_overlay_tile` / `draw_tile` + — общая структура «взять code/lcode, посчитать x/dmy/dby, разобрать слои». + Тут экономия потенциально сотни байт, но это **та самая зона риска из §3** — + только с пиксельным диффом до/после и по одному слою за раз. + +--- + +## 6. Ожидаемый итог + +| Шаг | W1/W2 | W3 | Риск | +|---|---|---|---| +| 1. kid_data → EMM + load_frame | **−3 745** | — | средний (сердце анимации) | +| 2. pop_geom (дедуп) | −250…400 | — | низкий | +| 3. loose → W3 | −1 500…2 000 | +1 500…2 000 | низкий (перенос как есть) | +| 4. разгрузка W3 (wall_pattern, mob) | +2 300 | −2 300 | низкий | +| 5. данные (room_modif, LINKLOC, kbd) | −1 000…1 600 | — | средний/высокий | +| 7. дедуп редроев pop_bg | — | −150…250 | средний | + +Суммарно: **W1/W2 освобождается ~4.5–6 КБ**, W3 остаётся примерно в нынешнем +объёме, но становится «правильно заполненным» — в нём только то, что банк +никогда не позовёт. Плюс открывается путь к банкам: стражи и боёвка получают +до 16 КБ на банк, не трогая резидент. + +--- + +## 7. Как мерить и проверять (обязательно к каждому шагу) + +1. **До/после по `.rel`** — точные размеры на модуль: + `for f in .sprinter-cc-roomtest/*.rel; do grep '^A ' $f; done` + (области `_CODE`/`_W3CODE`/`_DATA`). Итоги окон печатает сам `sprinter-cc`. +2. **Функции** — из `.lst` (метки `_name:` и адреса), скрипт в истории этой + сессии; полезно ловить «функция распухла после рефактора». +3. **MAME**: любой перенос кода между окнами/страницами — это класс «молча + ломается» (`sprinter_memory_modes`). Минимум: комната 1 (loose), 12 + (вис/подтягивание), 15 (меч), 9 (дверь уровня), 6 (кнопка/ворота). +4. **Пиксельный дифф** для правок отрисовки: заморозить кадр (`1`), сравнить + обе страницы дабл-буфера через `vram` (см. memory `mame_mcp_bridge`) и/или + сверить с эталонным рендером `render_room.py`. +5. **Скорость** — после шагов 1 и 4 замерить кадр маркерами в порт 0xFE + (приём из v1 §8), чтобы маппинг страницы за тик не съел бюджет. + +## 8. Ссылки + +- `bin/sprinter-cc` — справка по `--w3`, `--bank`, `--memory`, `--memory-manual`. +- `runtime/crt0_banked.s`, `runtime/bank.s` — трамплины и захват резидентной + страницы W3. +- `libbgi/common/_bgi_begin.c` / `_bgi_end.c` — механика скобки W3 (R4). +- memory: `w3_resident_code`, `pop_banking_architecture`, `sdcc_banking`, + `bank_local_data_pattern`, `sprinter_memory_modes`, `memory_modes_implemented`, + `sprinter_emm_budget`, `mame_mcp_bridge`, `avoid_32bit_arith_z80`, + `libc_one_function_per_module`. +- `applications/PoP/docs/size_optimization_plan.md` — v1 (замер 2026-07-21, + раздел §8 про скорость отрисовки актуален и не дублируется здесь).