Files
Sprinter-SDCC/applications/PoP/roomtest/pop_level_cold.c
T
snark13 055d6d7c89 Фикс: чтение файлов нельзя выносить в банк — банковый код сам живёт в W3
Первый запуск после разгрузки встал в halt по мусору в 0xC40E.  Причина:
pop_level_read_file и pop_kid_data_load маппят страницу данных в W3 на время
read() (файл читается по 0xC000), а модули банка исполняются ИЗ ЭТОГО ЖЕ
окна — после sprinter_page_w3() следующая инструкция приходит уже из чужой
страницы.  Обе функции вернулись в резидент; в банке остался код, который
ходит через W0 (gfx_w0_map) или окон не трогает вовсе.

Правило записано в _pop_level.h и _pop_kid.h: банковый модуль НЕ маппит W3
(через W0 — можно).  Поэтому же в резиденте живёт весь libbgi.

Куча 6095 Б (было 902 до разгрузки).  Проверено в MAME: игра стартует,
комната рисуется, Кид управляем.  tests-host 6/6.
2026-08-12 22:17:18 +03:00

281 lines
15 KiB
C

/*
* pop_level_cold.c — ХОЛОДНАЯ половина уровня (БАНК 8). Контракт и деление
* половин — в _pop_level.h.
*
* Здесь всё, что отрабатывает раз на уровень (чтение файла res200N.bin,
* рестарт тайлов/стражей, потабличные различия) или раз на комнату (разбор
* комнаты в рабочие массивы, посадочный ряд колонки, страж комнаты). Цена
* выноса — один трамплин на вызов; в кадре этих вызовов ноль.
*
* Формат — порт blueprnt DAT 1.0 (Table 6 POP-DAT).
*/
#include <fcntl.h>
#include <unistd.h>
#include <sprinter.h>
#include <sprinter_mem.h>
#include <sprite.h> /* gfx_w0_map / gfx_w0_unmap */
#include "pop_level.h"
#include "_pop_level.h"
/* ISR-стаб W0-страницы (тот же приём, что atlas_load): при прерывании с
* замапленной в W0 страницей CPU прыгает на 0x0038 → там обязан быть JP на
* _gfx_w0_isr (восстановит окно). Символ внутренний для libbgi, но
* стабильный — линкер тянет его из bgi256.lib (графика уже подключена). */
extern void _gfx_w0_isr(void);
/* ЧТЕНИЕ ФАЙЛА уровня живёт в РЕЗИДЕНТЕ (pop_level.c, pop_level_read_file):
* оно маппит страницу в W3 на время read(), а этот модуль сам лежит в W3 —
* переключив окно, он вырезал бы из-под себя собственный код (проверено:
* первый же вызов улетал в halt по мусору). Правило записано в _pop_level.h. */
void pop_level_free(void) __banked
{
if (pop_lvl_ok) { mem_free_block(pop_lvl_blk); pop_lvl_ok = 0; }
}
/* ---- Потабличные различия уровней ------------------------------------ */
/* Таблицы из SDLPoP data.h:840..848, индекс = номер уровня (0 = демо).
* Держим целиком по 16 байт: короче и дешевле любых «оптимизаций», а
* расхождение с оригиналом сразу видно глазами. */
static const uint8_t tbl_entry_pose[16] =
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 };
static const uint8_t tbl_guard_hp[16] =
{ 4, 3, 3, 3, 3, 4, 5, 4, 4, 5, 5, 5, 4, 6, 0, 0 };
static const int8_t tbl_guard_type[16] =
{ 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 4, 3, -1, -1 };
/* tbl_level_type (SDLPoP data.h:840): 0 — подземелье, 1 — дворец. По нему
* расходятся не только атласы, но и ветки отрисовки (seg008), кладка стены
* (wall_pattern) и модификатор пустой клетки от упавшей плиты
* (remove_loose, seg007:0EB8). */
static const uint8_t tbl_level_type[16] =
{ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0 };
uint8_t pop_level_entry_pose(uint8_t n) __banked { return n < 16 ? tbl_entry_pose[n] : 0; }
uint8_t pop_level_guard_hp(uint8_t n) __banked { return n < 16 ? tbl_guard_hp[n] : 3; }
int8_t pop_level_guard_type(uint8_t n) __banked { return n < 16 ? tbl_guard_type[n] : -1; }
uint8_t pop_level_tileset(uint8_t n) __banked { return n < 16 ? tbl_level_type[n] : 0; }
uint8_t pop_level_type(void) __banked { return tbl_level_type[pop_current_level & 15]; }
int pop_level_load_num(uint8_t n) __banked
{
/* Имена патчим на месте, а не через sprintf: printf-семейство тянет в
* резидент W1 форматтер, которого здесь больше негде применить. */
static char pri[] = "LEVELS\\res2000.bin"; /* цифры — [12],[13] */
static char alt[] = "a:\\res2000.bin"; /* цифры — [8],[9] */
uint8_t saved_blk = pop_lvl_blk, saved_page = pop_lvl_page, saved_ok = pop_lvl_ok;
if (n > 15) return -1;
pri[12] = alt[8] = (char)('0' + n / 10);
pri[13] = alt[9] = (char)('0' + n % 10);
/* Старую страницу отпускаем ТОЛЬКО после успешной загрузки новой:
* pop_level_read_file сам аллоцирует страницу, и если файла нет — остаёмся
* на текущем уровне вместо падения в пустой уровень. */
pop_lvl_ok = 0; /* чтобы level_load_path не мешался */
if (pop_level_read_file(pri) != 0 && pop_level_read_file(alt) != 0) {
pop_lvl_blk = saved_blk; pop_lvl_page = saved_page; pop_lvl_ok = saved_ok;
return -1;
}
if (saved_ok) mem_free_block(saved_blk);
pop_current_level = n;
return 0;
}
int pop_room_load(uint8_t room, uint8_t *fg, uint8_t *bg,
uint8_t *lcol_fg, uint8_t *lcol_bg,
uint8_t *rcol_fg, uint8_t *rcol_bg, uint8_t *below_fg) __banked
{
const uint8_t *rfg, *rbg, *lnk;
uint8_t left, right, down, i;
if (!pop_lvl_ok || room < 1 || room > 24) return -1;
gfx_w0_map(pop_lvl_page);
rfg = room_fg_ptr(room);
rbg = room_bg_ptr(room);
lnk = (const uint8_t *)(LVL_DATA_OFF + BP_LINKS) + (uint16_t)(room - 1) * 4;
for (i = 0; i < ROOM_TILES; i++) {
fg[i] = (uint8_t)(rfg[i] & 0x1F); /* тип тайла (верх. биты — модиф., пока не нужны) */
bg[i] = rbg[i]; /* backtable как есть */
}
left = lnk[0]; /* L */
right = lnk[1]; /* R */
down = lnk[3]; /* D */
/* leftcol: правый столбец (col9) комнаты СЛЕВА, строки 0..2 (кромка col0),
* а в [3..5] — ПРЕДпоследний (col8). Вторая колонка нужна коллизии: Kid,
* стоящий В шве (curr_col = −1), смотрит вперёд на колонку −2, и без неё
* там оказывалась мнимая стена (get_tile → TILE_WALL), которая выпихивала
* его сквозь решётку. Оригинал резолвит любую колонку через
* find_room_of_tile (seg006:005D). */
if (left >= 1 && left <= 24) {
const uint8_t *lf = room_fg_ptr(left);
const uint8_t *lb = room_bg_ptr(left);
for (i = 0; i < 3; i++) {
lcol_fg[i] = (uint8_t)(lf[i * 10 + 9] & 0x1F);
lcol_fg[3 + i] = (uint8_t)(lf[i * 10 + 8] & 0x1F);
lcol_bg[i] = lb[i * 10 + 9];
}
} else {
for (i = 0; i < 3; i++) { lcol_fg[i] = 20; lcol_fg[3 + i] = 20; lcol_bg[i] = 0; }
}
/* rightcol: левый столбец (col0) комнаты СПРАВА, строки 0..2 (кромка col9:
* коллизия на правом шве + переход вправо) */
if (right >= 1 && right <= 24) {
const uint8_t *rf = room_fg_ptr(right);
const uint8_t *rb = room_bg_ptr(right);
for (i = 0; i < 3; i++) {
rcol_fg[i] = (uint8_t)(rf[i * 10 + 0] & 0x1F);
rcol_fg[3 + i] = (uint8_t)(rf[i * 10 + 1] & 0x1F); /* колонка 11 (см. выше) */
rcol_bg[i] = rb[i * 10 + 0];
}
} else {
for (i = 0; i < 3; i++) { rcol_fg[i] = 20; rcol_fg[3 + i] = 20; rcol_bg[i] = 0; }
}
/* belowrow: верхний ряд (row0) комнаты снизу — ОДИННАДЦАТЬ байт.
* [0..9] — колонки 0..9 комнаты снизу; [10] — тайл (0,9) комнаты
* ПО ДИАГОНАЛИ СНИЗУ-СЛЕВА (room_BL у оригинала). Одиннадцатый нужен
* тайлу (2,0): его «сосед снизу-слева» — это колонка −1 ряда 3, то есть
* чужая комната, и load_rowbelow (seg008:368) резолвит её именно так,
* с дефолтом «стена» только когда такой комнаты НЕТ. Раньше мы клали
* туда стену БЕЗУСЛОВНО, и в пустом углу рисовался клин кладки
* (BUG-SEAM-WEDGE-1, ур. 3 комн. 18). */
if (down >= 1 && down <= 24) {
const uint8_t *df = room_fg_ptr(down);
uint8_t dl = ((const uint8_t *)(LVL_DATA_OFF + BP_LINKS))[(down - 1) * 4];
for (i = 0; i < 10; i++) below_fg[i] = (uint8_t)(df[i] & 0x1F);
below_fg[10] = (dl >= 1 && dl <= 24)
? (uint8_t)(room_fg_ptr(dl)[9] & 0x1F)
: 20; /* кромка уровня */
} else {
/* Комнаты снизу НЕТ. Оригинал (load_rowbelow, seg008:368) в этом
* случае подставляет РАЗНЫЕ кромки: колонкам 1..9 — `tiles_0_empty`,
* и только левому краю (колонка 0, тайл из room_BL) — `tiles_20_wall`.
* У нас тут стояла стена на всё, и её грань (topright стены) лезла
* жёлтыми треугольниками под пустые тайлы нижнего ряда — видно в
* шахте падения, уровень 6 комната 3 (BUG-BELOWROW-WALL). */
for (i = 0; i < 10; i++) below_fg[i] = 0; /* tiles_0_empty */
below_fg[10] = 20; /* левая кромка уровня */
}
gfx_w0_unmap();
return 0;
}
int8_t pop_room_col_landing(uint8_t room, uint8_t col) __banked
{
const uint8_t *rf;
int8_t row, res = -1;
if (!pop_lvl_ok || room < 1 || room > 24 || col > 9) return -1;
gfx_w0_map(pop_lvl_page);
rf = room_fg_ptr(room);
for (row = 0; row < 3; row++) { /* кусок пролетает пустые ряды */
uint8_t t = (uint8_t)(rf[row * 10 + col] & 0x1F);
if (t == 0) continue; /* empty — дальше вниз */
/* Набор площадок — дословно switch из loose_land (seg007:11E8):
* пол, пика, обе кнопки, зелье и оба факела. На всём остальном
* (стена, ворота, колонна) кусок просто исчезает. Раньше здесь
* стоял только пол, и плита, падавшая на КНОПКУ в комнате снизу,
* пропадала бесследно: ни щебня, ни нажатия (уровень 4, плита
* 16(1,1) -> кнопка 17(0,1), открывающая ворота 23(0,9)). */
if (t == 1 || t == 2 || t == 6 || t == 10 ||
t == 15 || t == 19 || t == 30) res = row;
break; /* первый не-пустой тайл — стоп */
}
gfx_w0_unmap();
return res;
}
/* Рестарт уровня (порт load_level из цикла play_level, seg003:57): вернуть
* foretable в исходное состояние из эталонной копии. */
void pop_level_reset_tiles(void) __banked
{
uint16_t i;
uint8_t *dst;
const uint8_t *src;
if (!pop_lvl_ok) return;
gfx_w0_map(pop_lvl_page);
dst = (uint8_t *)(LVL_DATA_OFF + BP_FG);
src = (const uint8_t *)LVL_PRISTINE_OFF;
for (i = 0; i < 24u * ROOM_TILES; i++) dst[i] = src[i];
gfx_w0_unmap();
}
/* Рестарт уровня, часть вторая (тот же load_level + pos_guards, seg003:83):
* стражи — такая же часть данных уровня, как тайлы, и перезагрузка файла
* возвращает их всех. Убитый до смерти Кида страж после respawn снова жив
* и стоит на исходном тайле; забывается и запомненная поза трупа
* (guards_seq_hi = 0 -> «поднять стандартной стойкой»). */
void pop_level_reset_guards(void) __banked
{
if (!pop_lvl_ok) return;
pop_gstate_init();
}
void pop_gstate_init(void) __banked
{
const uint8_t *p = (const uint8_t *)LVL_DATA_OFF;
uint8_t r;
gfx_w0_map(pop_lvl_page);
for (r = 0; r < 24; r++) {
uint8_t *g = pop_gstate + (uint16_t)r * 6;
g[GS_TILE] = p[BP_GTILE + r];
g[GS_DIR] = p[BP_GDIR + r];
g[GS_X] = p[BP_GX + r];
g[GS_SKILL] = p[BP_GSKILL + r];
/* guards_seq_lo/hi в ФАЙЛЕ не используются — там 0xFF во всех
* комнатах (оригинал чистит их в reset_level_unused_fields).
* Осмысленное значение туда кладёт только leave_guard, поэтому
* стартуем нулями: 0 = «поднимать стандартной стойкой».
* Без этого в интерпретатор уходил curr_seq = 0xFFFF и он вис. */
g[GS_SEQL] = 0;
g[GS_SEQH] = 0;
}
gfx_w0_unmap();
}
/* Страж комнаты (enter_guard, seg002:0112 + pos_guards, seg003). Возврат
* 0 = стража в комнате нет (tile >= 30). X оригинал пересчитывает из
* колонки (pos_guards), поэтому guards_x из файла не читаем. */
uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
uint8_t *color, uint8_t *skill) __banked
{
const uint8_t *g;
uint8_t t;
if (!pop_lvl_ok || room < 1 || room > 24) return 0;
/* Тайл/направление/мастерство — из ЖИВОЙ копии (её правит
* pop_guard_state_save); цвет статичен, его читаем со страницы. */
g = pop_gstate + (uint16_t)(room - 1) * 6;
t = g[GS_TILE];
if (t < 30) {
*tile = t;
*dir = (int8_t)g[GS_DIR];
*skill = g[GS_SKILL];
gfx_w0_map(pop_lvl_page);
*color = (uint8_t)(((const uint8_t *)LVL_DATA_OFF)[BP_GCOLOR + room - 1] & 0x0F);
gfx_w0_unmap();
}
return (uint8_t)(t < 30);
}
/* ---- Стартовая позиция уровня ---------------------------------------- */
static uint8_t start_byte(uint16_t off)
{
uint8_t v;
if (!pop_lvl_ok) return 0;
gfx_w0_map(pop_lvl_page);
v = ((const uint8_t *)(LVL_DATA_OFF))[off];
gfx_w0_unmap();
return v;
}
uint8_t pop_level_start_room(void) __banked { return start_byte(BP_START + 0); }
uint8_t pop_level_start_pos(void) __banked { return start_byte(BP_START + 1); }
int8_t pop_level_start_dir(void) __banked { return (int8_t)start_byte(BP_START + 2); }