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
+16 -12
View File
@@ -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)
+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; /* окно = прямоугольник спрайта */
+29 -29
View File
@@ -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
+4 -4
View File
@@ -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;
+4 -4
View File
@@ -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);
+22 -21
View File
@@ -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--;
+21 -21
View File
@@ -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
+15
View File
@@ -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;
+17
View File
@@ -0,0 +1,17 @@
/*
* pop_state.h — состояние, общее для банков и W1/W2. См. pop_state.c.
*
* Правило: если переменную читают/пишут по обе стороны банковой границы —
* её место здесь, а НЕ в банковом модуле (там она уедет в страницу банка).
*/
#ifndef POP_STATE_H
#define POP_STATE_H
#include <stdint.h>
/* Кусок loose приземлился (loose_land, seg007:11E8): 0 = нет, иначе
* tilepos+1 тайла, на который он лёг. Ставит отрисовка mob (банк),
* разбирает логика loose-полов (pop_map, W1/W2). */
extern uint8_t pop_loose_landed;
#endif
+25 -20
View File
@@ -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) */