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>
This commit is contained in:
2026-07-30 09:31:58 +03:00
parent 0280b05933
commit 1e6c377edc
11 changed files with 199 additions and 139 deletions
+29 -27
View File
@@ -14,6 +14,7 @@
#include <fcntl.h>
#include <unistd.h>
#include "pop_bg.h"
#include "pop_state.h"
#include "pop_geom.h"
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев (см. ниже) */
@@ -32,7 +33,7 @@ static const char *const ENV_ATL[5] = {
"BG\\pop_env3.atl", "BG\\pop_env4.atl"
};
int pop_bg_load(void)
int pop_bg_load(void) __banked
{
int i;
for (i = 0; i < 5; i++)
@@ -43,7 +44,7 @@ int pop_bg_load(void)
return 0;
}
void pop_bg_free(void)
void pop_bg_free(void) __banked
{
int i;
for (i = 0; i < 5; i++)
@@ -76,7 +77,7 @@ static int g_clip_top;
static int fclip_x0, fclip_y0, fclip_x1, fclip_y1;
static uint8_t fclip_on;
void pop_fore_set_clip(int x, int y, int w, int h)
void pop_fore_set_clip(int x, int y, int w, int h) __banked
{
fclip_x0 = x; fclip_y0 = y;
fclip_x1 = x + w; fclip_y1 = y + h;
@@ -169,7 +170,7 @@ static void ov_mark(int x, int y, int w, int h)
ov_pending = 3; /* обе страницы */
}
void pop_fore_heal(void)
void pop_fore_heal(void) __banked
{
uint8_t pg = gfx_get_draw_page() & 1;
if (!(ov_pending & (1 << pg))) return;
@@ -293,12 +294,12 @@ static uint8_t g_room;
* floorright, когда тайл нижнего ряда стал видимым (empty после падения
* loose): «стена вниз» = топрайт тайла room_B(0, col-1). 0 — нет комнаты
* снизу (кромка уровня = стена). */
void pop_room_set_below(const uint8_t *below_row0_fg) { g_below = below_row0_fg; }
void pop_room_set_below(const uint8_t *below_row0_fg) __banked { g_below = below_row0_fg; }
/* Задать ряд 2 (fg-коды + модификаторы) комнаты СВЕРХУ — для полосы кладки
* у потолка (seg008 draw_room: доп. верхний ряд из room_A, draw_tile_aboveroom
* при draw_main_y=-1). 0 — нет комнаты сверху (кромка уровня → пол). */
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod)
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod) __banked
{ g_above = above_row2_fg; g_abovemod = above_row2_mod; }
static uint8_t tile_code(int row, int col)
@@ -746,7 +747,7 @@ static void draw_tile(int row, int col)
* Порт SDLPoP redraw_tile_height: каждый кадр анимации перерисовать
* ПОЛНОСТЬЮ и loose-тайл, и ПРАВЫЙ СОСЕД (грани loose анимируются по modif
* внутри draw_tile). */
void pop_loose_shake_draw(int row, int col)
void pop_loose_shake_draw(int row, int col) __banked
{
int x = COL_XH[col] * 8;
/* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх —
@@ -768,7 +769,7 @@ void pop_loose_shake_draw(int row, int col)
* читает lmod = g_bg[row*10+col] = живой modif пики). g_bg должен указывать
* на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile.
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
void pop_spike_redraw(int row, int col)
void pop_spike_redraw(int row, int col) __banked
{
int x = COL_XH[col] * 8;
/* Порт redraw_21h -> redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ
@@ -795,7 +796,7 @@ void pop_spike_redraw(int row, int col)
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
void pop_room_redraw_seam_left(void)
void pop_room_redraw_seam_left(void) __banked
{
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
@@ -817,7 +818,7 @@ void pop_room_redraw_seam_left(void)
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
#define CEIL_BAND_H 8 /* видимая высота полосы (room-space y 0..7) */
void pop_ceil_shake_draw(int col)
void pop_ceil_shake_draw(int col) __banked
{
int x = COL_XH[col] * 8;
heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */
@@ -829,7 +830,7 @@ void pop_ceil_shake_draw(int col)
gfx_set_bank(GFX_BANK_NORMAL);
}
void pop_ceil_bake_empty(int col)
void pop_ceil_bake_empty(int col) __banked
{
int x = COL_XH[col] * 8;
gfx_set_bank(GFX_BANK_NORMAL);
@@ -852,7 +853,7 @@ void pop_ceil_bake_empty(int col)
* draw_main_y 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька
* potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y
* (pot_size<<2) 14 (seg008:668). */
void pop_torch_draw(int row, int col, uint8_t modif)
void pop_torch_draw(int row, int col, uint8_t modif) __banked
{
/* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = COL_XH[col+1] + 1
* (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env
@@ -869,7 +870,7 @@ void pop_torch_draw(int row, int col, uint8_t modif)
gfx_set_bank(GFX_BANK_NORMAL);
}
void pop_potion_draw(int row, int col, uint8_t modif)
void pop_potion_draw(int row, int col, uint8_t modif) __banked
{
uint8_t type = potion_type(modif);
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
@@ -887,7 +888,7 @@ void pop_potion_draw(int row, int col, uint8_t modif)
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
void pop_floor_bake(int row, int col)
void pop_floor_bake(int row, int col) __banked
{
int x = COL_XH[col] * 8;
int yb = POP_YOFF + 63 * row;
@@ -905,13 +906,13 @@ void pop_floor_bake(int row, int col)
gfx_set_bank(GFX_BANK_NORMAL);
}
void pop_button_redraw(int row, int col) { pop_floor_bake(row, col); }
void pop_button_redraw(int row, int col) __banked { pop_floor_bake(row, col); }
/* Перерисовать ПРАВУЮ половину двери уровня (в её ячейке рисуется створка,
* см. draw_leveldoor) на back-странице С ЗАПЕЧКОЙ в ОЗУ-копию: створка едет,
* а heal обязан возвращать её ТЕКУЩЕЕ положение, а не запечённое закрытое.
* Куски двери непрозрачные — wipe не нужен. */
void pop_leveldoor_redraw(int row, int col)
void pop_leveldoor_redraw(int row, int col) __banked
{
gfx_set_bank(GFX_BANK_TRANSPARENT);
draw_tile(row, col);
@@ -924,7 +925,7 @@ void pop_leveldoor_redraw(int row, int col)
* рисовал loose-правый-край env_b(42); теперь пусто → чистая грань).
* g_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые
* пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */
void pop_loose_bake_empty(int row, int col)
void pop_loose_bake_empty(int row, int col) __banked
{
int x = COL_XH[col] * 8;
int xr = COL_XH[col + 1] * 8;
@@ -987,7 +988,8 @@ static mob_t mobs[MOB_MAX];
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
* который он лёг → приложение/pop_map ставит debris + do_knock. */
uint8_t pop_loose_landed;
/* pop_loose_landed переехал в pop_state.c: его читает pop_map (W1/W2), а
* писучие данные банкового модуля недоступны снаружи (см. pop_state.h). */
/* y_loose_land / y_something (seg007): [row+1], row = 1..3. Ряд 1 —
* плита-потолок (ряд 2 комнаты сверху): в оригинале это отрицательный
@@ -996,7 +998,7 @@ static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254};
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
#define MOB_Y_NONE 0x7FFF
void pop_loose_mob_spawn(int row, int col)
void pop_loose_mob_spawn(int row, int col) __banked
{
uint8_t i;
mob_t *m = 0;
@@ -1033,7 +1035,7 @@ static int8_t obj_col, obj_row;
static void mob_render(mob_t *m, uint8_t pg);
void pop_loose_mob_reset(void)
void pop_loose_mob_reset(void) __banked
{
uint8_t i;
for (i = 0; i < MOB_MAX; i++) {
@@ -1098,7 +1100,7 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
mob_render(m, pg);
}
void pop_loose_mob_tick(void)
void pop_loose_mob_tick(void) __banked
{
uint8_t pg = gfx_get_draw_page() & 1, i;
for (i = 0; i < MOB_MAX; i++)
@@ -1148,7 +1150,7 @@ static void mob_render(mob_t *m, uint8_t pg)
/* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать
* после kid_draw, до fore-слоя. */
void pop_loose_mob_draw_over(void)
void pop_loose_mob_draw_over(void) __banked
{
uint8_t pg = gfx_get_draw_page() & 1, i;
for (i = 0; i < MOB_MAX; i++)
@@ -1157,7 +1159,7 @@ void pop_loose_mob_draw_over(void)
/* Позиция падающего куска для check_loose_fall_on_kid (pop_map): колонка и
* y (низ куска). 0 — куска нет. */
uint8_t pop_loose_mob_pos(int *col, int *y)
uint8_t pop_loose_mob_pos(int *col, int *y) __banked
{
uint8_t i;
for (i = 0; i < MOB_MAX; i++)
@@ -1172,7 +1174,7 @@ uint8_t pop_loose_mob_pos(int *col, int *y)
* кадрах падения — гейт по флагу). */
static uint8_t border_dirty;
void pop_clip_sprite(int x, int w, int sy_top, int h)
void pop_clip_sprite(int x, int w, int sy_top, int h) __banked
{
int sy_bot = sy_top + h;
(void)x; (void)w;
@@ -1184,7 +1186,7 @@ void pop_clip_sprite(int x, int w, int sy_top, int h)
* вышел за поле в последние 2 кадра) — иначе ноль стоимости. Возвращает
* чёрный из ОЗУ-копии (вне поля она чёрная) в верхний борт и статус-полосу
* + нижний борт. Звать КАЖДЫЙ кадр ПОСЛЕ всей отрисовки спрайтов. */
void pop_room_clip_borders(void)
void pop_room_clip_borders(void) __banked
{
if (!border_dirty) return;
border_dirty--;
@@ -1194,7 +1196,7 @@ void pop_room_clip_borders(void)
void pop_room_draw(uint8_t room_num,
const uint8_t *fg, const uint8_t *bg,
const uint8_t *lcol_fg, const uint8_t *lcol_bg)
const uint8_t *lcol_fg, const uint8_t *lcol_bg) __banked
{
int row, col;
g_room = room_num;
@@ -1502,7 +1504,7 @@ static void char_footprint(int obj_x, int obj_y, uint16_t w, uint16_t h,
}
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir, uint8_t frame, uint8_t action)
int8_t dir, uint8_t frame, uint8_t action) __banked
{
int8_t cL, cR, cLraw, rT, rB, rTraw, r, c;
fclip_on = 1; /* окно = прямоугольник спрайта */