From 1e6c377edc318e60c302dfab469ec58b93b2f9e8 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Thu, 30 Jul 2026 09:31:58 +0300 Subject: [PATCH] =?UTF-8?q?PoP=20roomtest:=20pop=5Fbg=20=D0=B8=20pop=5Fmap?= =?UTF-8?q?=20=D0=B2=20=D0=B1=D0=B0=D0=BD=D0=BA=D0=B8;=20--dataseg=20BANKn?= =?UTF-8?q?=20=D1=81=D1=82=D0=B0=D0=BB=20=D0=BE=D0=BF=D1=86=D0=B8=D0=B5?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Резидента --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 --- applications/PoP/roomtest/Makefile | 28 +++++++------ applications/PoP/roomtest/pop_bg.c | 56 +++++++++++++------------- applications/PoP/roomtest/pop_bg.h | 58 +++++++++++++-------------- applications/PoP/roomtest/pop_gdraw.c | 8 ++-- applications/PoP/roomtest/pop_guard.h | 8 ++-- applications/PoP/roomtest/pop_map.c | 43 ++++++++++---------- applications/PoP/roomtest/pop_map.h | 42 +++++++++---------- applications/PoP/roomtest/pop_state.c | 15 +++++++ applications/PoP/roomtest/pop_state.h | 17 ++++++++ applications/PoP/roomtest/roomtest.c | 45 ++++++++++++--------- bin/sprinter-cc | 18 ++++++++- 11 files changed, 199 insertions(+), 139 deletions(-) create mode 100644 applications/PoP/roomtest/pop_state.c create mode 100644 applications/PoP/roomtest/pop_state.h diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile index 924860a..e55d788 100644 --- a/applications/PoP/roomtest/Makefile +++ b/applications/PoP/roomtest/Makefile @@ -6,18 +6,22 @@ EXAMPLE := roomtest # small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе # CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge). MEMORY ?= huge # small-раскладка + банки кода в W3 (стражи) -# --w3 берёт ОДИН файл на флаг (флагов можно несколько). В W3-резиденте -# держим ТОЛЬКО отрисовку, которую зовёт лишь главный цикл: pop_bg (тайлы) -# и pop_gdraw (спрайт стража). Всё остальное живёт в W1/W2, чтобы -# оставаться достижимым из __banked-модулей (guards.c): из банка -# резидентная страница W3 не видна — ни напрямую, ни транзитивно. -# См. docs/layout_plan_v2.md §2 (R2), §4. -EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c --w3 pop_gdraw.c --bank 1=guards.c $(PROF_FLAGS) -# make PROF=1 — профилирование полосами бордюра (см. PROF() в roomtest.c): -# каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте +# Резидента --w3 БОЛЬШЕ НЕТ: отрисовка уехала в БАНК 2 (pop_bg + pop_gdraw +# в одной странице — вызовы между ними прямые, если функция не помечена +# __banked). Так снято главное ограничение резидента: из банка его было +# не достать ни прямо, ни транзитивно, а банк→банк работает (трамплин +# сохраняет страницу окна на СТЕКЕ, проверено вложенной цепочкой +# банк1→банк2→банк1 в MAME 2026-07-30). Теперь в банки можно уводить и +# логику, которая рисует. См. docs/layout_plan_v2.md §2, memory +# sdcc_banked_call_rules. +EXTRA_FLAGS ?= --gfx 256 --bank 2=pop_bg.c --bank 2=pop_gdraw.c --bank 3=pop_map.c --bank 1=guards.c $(PROF_FLAGS) +# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по +# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает. +# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте # MAME = время фазы в растровых строках. -PROF_FLAGS := $(if $(PROF),-DPROF_BORDER) -EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c pop_map.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c +PROF ?= 1 +PROF_FLAGS := -DPROF_BORDER=$(PROF) +EXTRA_SRCS := pop_state.c pop_kid.c pop_ctrl.c pop_level.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c BG_DIR := $(CURDIR)/../poc/res/bg KID_DIR := $(CURDIR)/../poc/res/kid @@ -67,4 +71,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py cd $(TC) && python3 pop_extract_kid_data.py -$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_gdraw.c pop_cheat.h pop_bg.c pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) +$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_gdraw.c pop_state.c pop_state.h pop_cheat.h pop_bg.c pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) diff --git a/applications/PoP/roomtest/pop_bg.c b/applications/PoP/roomtest/pop_bg.c index 85065dc..20279b7 100644 --- a/applications/PoP/roomtest/pop_bg.c +++ b/applications/PoP/roomtest/pop_bg.c @@ -14,6 +14,7 @@ #include #include #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; /* окно = прямоугольник спрайта */ diff --git a/applications/PoP/roomtest/pop_bg.h b/applications/PoP/roomtest/pop_bg.h index 4809af0..9aee114 100644 --- a/applications/PoP/roomtest/pop_bg.h +++ b/applications/PoP/roomtest/pop_bg.h @@ -28,10 +28,10 @@ /* Загрузить 7 атласов фона. Звать ДО initgraph (как poc.c — atlas_load * трогает W3; проверенный порядок). Палитру pop_bg.pal грузит ПРИЛОЖЕНИЕ * после initgraph (gfx_pal_fload). 0 — OK, -1 — ошибка. */ -int pop_bg_load(void); +int pop_bg_load(void) __banked; /* Освободить EMM-страницы атласов. */ -void pop_bg_free(void); +void pop_bg_free(void) __banked; /* Нарисовать комнату: fg[30]/bg[30] — foretable-коды/backtable-модификаторы * (по строкам, row*10+col). lcol_fg/lcol_bg — 3 тайла правого столбца @@ -39,24 +39,24 @@ void pop_bg_free(void); * тогда левая кромка = стена. room_num — номер комнаты (зерно стен). */ 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; /* Задать ряд 0 комнаты СНИЗУ (fg-коды, 10 шт.) — для draw_tile_topright, * когда тайл нижнего ряда стал empty (после падения loose): «стена вниз» * рисуется по коду room_B(0, col-1). 0 — нет комнаты снизу. */ -void pop_room_set_below(const uint8_t *below_row0_fg); +void pop_room_set_below(const uint8_t *below_row0_fg) __banked; /* Задать ряд 2 (fg-коды + модификаторы, 10 шт.) комнаты СВЕРХУ — для полосы * кладки у потолка (seg008 draw_room доп. верхний ряд из room_A). 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; /* Перерисовать передний слой (fore) тайлов футпринта Kid ПОВЕРХ него — * звать ПОСЛЕ kid_draw. obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ * спрайта, w/h — размер кадра, dir — направление Kid (<0 влево). Метрики * берутся из pop_kid (kid_fp_*). См. redraw_at_char/set_char_collision. */ 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; /* Окно fore-слоя: прямоугольник СПРАЙТА (экранные координаты, y уже с * POP_YOFF), поверх которого fore-проход возвращает куски тайлов. Ставит @@ -64,7 +64,7 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, * итоговый габарит (у Kid — с учётом клинка и обрезки clip_char). Вне * fore-проходов окно игнорируется: фон рисуется целиком. Мотивация и * замер — в pop_bg.c у 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; /* То же для НЕ-Kid персонажа (страж): только слой fore + полоса потолка, * без оверлеев поз виса/полёта/подъёма. obj_x — ЛОГИЧЕСКАЯ координата @@ -76,74 +76,74 @@ void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h, * (draw_other_overlay/floor_overlay/полоса потолка): он выходит за * прямоугольник Kid, поэтому kid_heal его не убирает — на второй странице * дабл-буфера остаётся след и мерцает. Звать рядом с kid_heal. */ -void pop_fore_heal(void); +void pop_fore_heal(void) __banked; /* Loose floors — динамическая отрисовка (состояние pop_loose_modif в * pop_map). Звать на ТЕКУЩЕЙ (back) странице из pop_loose_tick. * shake_draw — дрожащий кадр (heal+SPRITE); bake_empty — запечь пустоту * после падения (NORMAL, в ОЗУ-копию). */ -void pop_loose_shake_draw(int row, int col); -void pop_loose_bake_empty(int row, int col); +void pop_loose_shake_draw(int row, int col) __banked; +void pop_loose_bake_empty(int row, int col) __banked; /* Плита-ПОТОЛОК (loose ряда 2 комнаты СВЕРХУ, наш ряд −1): дрожащий кадр в * полосе у потолка / «запечь» пустоту (колодец наверх) после провала. * Состояние — pop_ceil_modif[10] в pop_map. */ -void pop_ceil_shake_draw(int col); -void pop_ceil_bake_empty(int col); +void pop_ceil_shake_draw(int col) __banked; +void pop_ceil_bake_empty(int col) __banked; -/* Кусок приземлился (loose_land): 0 = нет, иначе tilepos+1 тайла, на который - * он лёг. Ставит pop_loose_mob_tick, разбирает pop_loose_tick (debris+knock). */ -extern uint8_t pop_loose_landed; +/* Кусок приземлился (loose_land) — переехал в pop_state.h: переменную + * читают по обе стороны банковой границы. */ +#include "pop_state.h" /* Пики (spike) — перерисовка тайла (row,col) на back-странице по живому * modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */ -void pop_spike_redraw(int row, int col); +void pop_spike_redraw(int row, int col) __banked; /* Анимация факела/зелья (динамика каждый кадр; фон запечён без них): * modif — живой room_modif тайла. Для факела col — колонка САМОГО факела * (пламя рисуется в ячейке правого соседа). */ -void pop_torch_draw(int row, int col, uint8_t modif); -void pop_potion_draw(int row, int col, uint8_t modif); +void pop_torch_draw(int row, int col, uint8_t modif) __banked; +void pop_potion_draw(int row, int col, uint8_t modif) __banked; /* Ворота на ЛЕВОМ шве (col0) — перерисовать по живому modif соседа слева. * g_lbg (lcol_bg) должен быть обновлён вызывающим. Звать из основного цикла * на back-странице, когда сосед слева анимируется. См. pop_bg.c. */ -void pop_room_redraw_seam_left(void); +void pop_room_redraw_seam_left(void) __banked; /* Перерисовать ПОЛ тайла (row,col) + правую грань в соседе на back-странице * (wipe по площади пола, БЕЗ стены ряда ниже). Для смены тайла на месте: * кнопка нажата/отжата, floor → debris от упавшей плиты. */ -void pop_floor_bake(int row, int col); -void pop_button_redraw(int row, int col); /* = pop_floor_bake (кнопка) */ +void pop_floor_bake(int row, int col) __banked; +void pop_button_redraw(int row, int col) __banked; /* = pop_floor_bake (кнопка) */ /* Дверь уровня: перерисовать её ПРАВУЮ половину (row, col = правая) с * запечкой — створка анимируется (pop_trob animate_leveldoor). */ -void pop_leveldoor_redraw(int row, int col); +void pop_leveldoor_redraw(int row, int col) __banked; /* Падающий кусок loose (mob, seg007 move_loose): спавн при падении тайла * (row,col) и покадровое продвижение+отрисовка на back-странице. tick * зовётся каждый кадр (внутри pop_loose_tick); клип низа делает clip. */ -void pop_loose_mob_spawn(int row, int col); -void pop_loose_mob_tick(void); -void pop_loose_mob_reset(void); /* остановить кусок (смена комнаты) */ +void pop_loose_mob_spawn(int row, int col) __banked; +void pop_loose_mob_tick(void) __banked; +void pop_loose_mob_reset(void) __banked; /* остановить кусок (смена комнаты) */ /* Кусок НИЖЕ Kid по obj_y → рисуется ПОВЕРХ него (порт sort_curr_objs, * seg008:1561). Звать после kid_draw, перед fore-слоем. */ -void pop_loose_mob_draw_over(void); +void pop_loose_mob_draw_over(void) __banked; /* Позиция куска (колонка, y низа) для check_loose_fall_on_kid. 0 — нет. */ -uint8_t pop_loose_mob_pos(int *col, int *y); +uint8_t pop_loose_mob_pos(int *col, int *y) __banked; /* Детектор выхода спрайта за поле: если спрайт [x,w] с ЭКРАННЫМ верхом * sy_top и высотой h заходит ниже поля (статус-полоса/борт) или выше * верхнего борта — взводит внутренний флаг «борта грязные» на 2 кадра. * Звать СРАЗУ после блита падающего объекта (Kid/кусок). */ -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; /* Почистить борта (верхний + статус-полоса/нижний) — но ТОЛЬКО в кадрах, * где кто-то реально вышел за поле (гейт по флагу от pop_clip_sprite): * даром в покое, O(1) при падении (не зависит от числа спрайтов). Звать * КАЖДЫЙ кадр ПОСЛЕ kid_draw+fore на back-странице. */ -void pop_room_clip_borders(void); +void pop_room_clip_borders(void) __banked; #endif diff --git a/applications/PoP/roomtest/pop_gdraw.c b/applications/PoP/roomtest/pop_gdraw.c index 7853256..4342f25 100644 --- a/applications/PoP/roomtest/pop_gdraw.c +++ b/applications/PoP/roomtest/pop_gdraw.c @@ -29,7 +29,7 @@ static int gx_l[2], gy_l[2]; static uint16_t gw_l[2], gh_l[2]; static uint8_t gvalid[2]; -int pop_guard_load(void) +int pop_guard_load(void) __banked { static const char *const names[GUARD_PAGES] = { "GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl", @@ -42,7 +42,7 @@ int pop_guard_load(void) return 0; } -void pop_guard_free(void) +void pop_guard_free(void) __banked { uint8_t i; if (!g_ok) return; @@ -50,14 +50,14 @@ void pop_guard_free(void) g_ok = 0; } -void pop_guard_heal(void) +void pop_guard_heal(void) __banked { uint8_t p = gfx_get_draw_page() & 1; if (gvalid[p]) gfx_heal(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]); gvalid[p] = 0; } -void pop_guard_draw(void) +void pop_guard_draw(void) __banked { int obj_x, obj_y, top, fwd, fp_x, bx0 = 0, top0 = 0; uint8_t page, idx, p, flip; diff --git a/applications/PoP/roomtest/pop_guard.h b/applications/PoP/roomtest/pop_guard.h index d11e192..6d3e59e 100644 --- a/applications/PoP/roomtest/pop_guard.h +++ b/applications/PoP/roomtest/pop_guard.h @@ -56,8 +56,8 @@ uint8_t pop_guard_present(void); /* Атласы стража (GUARD\\g0..g4.atl) — грузить ДО initgraph, как Kid. * 0 — OK, -1 — ошибка (тогда страж просто не рисуется). */ -int pop_guard_load(void); -void pop_guard_free(void); +int pop_guard_load(void) __banked; +void pop_guard_free(void) __banked; /* Вход в комнату: поднять стража из данных уровня (порт enter_guard, * seg002:0112) либо убрать его, если в комнате стража нет. */ @@ -66,8 +66,8 @@ void pop_guard_enter(uint8_t room); /* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE, * как kid_draw) и стереть прошлый (heal). Реализация — pop_gdraw.c * (резидент W3), поэтому звать ТОЛЬКО из главного цикла, не из банка. */ -void pop_guard_heal(void); -void pop_guard_draw(void); +void pop_guard_heal(void) __banked; +void pop_guard_draw(void) __banked; /* Убрать стража со сцены (смена комнаты / инициализация). */ void pop_guard_reset(void); diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index 005de26..73c13e4 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -28,6 +28,7 @@ * есть то, что понадобится стражам, чиста от графики. Разделение mob на * логику и отрисовку — задел следующей фазы. */ #include "pop_bg.h" +#include "pop_state.h" #include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */ /* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */ @@ -149,13 +150,13 @@ uint8_t pop_ceil_fell; /* 0 = нет; иначе col+1 провал uint8_t pop_debris_at; /* 0 = нет; иначе tilepos+1 нового щебня в ЭТОЙ комнате */ -void pop_map_set_above(uint8_t *above_row2_fg) { g_above = above_row2_fg; } +void pop_map_set_above(uint8_t *above_row2_fg) __banked { g_above = above_row2_fg; } -void pop_map_set(uint8_t *fg) { g_fg = fg; } +void pop_map_set(uint8_t *fg) __banked { g_fg = fg; } -void pop_map_set_room(uint8_t room) { g_room = room; } +void pop_map_set_room(uint8_t room) __banked { g_room = room; } -void pop_kid_hp_reset(void) { hitp_curr = 3; pop_kid_dead = 0; } +void pop_kid_hp_reset(void) __banked { hitp_curr = 3; pop_kid_dead = 0; } /* take_hp (seg006:06BD, только Kid): count>=hitp → смерть (return 1). */ static uint8_t take_hp(uint8_t count) @@ -166,7 +167,7 @@ static uint8_t take_hp(uint8_t count) } void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down, - const uint8_t *lcol_fg, const uint8_t *rcol_fg) + const uint8_t *lcol_fg, const uint8_t *rcol_fg) __banked { g_link_l = left; g_link_r = right; g_link_u = up; g_link_d = down; if (lcol_fg) { uint8_t i; for (i = 0; i < 6; i++) g_lcol[i] = lcol_fg[i]; } @@ -587,7 +588,7 @@ static int edge_classify(int d) return d; } -int pop_edge_distance(void) +int pop_edge_distance(void) __banked { uint8_t t; int d; determine_col(); @@ -629,7 +630,7 @@ int pop_edge_distance(void) return distance_to_edge_weight(); } -uint8_t pop_edge_type(void) { return g_edge_type; } +uint8_t pop_edge_type(void) __banked { return g_edge_type; } /* ---- Прыжок вверх с зацепом (seg005 check_jump_up и производные) --- * * pop_jump_up_seq выбирает seq для ↑ в стойке: чистый прыжок (seq_28 в @@ -682,7 +683,7 @@ static uint8_t jump_up_or_grab(void) return grab_up_with_floor_behind(); } -uint8_t pop_jump_up_seq(void) +uint8_t pop_jump_up_seq(void) __banked { uint8_t through = get_tile_above_char(); get_tile_front_above_char(); /* g_curr_tile = спереди-сверху */ @@ -697,7 +698,7 @@ uint8_t pop_jump_up_seq(void) /* Стена впереди достаточно близко для стопа бега? Детект (без сдвига — * позицию держит check_bumped=in_wall). d<=2: передний край почти у грани. */ -int pop_wall_ahead(void) +int pop_wall_ahead(void) __banked { int d = pop_edge_distance(); return (g_edge_type == EDGE_WALL && d <= 2); @@ -708,7 +709,7 @@ int pop_wall_ahead(void) * тайловые предикаты + выбор seq (+ выравнивание Kid.x). */ /* grab_timer истёк — можно подтягиваться? */ -uint8_t pop_hang_grab_ready(void) { return grab_timer == 0; } +uint8_t pop_hang_grab_ready(void) __banked { return grab_timer == 0; } /* can_climb_up (seg005:09DF): тайл над персонажем — обычный подъём (seq_10) * либо подъём к ЗАКРЫТОЙ калитке/зеркалу/чомперу (seq_73 = «влез и съехал @@ -716,7 +717,7 @@ uint8_t pop_hang_grab_ready(void) { return grab_timer == 0; } * curr_tile2 == tiles_4_gate && direction != right && modif >> 2 < 6 * — под поднятой решёткой (modif >> 2 >= 6) подъём обычный. Без проверки * модификатора Kid срывался и с открытых ворот. */ -uint8_t pop_hang_climb_up_seq(void) +uint8_t pop_hang_climb_up_seq(void) __banked { uint8_t t = get_tile_above_char(); if (((t == TILE_MIRROR || t == TILE_CHOMPER) && Kid.direction == 0) || @@ -728,7 +729,7 @@ uint8_t pop_hang_climb_up_seq(void) /* Shift в висе: 1 = висит у стены/двери (caller играет seq_25); -1 = * сорваться (hang_fall); 0 = сверху пол, остаётся висеть (ничего). */ -int8_t pop_hang_wall(void) +int8_t pop_hang_wall(void) __banked { uint8_t t = get_tile_at_char(); if (Kid.action != ACT_HANGSTRAIGHT && @@ -743,7 +744,7 @@ int8_t pop_hang_wall(void) /* hang_fall (seg005:0A46): сорваться. Нет пола ни сзади, ни под * персонажем — seq_23 (отпустить и падать); иначе прижать к стене (-7) * и seq_11 (встать, конец спуска). Меняет Kid.x, возвращает seq. */ -uint8_t pop_hang_fall_seq(void) +uint8_t pop_hang_fall_seq(void) __banked { uint8_t behind = get_tile_behind_char(); uint8_t at; @@ -760,7 +761,7 @@ uint8_t pop_hang_fall_seq(void) * либо спуск seq_68 через зацеп, либо отступ от передней кромки), 0 = * приседать. Меняет Kid.x. Kid развёрнут от края (край сзади) у кромки * пола + под соседним тайлом есть за что зацепиться → спуск на этаж вниз. */ -uint8_t pop_down_action(void) +uint8_t pop_down_action(void) __banked { if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) { Kid.x = (uint8_t)char_dx_forward(5); /* у передней кромки — отступ */ @@ -805,7 +806,7 @@ static void do_pickup(int8_t obj_type, uint8_t tilepos) /* proc_get_object (seg006:16CB): эффект поднятого предмета. Зовётся из * play_seq по опкоду SEQ_GET_ITEM с аргументом 1 (pop_kid). */ -void pop_proc_get_object(void) +void pop_proc_get_object(void) __banked { if (pickup_obj_type == -1) { pop_have_sword = 1; /* в оригинале have_sword = -1 */ @@ -821,7 +822,7 @@ void pop_proc_get_object(void) /* check_get_item + get_item (seg005:061F/073E). Возврат: 0 — предмета нет * (управление идёт обычной веткой); 1 — присесть (caller зовёт crouch()); * 2 — поднять меч (caller ставит seq_91). */ -uint8_t pop_get_item_action(void) +uint8_t pop_get_item_action(void) __banked { uint8_t t; int8_t icol; @@ -1113,7 +1114,7 @@ static void do_knock(int tile_row) /* check_knock (seg003:0706): по флагу knock (ставит play_seq на seqtbl- * командах KNOCK_UP/DOWN) тряхнуть ряд. KNOCK_DOWN (knock=-1, приземление) → * ряд curr_row; KNOCK_UP (knock=+1) → ряд curr_row-1. */ -void pop_check_knock(void) +void pop_check_knock(void) __banked { if (knock) { do_knock(Kid.curr_row - (knock > 0)); @@ -1124,7 +1125,7 @@ void pop_check_knock(void) /* Сброс состояния loose (при смене комнаты): pop_loose_modif/bake/rest * индексируются позицией тайла и не должны утечь из старой комнаты в новую. * Плюс останавливает падающий кусок (pop_bg). */ -void pop_loose_reset(void) +void pop_loose_reset(void) __banked { int i; for (i = 0; i < 30; i++) { @@ -1183,7 +1184,7 @@ static void check_loose_fall_on_kid(void) /* animate_loose (seg007:0CBA) + перерисовка изменившихся тайлов на ТЕКУЩЕЙ * (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */ -void pop_loose_tick(void) +void pop_loose_tick(void) __banked { int pos; /* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80 @@ -1280,7 +1281,7 @@ void pop_loose_tick(void) * check_spike_below колонки берутся get_tile_div_mod БЕЗ −7. * * Возврат: КОМНАТНЫЙ y верхней границы (0 = клипа нет). */ -int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) +int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked { static const int16_t y_clip[5] = { -60, 3, 66, 129, 192 }; int wh = ((int)w + 1) >> 1; /* char_width_half */ @@ -1481,7 +1482,7 @@ static void check_spike_below(void) } } -void pop_phys_tick(void) +void pop_phys_tick(void) __banked { if (pop_kid_dead) return; /* труп на пиках — без физики (только seq смерти) */ if (grab_timer) grab_timer--; diff --git a/applications/PoP/roomtest/pop_map.h b/applications/PoP/roomtest/pop_map.h index 6cf7e02..ad8daa5 100644 --- a/applications/PoP/roomtest/pop_map.h +++ b/applications/PoP/roomtest/pop_map.h @@ -17,25 +17,25 @@ * col<0/>9 или row<0/>2 трактуются как стена (край уровня). Массив * МУТАБЕЛЬНЫЙ (loose-пол при падении → empty) — передавать ту же копию, * что и в pop_room_draw, чтобы отрисовка видела изменения. */ -void pop_map_set(uint8_t *fg); +void pop_map_set(uint8_t *fg) __banked; /* Связи комнаты (0=нет соседа; иначе номер) + кромки швов для коллизии: * lcol_fg[6] = col9 левого соседа (за col0) в [0..2] и его col8 (колонка −2) * в [3..5]; rcol_fg[6] симметрично (col0 / col1 = колонки 10 и 11). Вторая * колонка нужна, когда Kid стоит В шве и смотрит наружу. Звать в enter_room. */ void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down, - const uint8_t *lcol_fg, const uint8_t *rcol_fg); + const uint8_t *lcol_fg, const uint8_t *rcol_fg) __banked; /* Номер ТЕКУЩЕЙ комнаты (для доступа к room_modif пик/ворот в pop_trob). * Звать в enter_room. */ -void pop_map_set_room(uint8_t room); +void pop_map_set_room(uint8_t room) __banked; /* HP/смерть. pop_kid_dead=1 когда Kid убит (пики); hitp_curr — текущее HP. * pop_kid_hp_reset() ставит старт HP и снимает смерть (звать в kid_init/ * респавн). */ extern uint8_t pop_kid_dead; extern uint8_t hitp_curr; -void pop_kid_hp_reset(void); +void pop_kid_hp_reset(void) __banked; /* Предметы (seg005 check_get_item/get_item, seg006 do_pickup/proc_get_object). * pop_have_sword — «меч у Кида» (порт have_sword). pop_item_taken — 0 или @@ -46,8 +46,8 @@ extern uint8_t pop_item_taken; /* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока * НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */ extern uint8_t pop_flash_time; -uint8_t pop_get_item_action(void); /* 0 нет / 1 присесть / 2 поднять меч */ -void pop_proc_get_object(void); /* эффект — из play_seq (опкод GET_ITEM) */ +uint8_t pop_get_item_action(void) __banked; /* 0 нет / 1 присесть / 2 поднять меч */ +void pop_proc_get_object(void) __banked; /* эффект — из play_seq (опкод GET_ITEM) */ /* Уход из комнаты ВБОК/ВВЕРХ: 0=нет; 1=left,2=right,3=up (= side+1 для * pop_room_link). Координаты Kid уже репроецированы (x∓140 / y+189). @@ -66,8 +66,8 @@ extern uint8_t pop_leave_timer; * на ТЕКУЩЕЙ странице; звать в основном цикле ПОСЛЕ set_draw_page(back), * рядом с kid-отрисовкой. */ extern uint8_t pop_loose_modif[30]; -void pop_loose_tick(void); -void pop_loose_reset(void); /* сброс loose-состояния при смене комнаты */ +void pop_loose_tick(void) __banked; +void pop_loose_reset(void) __banked; /* сброс loose-состояния при смене комнаты */ /* Установлен в tilepos+1 в кадре, когда loose-пол упал (кусок улетел ВНИЗ). * Приложение: определить debris-приземление в комнате снизу. Снять (=0). */ @@ -81,7 +81,7 @@ extern uint8_t pop_loose_fell; * pop_loose_modif), читает pop_bg. Сигналы приложению (для персистентности * между входами в комнату): pop_ceil_fell = col+1 провалившейся плиты, * pop_debris_at = tilepos+1 щебня, оставшегося в ЭТОЙ комнате. Снимать (=0). */ -void pop_map_set_above(uint8_t *above_row2_fg); +void pop_map_set_above(uint8_t *above_row2_fg) __banked; extern uint8_t pop_ceil_modif[10]; extern uint8_t pop_ceil_fell; extern uint8_t pop_debris_at; @@ -92,7 +92,7 @@ extern uint8_t pop_kid_hurt; /* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда * приземления/удара. Зовётся из pop_phys_tick. */ -void pop_check_knock(void); +void pop_check_knock(void) __banked; /* Установлен в 1, когда Kid пересёк НИЖНЮЮ границу комнаты (провал в дыру * или спуск-зацеп с кромки). Координаты Kid УЖЕ репроецированы в кадр @@ -104,14 +104,14 @@ extern uint8_t pop_fell_out; /* Пер-кадровая физика ПОСЛЕ kid_tick(play_seq): fall_accel + fall_speed + * determine_col + check_action (триггер падения / do_fall / приземление). * Порядок как в оригинале (seg000 play_kid_frame). */ -void pop_phys_tick(void); +void pop_phys_tick(void) __banked; /* clip_char (seg006:1749), верхняя граница клипа спрайта персонажа: если тайл * над головой — стена/пол, всё выше линии своей полосы не рисуется (Kid, * ушедший головой под пол при спуске/висе, не просвечивает сквозь щели). * obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ спрайта, w/h — размер. * Возврат: КОМНАТНЫЙ y границы, 0 = клипа нет. Зовёт kid_draw. */ -int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h); +int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked; /* Тип препятствия/края ВПЕРЕДИ по направлению (pop_edge_type после * pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */ @@ -120,26 +120,26 @@ int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h); #define EDGE_FLOOR 2 /* пол (нет препятствия рядом, дистанция = 11) */ /* Расстояние до края/стены впереди + побочно g_edge_type (pop_edge_type). */ -int pop_edge_distance(void); -uint8_t pop_edge_type(void); +int pop_edge_distance(void) __banked; +uint8_t pop_edge_type(void) __banked; /* Стена впереди в пределах стопа? Если да — прижимает Kid.x к корректной * позиции (передний край у стены, симметрично) и возвращает 1. Для * control_running (чистый стоп у стены). */ -int pop_wall_ahead(void); +int pop_wall_ahead(void) __banked; /* Прыжок вверх в стойке (↑): seq чистого прыжка (seq_28/seq_14) ЛИБО * прыжка-с-зацепом за уступ выше (seq_8/24/16) + выравнивание Kid.x. * check_jump_up/grab_up (K4.4). control_up гасит caller. */ -uint8_t pop_jump_up_seq(void); +uint8_t pop_jump_up_seq(void) __banked; /* ---- Вис/зацеп/спуск (K4.2/K4.3) — тайловые решения для pop_ctrl --- * * Управление (control_hanging/can_climb_up/hang_fall/down_pressed) в * pop_ctrl; здесь — предикаты + выбор seq (+ выравнивание Kid.x). */ -uint8_t pop_hang_grab_ready(void); /* grab_timer истёк — можно climb-up */ -uint8_t pop_hang_climb_up_seq(void); /* seq_10 / seq_73 (подтягивание) */ -int8_t pop_hang_wall(void); /* Shift в висе: 1=у стены,-1=fall,0=держ.*/ -uint8_t pop_hang_fall_seq(void); /* seq_23 / seq_11 (сорваться), +Kid.x */ -uint8_t pop_down_action(void); /* 1=обработано(спуск/отступ), 0=присед */ +uint8_t pop_hang_grab_ready(void) __banked; /* grab_timer истёк — можно climb-up */ +uint8_t pop_hang_climb_up_seq(void) __banked; /* seq_10 / seq_73 (подтягивание) */ +int8_t pop_hang_wall(void) __banked; /* Shift в висе: 1=у стены,-1=fall,0=держ.*/ +uint8_t pop_hang_fall_seq(void) __banked; /* seq_23 / seq_11 (сорваться), +Kid.x */ +uint8_t pop_down_action(void) __banked; /* 1=обработано(спуск/отступ), 0=присед */ #endif diff --git a/applications/PoP/roomtest/pop_state.c b/applications/PoP/roomtest/pop_state.c new file mode 100644 index 0000000..3c4793b --- /dev/null +++ b/applications/PoP/roomtest/pop_state.c @@ -0,0 +1,15 @@ +/* + * pop_state.c — данные, которые читают ОБЕ стороны банковой границы. + * + * Зачем отдельный модуль: sprinter-cc собирает банковый модуль с + * --dataseg BANKn, поэтому ВСЯ его писучая память (глобалы, file-static, + * статики внутри функций) линкуется В СТРАНИЦУ БАНКА и снаружи не читается + * — проверено на .map (2026-07-30: глобал банкового модуля лёг по + * 0x0001C000, а не в _DATA). Значит любую переменную, к которой нужен + * доступ и из банка, и снаружи, надо держать в НЕбанковом модуле — здесь. + * + * Модуль обязан оставаться в W1/W2: его читают и главный цикл, и банки. + */ +#include "pop_state.h" + +uint8_t pop_loose_landed; diff --git a/applications/PoP/roomtest/pop_state.h b/applications/PoP/roomtest/pop_state.h new file mode 100644 index 0000000..a468ba3 --- /dev/null +++ b/applications/PoP/roomtest/pop_state.h @@ -0,0 +1,17 @@ +/* + * pop_state.h — состояние, общее для банков и W1/W2. См. pop_state.c. + * + * Правило: если переменную читают/пишут по обе стороны банковой границы — + * её место здесь, а НЕ в банковом модуле (там она уедет в страницу банка). + */ +#ifndef POP_STATE_H +#define POP_STATE_H + +#include + +/* Кусок loose приземлился (loose_land, seg007:11E8): 0 = нет, иначе + * tilepos+1 тайла, на который он лёг. Ставит отрисовка mob (банк), + * разбирает логика loose-полов (pop_map, W1/W2). */ +extern uint8_t pop_loose_landed; + +#endif diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 6ddce4b..bd13862 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -21,28 +21,33 @@ __sfr __at (0xFE) io_border; -/* ПРОФИЛИРОВАНИЕ ПОЛОСАМИ БОРДЮРА (make PROF=1). Цвет бордюра держится, - * пока фаза считает, поэтому высота полосы на скриншоте = время фазы в - * растровых строках (1 строка ≈ 224 такта Z80 при 3.5 МГц). Это - * единственный способ мерить здесь: мост MAME отвечает из фреймового - * колбэка, поэтому выборка PC вырождается (100 % сэмплов в обработчике - * прерывания). Фазы: +/* ПРОФИЛИРОВАНИЕ ПОЛОСАМИ БОРДЮРА (включено по умолчанию; make PROF=0 — + * выключить). Цвет бордюра держится, пока фаза считает, поэтому высота + * полосы на скриншоте = время фазы в растровых строках. Это единственный + * способ смотреть профиль без дебаггера: выборка PC вырождается (мост MAME + * отвечает из фреймового колбэка, все сэмплы падают в обработчик + * прерывания); точные такты — breakpoint'ы с {printf totalcycles; g}. + * + * ЦВЕТА: у бордюра Sprinter бит 0 (красный) ИГНОРИРУЕТСЯ, работают только + * бит 1 (синий) и бит 2 (зелёный) — значит различимых состояний ровно + * ЧЕТЫРЕ, и значения обязаны быть чётными. Отсюда фазы сгруппированы по + * четыре, а не по семь (нечётные номера раньше сливались с соседями и + * полосы не читались): * 0 чёрный — ожидание vsync (запас кадра) - * 1 синий — ввод + heal (стереть прошлые спрайты) - * 2 красный — логика (kid_tick/физика/страж в банке) - * 3 лиловый — слой фона (loose/trobs/redraw_needed/шов) - * 4 зелёный — kid_draw - * 5 циан — guard_draw + fore поверх стража - * 6 жёлтый — fore поверх Kid + чистка бортов - * 7 белый — флип */ -#ifdef PROF_BORDER + * 2 синий — ввод, heal и ЛОГИКА (kid_tick/физика/страж) + * 4 зелёный — слой ФОНА (loose/trobs/redraw_needed/шов) + * 6 циан — СПРАЙТЫ (страж, Kid, fore поверх них) */ +#ifndef PROF_BORDER +#define PROF_BORDER 1 +#endif +#if PROF_BORDER #define PROF(n) io_border = (n) #else #define PROF(n) ((void)0) #endif /* Сколько банков грузит crt0_banked (читает его стартовый код). */ -const uint8_t n_banks = 1; +const uint8_t n_banks = 3; /* Режим читов (pop_cheat.h): на время разработки включаем в main. */ uint8_t pop_cheats; @@ -287,7 +292,7 @@ int main(void) /* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один * переключ). Выключение возвращает показ на страницу 0. */ - PROF(1); /* ввод + heal */ + PROF(2); /* ввод + heal */ uint8_t sp = kbd_raw_down(KBD_SPACE); if (sp && !sp_prev) { @@ -355,7 +360,7 @@ int main(void) kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */ pop_guard_heal(); /* и прошлый кадр стража */ pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */ - PROF(2); /* логика */ + PROF(2); /* логика (тот же синий) */ kid_tick(); /* play_seq: следующий кадр */ pop_phys_tick(); /* K3: падение/приземление/стена */ pop_guard_tick(); /* логика стража — В БАНКЕ (guards.c) */ @@ -381,7 +386,7 @@ int main(void) } } - PROF(3); /* слой фона */ + PROF(4); /* слой фона */ pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */ /* — ДО Kid, чтобы Kid всегда был поверх плиты) */ @@ -466,9 +471,9 @@ int main(void) flash_on = 1; } update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */ - PROF(5); /* страж: спрайт + fore поверх него */ + PROF(6); /* спрайты: страж, Kid, fore поверх */ pop_guard_draw(); /* страж — ДО Kid (порядок как в оригинале) */ - PROF(4); /* kid_draw */ + PROF(6); /* kid_draw (тот же циан) */ kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */ pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */ if (pop_kid_hurt) { /* HP убавилось → «брызги» удара (draw_hurt_splash) */ diff --git a/bin/sprinter-cc b/bin/sprinter-cc index f8ec431..b3b84f7 100755 --- a/bin/sprinter-cc +++ b/bin/sprinter-cc @@ -33,6 +33,9 @@ # --data-loc 0xN override SDCC --data-loc (default: derived from --memory) # -Wl FLAG extra linker flag (repeatable) # --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked +# --bank-data put bank modules' writable data INTO the bank page +# (default: it goes to the shared _DATA in W1/W2, which +# stays mapped and is visible from everywhere) # automatically and adds -Wl-b_BANKN=0x{N}C000 # --w3 FILE.c place FILE.c resident in window 3 (0xC000), called # DIRECTLY (no trampoline). Repeatable. Defaults to @@ -108,6 +111,7 @@ MKEXE_EXTRA=() BANK_SPECS=() # entries like "1=engine.c" W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3) USER_DEFS=() # -DFOO / -DFOO=bar — пробрасываются в sdcc как есть +BANK_LOCAL_DATA=0 # 1 (--bank-data): писучие данные банков — В СТРАНИЦУ банка W3_RELS=() # заполняется при компиляции W3-модулей W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3 USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small) @@ -143,6 +147,7 @@ while [[ $# -gt 0 ]]; do -Wl) LD_EXTRA+=("$2"); shift 2;; -D*) USER_DEFS+=("$1"); shift;; --bank) BANK_SPECS+=("$2"); shift 2;; + --bank-data) BANK_LOCAL_DATA=1; shift;; --mkexe) MKEXE_EXTRA+=("$2"); shift 2;; --max-allocs) MAX_ALLOCS="$2"; shift 2;; --gfx) GFX_MODE="$2"; shift 2;; @@ -423,8 +428,19 @@ if [[ "$CRT0_TYPE" == "banked" ]]; then bank_n="${spec%%=*}" bank_src="${spec#*=}" rel="$WORK/bank${bank_n}_$(basename "$bank_src" .c).rel" + # По умолчанию --dataseg НЕ задаём: писучие данные банкового + # модуля (глобалы, file-static, статики функций) остаются в + # общем _DATA, то есть в W1/W2 — они замаплены всегда и видны + # и банку, и остальному коду. С --dataseg BANKn они уезжают в + # СТРАНИЦУ БАНКА и снаружи не читаются (проверено на .map: + # глобал банкового модуля лёг по 0x0001C000): это экономит + # W1/W2, но требует ручного контроля видимости, поэтому + # включается явно через --bank-data. + bank_data_flags=() + [[ $BANK_LOCAL_DATA -eq 1 ]] && bank_data_flags=(--dataseg "BANK${bank_n}") run "$SDCC" "${CC_FLAGS[@]}" \ - --codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" --dataseg "BANK${bank_n}" \ + --codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" \ + "${bank_data_flags[@]}" \ -c -o "$rel" "$bank_src" BANK_RELS+=("$rel") # Virtual address: bank_n in upper byte, BANK_LOW16 in low half.