diff --git a/applications/PoP/roomtest/_pop_kid.h b/applications/PoP/roomtest/_pop_kid.h index c07bbca..4fbfcfd 100644 --- a/applications/PoP/roomtest/_pop_kid.h +++ b/applications/PoP/roomtest/_pop_kid.h @@ -20,4 +20,10 @@ extern uint8_t kdat_blk; /* EMM-блок страницы kid_data.bin */ extern uint8_t kdat_page; /* её номер (маппится в W0 на время play_seq) */ extern uint8_t kdat_ok; /* данные загружены */ +/* ПРАВИЛО ОКНА W3: банковый модуль исполняется из W3 и переключать это окно + * не может (следующая инструкция придёт уже из чужой страницы). Поэтому + * ЧТЕНИЕ kid_data.bin — pop_kid_data_load — осталось в резиденте, а в банк + * уехало только то, что читает через W0 или вообще не трогает окна. + * То же правило и в _pop_level.h. */ + #endif /* _POP_KID_H */ diff --git a/applications/PoP/roomtest/_pop_level.h b/applications/PoP/roomtest/_pop_level.h index 7bad5ed..e527a85 100644 --- a/applications/PoP/roomtest/_pop_level.h +++ b/applications/PoP/roomtest/_pop_level.h @@ -69,6 +69,14 @@ static const uint8_t *room_bg_ptr(uint8_t r) return (const uint8_t *)(LVL_DATA_OFF + BP_BG) + (uint16_t)(r - 1) * ROOM_TILES; } +/* ПРАВИЛО ОКНА W3. Банковый модуль исполняется ИЗ W3, поэтому сам он + * менять это окно НЕ МОЖЕТ: после sprinter_page_w3(другая_страница) + * следующая инструкция читается уже из чужой страницы. Всё чтение файлов + * (оно грузит данные по 0xC000) обязано жить в резиденте — отсюда + * pop_level_read_file здесь, а не в pop_level_cold.c. Через W0 + * (gfx_w0_map) банкам работать можно: это другое окно. */ +int pop_level_read_file(const char *path); + /* Живая копия стражей из данных уровня (загрузка/рестарт) — в холодной. */ void pop_gstate_init(void) __banked; diff --git a/applications/PoP/roomtest/pop_kboot.c b/applications/PoP/roomtest/pop_kboot.c index b339823..df9cde2 100644 --- a/applications/PoP/roomtest/pop_kboot.c +++ b/applications/PoP/roomtest/pop_kboot.c @@ -48,40 +48,9 @@ void pop_kid_free(void) __banked kid_npages = 0; } -/* Таблица кадров (241*5 Б) и seqtbl (2310 Б) занимали 3.5 КБ в _CODE окна - * W1/W2 — самый дефицитный ресурс. Теперь они лежат в отдельной EMM- - * странице, которая маппится в W0 РОВНО на время play_seq (раз за тик). - * Страница адресуется как у pop_level: данные с 0x100, ниже — ISR-стаб. */ -int pop_kid_data_load(const char *path) __banked -{ - uint8_t *pg = (uint8_t *)0xC000; /* страница мапится в W3 на время чтения */ - uint8_t saved_w3, blk; - uint16_t stub; - int fd, n; - - fd = open(path, O_RDONLY); - if (fd < 0) return -1; - blk = mem_alloc_pages(1); - if (!blk) { close(fd); return -1; } - kdat_blk = blk; - kdat_page = mem_get_page(blk, 0); - - saved_w3 = _io_page_w3; - sprinter_page_w3(kdat_page); - n = read(fd, pg + KD_DATA_OFF, 16384 - KD_DATA_OFF); - if (n >= (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) { - stub = (uint16_t)&_gfx_w0_isr; /* ISR-стаб W0-страницы (как pop_level) */ - pg[0x38] = 0xC3; - pg[0x39] = (uint8_t)(stub & 0xFF); - pg[0x3A] = (uint8_t)(stub >> 8); - pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN */ - } - sprinter_page_w3(saved_w3); - close(fd); - if (n < (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) { mem_free_block(blk); return -1; } - kdat_ok = 1; - return 0; -} +/* ЗАГРУЗКА kid_data.bin (pop_kid_data_load) осталась в РЕЗИДЕНТЕ: она + * маппит страницу в W3 на время чтения, а этот модуль сам в W3 — см. + * то же правило в _pop_kid.h. */ void pop_kid_data_free(void) __banked { diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index 80f93d8..1e5932b 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -16,6 +16,9 @@ #include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */ #include "pop_map.h" /* pop_clip_char_top (clip_char) */ #include "pop_geom.h" +#include +#include +#include #include "kid_data.h" #include "_pop_kid.h" /* дескриптор страницы данных (её грузит банк 8) */ @@ -416,3 +419,40 @@ void kid_tick(void) /* Отрисовка кадра (load_frame_to_obj, heal, клип, брызги, клинок) живёт в * pop_cdraw.c — ОДНА на всех Char, см. DRAW-CHAR. Отсюда она получает * атласы Кида (kidp/kid_npages) и его кадр (kid_frame). */ + +/* Таблица кадров (241*5 Б) и seqtbl (2310 Б) занимали 3.5 КБ в _CODE окна + * W1/W2 — самый дефицитный ресурс. Теперь они лежат в отдельной EMM- + * странице, которая маппится в W0 РОВНО на время play_seq (раз за тик). + * Страница адресуется как у pop_level: данные с 0x100, ниже — ISR-стаб. */ +/* ЖИВЁТ В РЕЗИДЕНТЕ, хотя зовётся раз за игру: маппит страницу в W3 на + * время чтения, а банковый код сам лежит в W3 (см. _pop_kid.h). */ +int pop_kid_data_load(const char *path) +{ + uint8_t *pg = (uint8_t *)0xC000; /* страница мапится в W3 на время чтения */ + uint8_t saved_w3, blk; + uint16_t stub; + int fd, n; + + fd = open(path, O_RDONLY); + if (fd < 0) return -1; + blk = mem_alloc_pages(1); + if (!blk) { close(fd); return -1; } + kdat_blk = blk; + kdat_page = mem_get_page(blk, 0); + + saved_w3 = _io_page_w3; + sprinter_page_w3(kdat_page); + n = read(fd, pg + KD_DATA_OFF, 16384 - KD_DATA_OFF); + if (n >= (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) { + stub = (uint16_t)&_gfx_w0_isr; /* ISR-стаб W0-страницы (как pop_level) */ + pg[0x38] = 0xC3; + pg[0x39] = (uint8_t)(stub & 0xFF); + pg[0x3A] = (uint8_t)(stub >> 8); + pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN */ + } + sprinter_page_w3(saved_w3); + close(fd); + if (n < (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) { mem_free_block(blk); return -1; } + kdat_ok = 1; + return 0; +} diff --git a/applications/PoP/roomtest/pop_kid.h b/applications/PoP/roomtest/pop_kid.h index d08fac9..bbe1ef5 100644 --- a/applications/PoP/roomtest/pop_kid.h +++ b/applications/PoP/roomtest/pop_kid.h @@ -35,7 +35,7 @@ int pop_kid_load(uint8_t npages) __banked; * (kid_data.bin): в _CODE окна W1/W2 они занимали 3.5 КБ. Грузить ДО * первого kid_init/play_seq. 0 — OK, -1 — ошибка (без них Kid не оживёт). * Страница маппится в W0 внутри play_seq, раз за тик. */ -int pop_kid_data_load(const char *path) __banked; +int pop_kid_data_load(const char *path); /* Прочитать кадр из таблицы страницы данных: tbl_off — KID_BIN_FRAMES_OFF * (Kid) или KID_BIN_GFRAMES_OFF (страж, своя таблица frame_tbl_guard). diff --git a/applications/PoP/roomtest/pop_level.c b/applications/PoP/roomtest/pop_level.c index c8e85b6..e320766 100644 --- a/applications/PoP/roomtest/pop_level.c +++ b/applications/PoP/roomtest/pop_level.c @@ -11,7 +11,16 @@ * объявлено в _pop_level.h — данные банковых модулей всё равно линкуются в * общий _DATA, так что холодная половина видит их напрямую. */ +#include +#include +#include +#include #include /* gfx_w0_map / gfx_w0_unmap */ + +/* ISR-стаб W0-страницы (тот же приём, что atlas_load): при прерывании с + * замапленной в W0 страницей CPU прыгает на 0x0038 → там обязан быть JP на + * _gfx_w0_isr (восстановит окно). */ +extern void _gfx_w0_isr(void); #include "pop_level.h" #include "_pop_level.h" @@ -129,3 +138,53 @@ uint8_t pop_guard_state_x(uint8_t room) if (room < 1 || room > 24) return 0; return pop_gstate[(uint16_t)(room - 1) * 6 + GS_X]; } + +/* Чтение файла уровня в свежую EMM-страницу. ЖИВЁТ В РЕЗИДЕНТЕ, хотя + * зовётся раз на уровень: функция маппит страницу в W3 на время read(), а + * весь холодный код лежит В ЭТОМ ЖЕ окне — переключив его оттуда, банк + * вырезает из-под себя собственные инструкции (первый же вызов улетал в + * halt по мусору, поймано в MAME 2026-08-12). Правило — в _pop_level.h. + * Наружу не публичная в pop_level.h: уровень грузят по НОМЕРУ. */ +int pop_level_read_file(const char *path) +{ + uint8_t *pg = (uint8_t *)0xC000; /* страница мапится в W3 на время чтения */ + uint8_t saved_w3, blk; + uint16_t stub; + int fd, n; + + fd = open(path, O_RDONLY); + if (fd < 0) return -1; + + blk = mem_alloc_pages(1); + if (!blk) { close(fd); return -1; } + pop_lvl_blk = blk; + pop_lvl_page = mem_get_page(blk, 0); + + saved_w3 = _io_page_w3; + sprinter_page_w3(pop_lvl_page); + /* Файл целиком в offset 0x100 (ESTEX READ пишет в W3 — как atlas_load). */ + n = read(fd, pg + LVL_DATA_OFF, 16384 - LVL_DATA_OFF); + if (n >= (int)MIN_SIZE) { /* пропатчить ISR-стаб */ + uint16_t i; + stub = (uint16_t)&_gfx_w0_isr; + pg[0x38] = 0xC3; /* JP _gfx_w0_isr */ + pg[0x39] = (uint8_t)(stub & 0xFF); + pg[0x3A] = (uint8_t)(stub >> 8); + pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN (NMI-хвост) */ + /* Скопировать таблицы дверных связей в W2 (страница ещё в W3). */ + for (i = 0; i < 256; i++) { + pop_dl1[i] = pg[LVL_DATA_OFF + BP_LINKLOC + i]; + pop_dl2[i] = pg[LVL_DATA_OFF + BP_LINKMAP + i]; + } + /* Эталон foretable — для рестарта уровня (см. LVL_PRISTINE_OFF). */ + for (i = 0; i < 24u * ROOM_TILES; i++) + pg[LVL_PRISTINE_OFF + i] = pg[LVL_DATA_OFF + BP_FG + i]; + } + sprinter_page_w3(saved_w3); + close(fd); + + if (n < (int)MIN_SIZE) { mem_free_block(blk); return -1; } + pop_lvl_ok = 1; + pop_gstate_init(); /* живая копия состояния стражей — из уровня */ + return 0; +} diff --git a/applications/PoP/roomtest/pop_level_cold.c b/applications/PoP/roomtest/pop_level_cold.c index b7c31fa..a6803d5 100644 --- a/applications/PoP/roomtest/pop_level_cold.c +++ b/applications/PoP/roomtest/pop_level_cold.c @@ -23,51 +23,10 @@ * стабильный — линкер тянет его из bgi256.lib (графика уже подключена). */ extern void _gfx_w0_isr(void); -/* Загрузка по пути. Не публичная: снаружи уровень грузят по НОМЕРУ - * (pop_level_load_num), а тот сам перебирает пути. */ -static int level_load_path(const char *path) -{ - uint8_t *pg = (uint8_t *)0xC000; /* страница мапится в W3 на время чтения */ - uint8_t saved_w3, blk; - uint16_t stub; - int fd, n; - - fd = open(path, O_RDONLY); - if (fd < 0) return -1; - - blk = mem_alloc_pages(1); - if (!blk) { close(fd); return -1; } - pop_lvl_blk = blk; - pop_lvl_page = mem_get_page(blk, 0); - - saved_w3 = _io_page_w3; - sprinter_page_w3(pop_lvl_page); - /* Файл целиком в offset 0x100 (ESTEX READ пишет в W3 — как atlas_load). */ - n = read(fd, pg + LVL_DATA_OFF, 16384 - LVL_DATA_OFF); - if (n >= (int)MIN_SIZE) { /* пропатчить ISR-стаб */ - uint16_t i; - stub = (uint16_t)&_gfx_w0_isr; - pg[0x38] = 0xC3; /* JP _gfx_w0_isr */ - pg[0x39] = (uint8_t)(stub & 0xFF); - pg[0x3A] = (uint8_t)(stub >> 8); - pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN (NMI-хвост) */ - /* Скопировать таблицы дверных связей в W2 (страница ещё в W3). */ - for (i = 0; i < 256; i++) { - pop_dl1[i] = pg[LVL_DATA_OFF + BP_LINKLOC + i]; - pop_dl2[i] = pg[LVL_DATA_OFF + BP_LINKMAP + i]; - } - /* Эталон foretable — для рестарта уровня (см. LVL_PRISTINE_OFF). */ - for (i = 0; i < 24u * ROOM_TILES; i++) - pg[LVL_PRISTINE_OFF + i] = pg[LVL_DATA_OFF + BP_FG + i]; - } - sprinter_page_w3(saved_w3); - close(fd); - - if (n < (int)MIN_SIZE) { mem_free_block(blk); return -1; } - pop_lvl_ok = 1; - pop_gstate_init(); /* живая копия состояния стражей — из уровня */ - return 0; -} +/* ЧТЕНИЕ ФАЙЛА уровня живёт в РЕЗИДЕНТЕ (pop_level.c, pop_level_read_file): + * оно маппит страницу в W3 на время read(), а этот модуль сам лежит в W3 — + * переключив окно, он вырезал бы из-под себя собственный код (проверено: + * первый же вызов улетал в halt по мусору). Правило записано в _pop_level.h. */ void pop_level_free(void) __banked { @@ -111,10 +70,10 @@ int pop_level_load_num(uint8_t n) __banked pri[13] = alt[9] = (char)('0' + n % 10); /* Старую страницу отпускаем ТОЛЬКО после успешной загрузки новой: - * level_load_path сам аллоцирует страницу, и если файла нет — остаёмся + * pop_level_read_file сам аллоцирует страницу, и если файла нет — остаёмся * на текущем уровне вместо падения в пустой уровень. */ pop_lvl_ok = 0; /* чтобы level_load_path не мешался */ - if (level_load_path(pri) != 0 && level_load_path(alt) != 0) { + 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; }