diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile index d8b2841..7e9cea1 100644 --- a/applications/PoP/roomtest/Makefile +++ b/applications/PoP/roomtest/Makefile @@ -35,14 +35,14 @@ MEMORY ?= huge # small-раскладка + банки кода в W # ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том # же ALLOCS — иначе сравниваются не правки, а уровни оптимизации. ALLOCS ?= 3000 -EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c --bank 8=roomtest_cold.c $(PROF_FLAGS) +EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c --bank 7=pop_redraw.c --bank 8=roomtest_cold.c --bank 8=pop_level_cold.c $(PROF_FLAGS) # Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по # умолчанию, пока идёт работа с производительностью; make PROF=0 выключает. # Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте # MAME = время фазы в растровых строках. PROF ?= 1 PROF_FLAGS := -DPROF_BORDER=$(PROF) -EXTRA_SRCS := pop_state.c pop_draw.c pop_tile.c pop_kid.c pop_level.c pop_redraw.c pop_geom.c pop_guard.c +EXTRA_SRCS := pop_state.c pop_draw.c pop_tile.c pop_kid.c pop_level.c pop_geom.c pop_guard.c BG_DIR := $(CURDIR)/../poc/res/bg KID_DIR := $(CURDIR)/../poc/res/kid @@ -112,4 +112,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: roomtest_cold.c roomtest_cold.h guards.c pop_guard.c pop_guard.h pop_cdraw.c pop_cdraw.h pop_draw.c _pop_draw.h pop_tile.c pop_tile.h pop_state.c pop_state.h pop_cheat.h pop_bg.c pop_bg.h pop_room.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: roomtest_cold.c roomtest_cold.h guards.c pop_guard.c pop_guard.h pop_cdraw.c pop_cdraw.h pop_draw.c _pop_draw.h pop_tile.c pop_tile.h pop_state.c pop_state.h pop_cheat.h pop_bg.c pop_bg.h pop_room.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_cold.c _pop_level.h 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_level.h b/applications/PoP/roomtest/_pop_level.h new file mode 100644 index 0000000..7bad5ed --- /dev/null +++ b/applications/PoP/roomtest/_pop_level.h @@ -0,0 +1,75 @@ +/* + * _pop_level.h — ВНУТРЕННИЙ контракт между двумя половинами уровня: + * + * pop_level.c — ГОРЯЧАЯ (резидент W1): чтение тайлов/связей/дверных + * таблиц, живое состояние стражей. Её зовут все банки + * и кадровый путь главного цикла, местами на каждый тайл. + * pop_level_cold.c — ХОЛОДНАЯ (банк 8): чтение файла уровня, разбор комнаты, + * рестарт, потабличные различия уровней. Всё это + * отрабатывает раз на уровень или раз на комнату, и + * держать ~1.9 КБ в дефицитном W1 незачем. + * + * Общее состояние (страница уровня, копии дверных таблиц, живые стражи) + * живёт в ГОРЯЧЕЙ половине и объявлено здесь: данные банковых модулей + * линкуются в общий _DATA (--bank-data мы не включаем), поэтому обе + * половины видят их как обычные глобалы — в банк уехал только КОД. + * + * Заголовок НЕ публичный: снаружи уровень виден только через pop_level.h. + */ +#ifndef _POP_LEVEL_H +#define _POP_LEVEL_H + +#include + +/* Раскладка страницы уровня (Table 6 POP-DAT); подробности — в pop_level.c. */ +#define LVL_DATA_OFF 0x100u /* данные с 0x100; 0x00..0xFF — ISR-стаб */ +#define LVL_PRISTINE_OFF 0x1000u /* эталон foretable для рестарта уровня */ + +#define BP_FG 0u +#define BP_BG 720u +#define BP_LINKLOC 1440u +#define BP_LINKMAP 1696u +#define BP_LINKS 1952u +#define BP_GTILE 2119u +#define BP_GDIR 2143u +#define BP_GX 2167u +#define BP_GSEQLO 2191u +#define BP_GSKILL 2215u +#define BP_GSEQHI 2239u +#define BP_GCOLOR 2263u +#define BP_START 2112u +#define ROOM_TILES 30u +#define MIN_SIZE (BP_START + 3u) + +/* Живое состояние стражей: 6 байт на комнату (порт level.guards_*). */ +#define GS_TILE 0 +#define GS_DIR 1 +#define GS_X 2 +#define GS_SKILL 3 +#define GS_SEQL 4 +#define GS_SEQH 5 + +/* ---- общее состояние (определено в pop_level.c) ---------------------- */ +extern uint8_t pop_lvl_blk; /* EMM-блок страницы уровня */ +extern uint8_t pop_lvl_page; /* её номер (маппится в W0/W3) */ +extern uint8_t pop_lvl_ok; /* уровень загружен */ +extern uint8_t pop_dl1[256]; /* LINKLOC как в файле */ +extern uint8_t pop_dl2[256]; /* LINKMAP, МУТАБЕЛЬНА (таймеры кнопок) */ +extern uint8_t pop_gstate[24 * 6]; + +/* Указатели на тайлы комнаты r (1..24) в W0-пространстве — страницу мапит + * вызывающий. static-инлайн в обеих половинах: две команды сложения, звать + * их через границу банка было бы дороже самого вычисления. */ +static const uint8_t *room_fg_ptr(uint8_t r) +{ + return (const uint8_t *)(LVL_DATA_OFF + BP_FG) + (uint16_t)(r - 1) * ROOM_TILES; +} +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; +} + +/* Живая копия стражей из данных уровня (загрузка/рестарт) — в холодной. */ +void pop_gstate_init(void) __banked; + +#endif /* _POP_LEVEL_H */ diff --git a/applications/PoP/roomtest/pop_level.c b/applications/PoP/roomtest/pop_level.c index c0eb5ed..c8e85b6 100644 --- a/applications/PoP/roomtest/pop_level.c +++ b/applications/PoP/roomtest/pop_level.c @@ -1,269 +1,32 @@ /* - * pop_level.c — загрузка уровня PoP из сырого res200N.bin (Фаза L1). - * См. pop_level.h. Порт формата blueprnt DAT 1.0 (Table 6 POP-DAT). + * pop_level.c — ГОРЯЧАЯ половина уровня: доступ к уже загруженным данным. + * См. pop_level.h (публичный API) и _pop_level.h (деление половин). + * + * Здесь живёт то, что зовут на каждый тайл и из любого банка: чтение + * foretable/backtable, дверные связи, живое состояние стражей. Загрузка + * файла, разбор комнаты и рестарт уровня — в pop_level_cold.c (банк 8): + * они отрабатывают раз на уровень/комнату, и в дефицитном W1 им не место. + * + * Общее состояние (страница, дверные таблицы, стражи) определено ЗДЕСЬ и + * объявлено в _pop_level.h — данные банковых модулей всё равно линкуются в + * общий _DATA, так что холодная половина видит их напрямую. */ -#include -#include -#include -#include #include /* 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); - -#define LVL_DATA_OFF 0x100u /* данные с 0x100; 0x00..0xFF — ISR-стаб */ -/* Эталонная копия foretable В ТОЙ ЖЕ странице: страница 16 КБ, данных - * уровня 2.3 КБ — место даром. Нужна, потому что foretable теперь - * МУТАБЕЛЬНАЯ (pop_level_set_tile), а рестарт уровня в оригинале — - * это load_level() заново (play_level, seg003:57), то есть все плиты целы - * и зелья на месте. pop_level_reset_tiles восстанавливает из неё. */ -#define LVL_PRISTINE_OFF 0x1000u - -/* Смещения блоков blueprnt относительно начала данных (Table 6). */ -#define BP_FG 0u -#define BP_BG 720u -#define BP_LINKLOC 1440u /* doorlink1 (LINKLOC), 256 Б */ -#define BP_LINKMAP 1696u /* doorlink2 (LINKMAP), 256 Б */ -#define BP_LINKS 1952u -/* Стражи (level_type, types.h:228): по байту на комнату в каждом массиве. */ -#define BP_GTILE 2119u -#define BP_GDIR 2143u -#define BP_GX 2167u -#define BP_GSEQLO 2191u -#define BP_GSKILL 2215u -#define BP_GSEQHI 2239u -#define BP_GCOLOR 2263u -#define BP_START 2112u -#define ROOM_TILES 30u -#define MIN_SIZE (BP_START + 3u) /* минимум, чтобы прочитать старт */ - -/* fwd: инициализация живой копии стражей (тело ниже). */ -static void gstate_init(void); - - -static uint8_t lvl_blk, lvl_page, lvl_ok; +uint8_t pop_lvl_blk, pop_lvl_page, pop_lvl_ok; /* Копии таблиц дверных связей в W2 (LINKMAP мутабельна — таймеры кнопок). */ -static uint8_t dl1[256], dl2[256]; - -int pop_level_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; } - lvl_blk = blk; - lvl_page = mem_get_page(blk, 0); - - saved_w3 = _io_page_w3; - sprinter_page_w3(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++) { - dl1[i] = pg[LVL_DATA_OFF + BP_LINKLOC + i]; - 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; } - lvl_ok = 1; - gstate_init(); /* живая копия состояния стражей — из уровня */ - return 0; -} - -void pop_level_free(void) -{ - if (lvl_ok) { mem_free_block(lvl_blk); lvl_ok = 0; } -} - -/* ---- Номер уровня и потабличные различия ---------------------------- */ +uint8_t pop_dl1[256], pop_dl2[256]; +/* Живое состояние стражей: в оригинале массивы стражей — часть структуры + * уровня в ОЗУ, и движок их ПЕРЕПИСЫВАЕТ (leave_guard кладёт туда позицию/ + * направление/мастерство, а у мёртвого ещё и curr_seq), благодаря чему, + * вернувшись, находишь труп там же. У нас уровень лежит в EMM-странице + * только на чтение, поэтому держим мутабельную копию в W2: 6 байт/комнату. */ +uint8_t pop_gstate[24 * 6]; uint8_t pop_current_level; -/* Таблицы из 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 }; -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) { return n < 16 ? tbl_entry_pose[n] : 0; } -uint8_t pop_level_guard_hp(uint8_t n) { return n < 16 ? tbl_guard_hp[n] : 3; } -int8_t pop_level_guard_type(uint8_t n) { return n < 16 ? tbl_guard_type[n] : -1; } -uint8_t pop_level_tileset(uint8_t n) { return n < 16 ? tbl_level_type[n] : 0; } - -int pop_level_load_num(uint8_t n) -{ - /* Имена патчим на месте, а не через sprintf: printf-семейство тянет в - * резидент W1 форматтер, которого здесь больше негде применить. */ - static char pri[] = "LEVELS\\res2000.bin"; /* цифры — [12],[13] */ - static char alt[] = "a:\\res2000.bin"; /* цифры — [8],[9] */ - uint8_t saved_blk = lvl_blk, saved_page = lvl_page, saved_ok = 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_load сам аллоцирует страницу, и если файла нет — остаёмся - * на текущем уровне вместо падения в пустой уровень. */ - lvl_ok = 0; /* чтобы pop_level_load не мешался */ - if (pop_level_load(pri) != 0 && pop_level_load(alt) != 0) { - lvl_blk = saved_blk; lvl_page = saved_page; lvl_ok = saved_ok; - return -1; - } - if (saved_ok) mem_free_block(saved_blk); - pop_current_level = n; - return 0; -} - -/* Указатель на fg-тайлы комнаты r (1..24) в W0-пространстве (страница уже - * замаплена вызывающим). */ -static const uint8_t *room_fg_ptr(uint8_t r) -{ - return (const uint8_t *)(LVL_DATA_OFF + BP_FG) + (uint16_t)(r - 1) * ROOM_TILES; -} -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; -} - -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) -{ - const uint8_t *rfg, *rbg, *lnk; - uint8_t left, right, down, i; - - if (!lvl_ok || room < 1 || room > 24) return -1; - - gfx_w0_map(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) -{ - const uint8_t *rf; - int8_t row, res = -1; - if (!lvl_ok || room < 1 || room > 24 || col > 9) return -1; - gfx_w0_map(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; -} - /* set_tile-запись в ЖИВУЮ foretable уровня (порт curr_room_tiles[…] = …). * В оригинале уровень — обычная структура в ОЗУ, и do_pickup (seg006:1671), * remove_loose (seg007:0EB8), loose_land (seg007:11E8) правят её насовсем. @@ -273,48 +36,22 @@ int8_t pop_room_col_landing(uint8_t room, uint8_t col) * отношения не имеют. */ void pop_level_set_tile(uint8_t room, uint8_t tilepos, uint8_t tile) { - if (!lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return; - gfx_w0_map(lvl_page); + if (!pop_lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return; + gfx_w0_map(pop_lvl_page); ((uint8_t *)(LVL_DATA_OFF + BP_FG))[(uint16_t)(room - 1) * ROOM_TILES + tilepos] = tile; gfx_w0_unmap(); } -/* Рестарт уровня (порт load_level из цикла play_level, seg003:57): вернуть - * foretable в исходное состояние из эталонной копии. */ -void pop_level_reset_tiles(void) -{ - uint16_t i; - uint8_t *dst; - const uint8_t *src; - if (!lvl_ok) return; - gfx_w0_map(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) -{ - if (!lvl_ok) return; - gstate_init(); -} - -uint8_t pop_doorlink1(uint8_t i) { return dl1[i]; } -uint8_t pop_doorlink2(uint8_t i) { return dl2[i]; } -void pop_doorlink2_set(uint8_t i, uint8_t v) { dl2[i] = v; } +uint8_t pop_doorlink1(uint8_t i) { return pop_dl1[i]; } +uint8_t pop_doorlink2(uint8_t i) { return pop_dl2[i]; } +void pop_doorlink2_set(uint8_t i, uint8_t v) { pop_dl2[i] = v; } uint8_t pop_level_tile(uint8_t room, uint8_t tilepos) { uint8_t v; - if (!lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return 0; - gfx_w0_map(lvl_page); + if (!pop_lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return 0; + gfx_w0_map(pop_lvl_page); v = (uint8_t)(room_fg_ptr(room)[tilepos] & 0x1F); gfx_w0_unmap(); return v; @@ -325,12 +62,12 @@ uint8_t pop_level_tile(uint8_t room, uint8_t tilepos) * окна 0 в горячих циклах (pop_process_trobs звал pop_level_tile на каждый * trob каждый кадр). Между begin/end НЕЛЬЗЯ рисовать/звать код, которому * нужно окно 0 в исходном состоянии — только сырые чтения уровня. */ -void pop_level_access_begin(void) { if (lvl_ok) gfx_w0_map(lvl_page); } -void pop_level_access_end(void) { if (lvl_ok) gfx_w0_unmap(); } +void pop_level_access_begin(void) { if (pop_lvl_ok) gfx_w0_map(pop_lvl_page); } +void pop_level_access_end(void) { if (pop_lvl_ok) gfx_w0_unmap(); } uint8_t pop_level_tile_raw(uint8_t room, uint8_t tilepos) { - if (!lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return 0; + if (!pop_lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return 0; return (uint8_t)(room_fg_ptr(room)[tilepos] & 0x1F); } @@ -339,11 +76,11 @@ void pop_level_room_bg(uint8_t room, uint8_t *out30) const uint8_t *rbg; uint8_t i; if (!out30) return; - if (!lvl_ok || room < 1 || room > 24) { + if (!pop_lvl_ok || room < 1 || room > 24) { for (i = 0; i < ROOM_TILES; i++) out30[i] = 0; return; } - gfx_w0_map(lvl_page); + gfx_w0_map(pop_lvl_page); rbg = room_bg_ptr(room); for (i = 0; i < ROOM_TILES; i++) out30[i] = rbg[i]; gfx_w0_unmap(); @@ -352,63 +89,13 @@ void pop_level_room_bg(uint8_t room, uint8_t *out30) uint8_t pop_room_link(uint8_t room, uint8_t side) { uint8_t v; - if (!lvl_ok || room < 1 || room > 24 || side > 3) return 0; - gfx_w0_map(lvl_page); + if (!pop_lvl_ok || room < 1 || room > 24 || side > 3) return 0; + gfx_w0_map(pop_lvl_page); v = ((const uint8_t *)(LVL_DATA_OFF + BP_LINKS))[(uint16_t)(room - 1) * 4 + side]; gfx_w0_unmap(); return v; } -static uint8_t start_byte(uint16_t off) -{ - uint8_t v; - if (!lvl_ok) return 0; - gfx_w0_map(lvl_page); - v = ((const uint8_t *)(LVL_DATA_OFF))[off]; - gfx_w0_unmap(); - return v; -} - -/* Страж комнаты (enter_guard, seg002:0112 + pos_guards, seg003). Возврат - * 0 = стража в комнате нет (tile >= 30). X оригинал пересчитывает из - * колонки (pos_guards), поэтому guards_x из файла не читаем. */ -/* ---- ЖИВОЕ состояние стражей (порт level.guards_*) ------------------ * - * В оригинале массивы стражей — часть структуры уровня в ОЗУ, и движок их - * ПЕРЕПИСЫВАЕТ: leave_guard при выходе из комнаты кладёт туда текущие - * позицию/направление/мастерство, а у МЁРТВОГО ещё и curr_seq. Благодаря - * этому, вернувшись, находишь труп там же, а не воскресшего стража. - * У нас уровень лежит в EMM-странице только на чтение, поэтому держим - * мутабельную копию в W2: 6 байт на комнату. */ -#define GS_TILE 0 -#define GS_DIR 1 -#define GS_X 2 -#define GS_SKILL 3 -#define GS_SEQL 4 -#define GS_SEQH 5 -static uint8_t gstate[24 * 6]; - -static void gstate_init(void) -{ - const uint8_t *p = (const uint8_t *)LVL_DATA_OFF; - uint8_t r; - gfx_w0_map(lvl_page); - for (r = 0; r < 24; r++) { - uint8_t *g = 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(); -} - /* leave_guard (seg002:02F5): запомнить состояние стража комнаты. seq * сохраняем ТОЛЬКО у мёртвого — у живого seq_hi = 0 означает «поднять * стандартной стойкой», как в оригинале. */ @@ -417,7 +104,7 @@ void pop_guard_state_save(uint8_t room, uint8_t tile, int8_t dir, uint8_t x, { uint8_t *g; if (room < 1 || room > 24) return; - g = gstate + (uint16_t)(room - 1) * 6; + g = pop_gstate + (uint16_t)(room - 1) * 6; g[GS_TILE] = tile; g[GS_DIR] = (uint8_t)dir; g[GS_X] = x; @@ -430,7 +117,7 @@ uint16_t pop_guard_state_seq(uint8_t room) { const uint8_t *g; if (room < 1 || room > 24) return 0; - g = gstate + (uint16_t)(room - 1) * 6; + g = pop_gstate + (uint16_t)(room - 1) * 6; if (g[GS_SEQH] == 0) return 0; /* живой — стандартная стойка */ return (uint16_t)(g[GS_SEQL] | ((uint16_t)g[GS_SEQH] << 8)); } @@ -440,43 +127,5 @@ uint16_t pop_guard_state_seq(uint8_t room) uint8_t pop_guard_state_x(uint8_t room) { if (room < 1 || room > 24) return 0; - return gstate[(uint16_t)(room - 1) * 6 + GS_X]; + return pop_gstate[(uint16_t)(room - 1) * 6 + GS_X]; } - -uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, - uint8_t *color, uint8_t *skill) -{ - const uint8_t *g; - uint8_t t; - if (!lvl_ok || room < 1 || room > 24) return 0; - /* Тайл/направление/мастерство — из ЖИВОЙ копии (её правит - * pop_guard_state_save); цвет статичен, его читаем со страницы. */ - g = 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(lvl_page); - *color = (uint8_t)(((const uint8_t *)LVL_DATA_OFF)[BP_GCOLOR + room - 1] & 0x0F); - gfx_w0_unmap(); - } - return (uint8_t)(t < 30); -} - -/* tbl_level_type (SDLPoP data.h:840). Держим ЗДЕСЬ, а не в слое фона: - * тип уровня нужен не только атласам — по нему расходятся ветки отрисовки - * (seg008), кладка стены (wall_pattern) и модификатор пустой клетки от - * упавшей плиты (remove_loose, seg007:0EB8). */ -static const uint8_t LEVEL_TYPE[16] = { - 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0 -}; - -uint8_t pop_level_type(void) -{ - return LEVEL_TYPE[pop_current_level & 15]; -} - -uint8_t pop_level_start_room(void) { return start_byte(BP_START + 0); } -uint8_t pop_level_start_pos(void) { return start_byte(BP_START + 1); } -int8_t pop_level_start_dir(void) { return (int8_t)start_byte(BP_START + 2); } diff --git a/applications/PoP/roomtest/pop_level.h b/applications/PoP/roomtest/pop_level.h index 3656adf..a58faa7 100644 --- a/applications/PoP/roomtest/pop_level.h +++ b/applications/PoP/roomtest/pop_level.h @@ -23,8 +23,8 @@ /* Загрузить уровень из файла в EMM-страницу. 0 — OK, -1 — ошибка * (open/alloc/размер). Звать ПОСЛЕ первого atlas_load (pop_bg_load) — * он снимает страницу ядра DSS для W0-unmap. */ -int pop_level_load(const char *path); -void pop_level_free(void); +/* pop_level_load — внутренняя для pop_level_cold.c (грузим по НОМЕРУ) */ +void pop_level_free(void) __banked; /* ---- Номер уровня и потабличные различия (levels_plan.md §1) -------- */ @@ -38,23 +38,23 @@ extern uint8_t pop_current_level; * pop_current_level. 0 — OK, -1 — файла нет / не влез (тогда * pop_current_level и страница НЕ тронуты — играем дальше на старом). * Порт связки load_lev_spr + load_level (seg000:1098/1169). */ -int pop_level_load_num(uint8_t n); +int pop_level_load_num(uint8_t n) __banked; /* tbl_entry_pose (data.h:848): 1 — падение внутрь (ур. 1), 2 — вбегание * (ур. 13), 0 — разворот на месте. Индекс — номер уровня. */ -uint8_t pop_level_entry_pose(uint8_t n); +uint8_t pop_level_entry_pose(uint8_t n) __banked; /* tbl_guard_hp (data.h:846) — база HP стража на уровне (get_guard_hp, * seg002:0044: extrastrength[skill] + tbl_guard_hp[level]). */ -uint8_t pop_level_guard_hp(uint8_t n); +uint8_t pop_level_guard_hp(uint8_t n) __banked; /* tbl_guard_type (data.h:844): 0 обычный, 1 толстый, 2 скелет, 3 визирь, * 4 тень, −1 — стражей на уровне НЕТ (уровни 14/15). */ -int8_t pop_level_guard_type(uint8_t n); +int8_t pop_level_guard_type(uint8_t n) __banked; /* tbl_level_type (data.h:840): 0 = dungeon, 1 = palace. Пока читается * только для контроля — второй тайлсет это levels_plan.md §3. */ -uint8_t pop_level_tileset(uint8_t n); +uint8_t pop_level_tileset(uint8_t n) __banked; /* Извлечь комнату room (1..24) в массивы приложения (W2): * fg[30], bg[30] — тайлы комнаты (fg маскирован &0x1F, bg raw); @@ -69,7 +69,7 @@ uint8_t pop_level_tileset(uint8_t n); * 0 — OK, -1 — уровень не загружен / room вне диапазона. */ 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); + uint8_t *rcol_fg, uint8_t *rcol_bg, uint8_t *below_fg) __banked; /* Связь комнаты по стороне (0=L,1=R,2=U,3=D) → номер соседней (1..24) или * 0, если стороны нет. Для переходов между комнатами (L2/L3). */ @@ -88,11 +88,11 @@ void pop_level_set_tile(uint8_t room, uint8_t tilepos, uint8_t tile); /* Рестарт уровня: вернуть ВСЕ тайлы в исходное (порт load_level, который * play_level зовёт на каждой итерации — в т.ч. после смерти Кида). */ -void pop_level_reset_tiles(void); +void pop_level_reset_tiles(void) __banked; /* Рестарт уровня: вернуть ВСЕХ стражей (тот же load_level + pos_guards). * Убитый страж после respawn снова жив — как в оригинале. */ -void pop_level_reset_guards(void); +void pop_level_reset_guards(void) __banked; /* Батч-доступ к странице уровня (один map/unmap окна 0 на много чтений). * Между begin/end — только pop_level_tile_raw, без рисования. */ @@ -115,7 +115,7 @@ void pop_doorlink2_set(uint8_t i, uint8_t v); /* Ряд приземления падающего loose-куска в колонке col комнаты room: первый * НЕ-пустой тайл сверху вниз; возвращает его ряд, если это floor (→ debris), * иначе -1 (пролетел/крушение о стену). */ -int8_t pop_room_col_landing(uint8_t room, uint8_t col); +int8_t pop_room_col_landing(uint8_t room, uint8_t col) __banked; /* Стартовая позиция уровня (блок @2112): комната / поза / направление. */ /* Страж комнаты: 1 = есть (заполнит tile/dir/color/skill), 0 = нет. @@ -133,17 +133,17 @@ uint16_t pop_guard_state_seq(uint8_t room); uint8_t pop_guard_state_x(uint8_t room); uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, - uint8_t *color, uint8_t *skill); + uint8_t *color, uint8_t *skill) __banked; /* tbl_level_type (SDLPoP data.h:840) — какой тайлсет у уровня: * 0 подземелье, 1 дворец. Дворцовые: 4, 5, 6, 10, 11, 14. Индекс — * НОМЕР уровня; элемент 0 — демо-уровень. Оригинал по нему выбирает и * .DAT окружения (tbl_envir_ki, seg000:1108), и десяток веток отрисовки * в seg008, и графику стража (GUARD1/GUARD2). */ -uint8_t pop_level_type(void); +uint8_t pop_level_type(void) __banked; -uint8_t pop_level_start_room(void); -uint8_t pop_level_start_pos(void); -int8_t pop_level_start_dir(void); +uint8_t pop_level_start_room(void) __banked; +uint8_t pop_level_start_pos(void) __banked; +int8_t pop_level_start_dir(void) __banked; #endif diff --git a/applications/PoP/roomtest/pop_level_cold.c b/applications/PoP/roomtest/pop_level_cold.c new file mode 100644 index 0000000..b7c31fa --- /dev/null +++ b/applications/PoP/roomtest/pop_level_cold.c @@ -0,0 +1,321 @@ +/* + * pop_level_cold.c — ХОЛОДНАЯ половина уровня (БАНК 8). Контракт и деление + * половин — в _pop_level.h. + * + * Здесь всё, что отрабатывает раз на уровень (чтение файла res200N.bin, + * рестарт тайлов/стражей, потабличные различия) или раз на комнату (разбор + * комнаты в рабочие массивы, посадочный ряд колонки, страж комнаты). Цена + * выноса — один трамплин на вызов; в кадре этих вызовов ноль. + * + * Формат — порт blueprnt DAT 1.0 (Table 6 POP-DAT). + */ +#include +#include +#include +#include +#include /* 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_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; +} + +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); + + /* Старую страницу отпускаем ТОЛЬКО после успешной загрузки новой: + * level_load_path сам аллоцирует страницу, и если файла нет — остаёмся + * на текущем уровне вместо падения в пустой уровень. */ + pop_lvl_ok = 0; /* чтобы level_load_path не мешался */ + if (level_load_path(pri) != 0 && level_load_path(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); } diff --git a/applications/PoP/roomtest/pop_redraw.c b/applications/PoP/roomtest/pop_redraw.c index ec9468b..6444cc8 100644 --- a/applications/PoP/roomtest/pop_redraw.c +++ b/applications/PoP/roomtest/pop_redraw.c @@ -19,7 +19,7 @@ static uint8_t rda_kind[NCOLS], rda_cnt[NCOLS]; * КАЖДЫЙ кадр впустую (замер 2026-07-27 на прежнем rest-цикле: ~12 % кадра). */ static uint8_t rd_pending; -void pop_redraw_reset(void) +void pop_redraw_reset(void) __banked { uint8_t i; for (i = 0; i < NTILES; i++) { rd_kind[i] = 0; rd_cnt[i] = 0; } @@ -27,7 +27,7 @@ void pop_redraw_reset(void) rd_pending = 0; } -void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) +void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) __banked { if (tilepos >= NTILES) return; if (!rd_cnt[tilepos]) rd_pending++; @@ -35,7 +35,7 @@ void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) if (pages > rd_cnt[tilepos]) rd_cnt[tilepos] = pages; } -void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) +void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) __banked { if (col >= NCOLS) return; if (!rda_cnt[col]) rd_pending++; @@ -47,7 +47,7 @@ void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) * бегущими счётчиками, а не через i/10 и i%10 — у Z80 нет деления, и SDCC * выносит __divuchar/__moduchar в вершину цикла как общее подвыражение * аргументов (30*2 делений за кадр даже когда рисовать нечего). */ -void pop_redraw_needed(void) +void pop_redraw_needed(void) __banked { uint8_t i, row = 0, col = 0, left = 0; if (!rd_pending) return; diff --git a/applications/PoP/roomtest/pop_redraw.h b/applications/PoP/roomtest/pop_redraw.h index 822f72b..c333139 100644 --- a/applications/PoP/roomtest/pop_redraw.h +++ b/applications/PoP/roomtest/pop_redraw.h @@ -39,14 +39,14 @@ /* Пометить тайл/колонку: kind — вид, pages — на скольких страницах * дабл-буфера перерисовать (1 = только текущая, 2 = обе). Повторная * пометка перекрывает вид и берёт максимум счётчика. */ -void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages); -void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages); +void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) __banked; +void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) __banked; /* Разобрать пометки: нарисовать всё помеченное на ТЕКУЩЕЙ (back) странице и * уменьшить счётчики. Звать из главного цикла в слое ФОНА — до персонажей. */ -void pop_redraw_needed(void); +void pop_redraw_needed(void) __banked; /* Сбросить все пометки (смена комнаты/уровня). */ -void pop_redraw_reset(void); +void pop_redraw_reset(void) __banked; #endif diff --git a/applications/PoP/roomtest/tests-host/stubs.c b/applications/PoP/roomtest/tests-host/stubs.c index 16da24f..41438f2 100644 --- a/applications/PoP/roomtest/tests-host/stubs.c +++ b/applications/PoP/roomtest/tests-host/stubs.c @@ -132,7 +132,7 @@ uint8_t tk_modif[30]; uint8_t tk_start_room; uint8_t *pop_trob_modif(uint8_t room) __banked { (void)room; return tk_modif; } -uint8_t pop_level_start_room(void) { return tk_start_room; } +uint8_t pop_level_start_room(void) __banked { return tk_start_room; } /* Журнал: что физика попросила у соседей. Тест сверяет ФАКТ и параметры * (какой тайл помечен, где заспавнен кусок) — заглушка-пустышка этого не @@ -170,10 +170,10 @@ void pop_trigger_button(uint8_t room, uint8_t tilepos, uint8_t button_type, uint8_t modifier) __banked { (void)modifier; log_put(TK_BUTTON, room, tilepos, button_type); } -void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) +void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) __banked { log_put(TK_REDRAW, tilepos, kind, pages); } -void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) +void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) __banked { log_put(TK_REDRAW_ABOVE, col, kind, pages); } void pop_loose_mob_spawn(int row, int col) __banked @@ -217,8 +217,8 @@ uint8_t tk_link_down; uint8_t pop_room_link(uint8_t room, uint8_t side) { (void)room; return side == 3 ? tk_link_down : 0; } -int8_t pop_level_guard_type(uint8_t level) { (void)level; return 0; } -uint8_t pop_level_guard_hp(uint8_t level) { (void)level; return 3; } +int8_t pop_level_guard_type(uint8_t level) __banked { (void)level; return 0; } +uint8_t pop_level_guard_hp(uint8_t level) __banked { (void)level; return 3; } /* Тайлы «уровня» для соседних комнат: pop_map_set_edges забирает fg соседей * слева/справа сам (кэш на входе в комнату), значит тесту нужен источник. @@ -263,19 +263,24 @@ void tk_guard_state_clear(void) tk_guard_room = 0; } +/* ВНИМАНИЕ на форму возврата: результат собирается в res и отдаётся ОДНИМ + * выходом. С `return 1;` прямо в ветке SDCC 4.5 не кладёт единицу в A — + * функция возвращает мусор (docs/bugs/sdcc-z80-ret-const-lost). Без + * __banked баг маскировался: в A случайно оставался ненулевой байт от + * `*tile = 11`, и проверка «истина» проходила. */ uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, - uint8_t *color, uint8_t *skill) + uint8_t *color, uint8_t *skill) __banked { + uint8_t res = 0; if (room < 1 || room > TK_ROOMS) return 0; if (tk_gs_tile[room - 1] < 30) { /* запомненный (в т.ч. труп) */ *tile = tk_gs_tile[room - 1]; *dir = 0; *color = 0; *skill = 1; - return 1; - } - if (room == tk_guard_room) { /* страж из данных уровня */ + res = 1; + } else if (room == tk_guard_room) { /* страж из данных уровня */ *tile = 11; *dir = 0; *color = 0; *skill = 1; - return 1; + res = 1; } - return 0; + return res; } void pop_guard_state_save(uint8_t room, uint8_t tile, int8_t dir, uint8_t x, uint8_t skill, uint16_t seq, uint8_t dead) diff --git a/docs/bugs/sdcc-z80-ret-const-lost/REPORT-ru.md b/docs/bugs/sdcc-z80-ret-const-lost/REPORT-ru.md new file mode 100644 index 0000000..3ff0319 --- /dev/null +++ b/docs/bugs/sdcc-z80-ret-const-lost/REPORT-ru.md @@ -0,0 +1,88 @@ +# SDCC 4.5 z80: `return <константа>` из ветки теряется — функция возвращает мусор + +**Компилятор:** SDCC 4.5.0 (z80), флаги `-mz80 --std-c99 --opt-code-size` +(воспроизводится и с `--no-std-crt0`, и в `__banked`, и без него). + +**Симптом.** У функции, возвращающей `uint8_t`, ветка с `return 1;` НЕ кладёт +1 в A. Вызывающий читает A (`__sdcccall(1)`: uint8 возвращается в A) и +получает то, что там осталось от предыдущей операции — то есть мусор. + +## Репро + +`repro.c` (полный текст рядом; собран в `repro.asm`): + +```c +uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, + uint8_t *color, uint8_t *skill) __banked +{ + if (room < 1 || room > TK_ROOMS) return 0; + if (tk_gs_tile[room - 1] < 30) { + *tile = tk_gs_tile[room - 1]; *dir = 0; *color = 0; *skill = 1; + return 1; /* <-- значение теряется */ + } + if (room == tk_guard_room) { + *tile = 11; *dir = 0; *color = 0; *skill = 1; + return 1; /* <-- и здесь */ + } + return 0; +} +``` + +Сгенерированный хвост второй ветки (`repro.asm`): + +``` +;repro.c:15: *tile = 11; *dir = 0; *color = 0; *skill = 1; + ld a, #0x0b + ld (de), a + xor a, a ; A := 0 (это *dir = 0) + ld (bc), a + pop hl + ld (hl), #0x00 +;repro.c:16: return 1; + ex de,hl + pop hl + push hl + push de + ld (hl), #0x01 ; это *skill = 1, НЕ возврат + jr 00108$ +00107$: + xor a, a +00108$: + ld sp, ix + pop ix + ret ; A = 0 — вернулась ЛОЖЬ вместо 1 +``` + +Кода `ld a, #0x01` для самого `return 1` нет ни в одной из двух веток. + +## Когда прячется, когда стреляет + +Баг присутствует и БЕЗ `__banked`, но там часто маскируется: если последним +в ветке A случайно оказался ненулевой байт (например `ld a,#0x0b` для +`*tile = 11`), вызывающий с проверкой «истина/ложь» получает правильный +ответ. `__banked` меняет распределение регистров (указатели уезжают в +IX-кадр, `*dir = 0` компилируется в `xor a,a`), A обнуляется — и та же +функция начинает возвращать 0 вместо 1. + +Именно так это и вылезло у нас: набор `tests-host/t_char` годами был зелёным +и покраснел ровно в тот момент, когда стаб пометили `__banked` — хотя код +стаба не менялся. + +## Обход + +Один выход и явная переменная результата (`workaround.c` / `workaround.asm`): + +```c +uint8_t res = 0; +... + res = 1; +... +return res; /* -> ld a, -5 (ix) — корректно */ +``` + +## Признак для аудита + +Грепать функции, у которых `return <константа>` стоит в ветке, где +последней операцией была запись по указателю или `xor a,a`. Проверять по +сгенерированному `.asm`: перед `jr` на общий выход обязан быть `ld a,#…` +(или загрузка результата), иначе возврат — мусор. diff --git a/docs/bugs/sdcc-z80-ret-const-lost/repro.asm b/docs/bugs/sdcc-z80-ret-const-lost/repro.asm new file mode 100644 index 0000000..7c5091d --- /dev/null +++ b/docs/bugs/sdcc-z80-ret-const-lost/repro.asm @@ -0,0 +1,140 @@ +;-------------------------------------------------------- +; File Created by SDCC : free open source ISO C Compiler +; Version 4.5.0 #15242 (Mac OS X x86_64) +;-------------------------------------------------------- + .module bt2 + + .optsdcc -mz80 sdcccall(1) +;-------------------------------------------------------- +; Public variables in this module +;-------------------------------------------------------- + .globl b_pop_level_guard + .globl _pop_level_guard + .globl _tk_gs_tile + .globl _tk_guard_room +;-------------------------------------------------------- +; special function registers +;-------------------------------------------------------- +;-------------------------------------------------------- +; ram data +;-------------------------------------------------------- + .area _DATA +_tk_guard_room:: + .ds 1 +_tk_gs_tile:: + .ds 8 +;-------------------------------------------------------- +; ram data +;-------------------------------------------------------- + .area _INITIALIZED +;-------------------------------------------------------- +; absolute external ram data +;-------------------------------------------------------- + .area _DABS (ABS) +;-------------------------------------------------------- +; global & static initialisations +;-------------------------------------------------------- + .area _HOME + .area _GSINIT + .area _GSFINAL + .area _GSINIT +;-------------------------------------------------------- +; Home +;-------------------------------------------------------- + .area _HOME + .area _HOME +;-------------------------------------------------------- +; code +;-------------------------------------------------------- + .area _CODE +;bt2.c:6: uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, +; --------------------------------- +; Function pop_level_guard +; --------------------------------- + b_pop_level_guard = 0 +_pop_level_guard:: + call ___sdcc_enter_ix + push af + push af +;bt2.c:9: if (room < 1 || room > TK_ROOMS) return 0; + ld a, 7 (ix) + sub a, #0x01 + jr C, 00101$ + ld a, #0x08 + sub a, 7 (ix) + jr NC, 00102$ +00101$: + xor a, a + jr 00108$ +00102$: +;bt2.c:10: if (tk_gs_tile[room - 1] < 30) { + ld bc, #_tk_gs_tile+0 + ld a, 7 (ix) + dec a + ld l, a + rlca + sbc a, a + ld h, a + add hl, bc + ld l, (hl) +;bt2.c:11: *tile = tk_gs_tile[room - 1]; *dir = 0; *color = 0; *skill = 1; + ld e, 8 (ix) + ld d, 9 (ix) + ld c, 10 (ix) + ld b, 11 (ix) + ld a, 12 (ix) + ld -4 (ix), a + ld a, 13 (ix) + ld -3 (ix), a + ld a, 14 (ix) + ld -2 (ix), a + ld a, 15 (ix) + ld -1 (ix), a +;bt2.c:10: if (tk_gs_tile[room - 1] < 30) { +;bt2.c:11: *tile = tk_gs_tile[room - 1]; *dir = 0; *color = 0; *skill = 1; + ld a,l + cp a,#0x1e + jr NC, 00105$ + ld (de), a + xor a, a + ld (bc), a + pop hl + ld (hl), #0x00 +;bt2.c:12: return 1; + ex de,hl + pop hl + push hl + push de + ld (hl), #0x01 + jr 00108$ +00105$: +;bt2.c:14: if (room == tk_guard_room) { + ld a, 7 (ix) + ld hl, #_tk_guard_room + sub a, (hl) + jr NZ, 00107$ +;bt2.c:15: *tile = 11; *dir = 0; *color = 0; *skill = 1; + ld a, #0x0b + ld (de), a + xor a, a + ld (bc), a + pop hl + ld (hl), #0x00 +;bt2.c:16: return 1; + ex de,hl + pop hl + push hl + push de + ld (hl), #0x01 + jr 00108$ +00107$: +;bt2.c:18: return 0; + xor a, a +00108$: +;bt2.c:19: } + ld sp, ix + pop ix + ret + .area _CODE + .area _INITIALIZER + .area _CABS (ABS) diff --git a/docs/bugs/sdcc-z80-ret-const-lost/repro.c b/docs/bugs/sdcc-z80-ret-const-lost/repro.c new file mode 100644 index 0000000..5e12618 --- /dev/null +++ b/docs/bugs/sdcc-z80-ret-const-lost/repro.c @@ -0,0 +1,19 @@ +#include +#define TK_ROOMS 8 +uint8_t tk_guard_room; +uint8_t tk_gs_tile[TK_ROOMS]; + +uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, + uint8_t *color, uint8_t *skill) __banked +{ + if (room < 1 || room > TK_ROOMS) return 0; + if (tk_gs_tile[room - 1] < 30) { + *tile = tk_gs_tile[room - 1]; *dir = 0; *color = 0; *skill = 1; + return 1; + } + if (room == tk_guard_room) { + *tile = 11; *dir = 0; *color = 0; *skill = 1; + return 1; + } + return 0; +} diff --git a/docs/bugs/sdcc-z80-ret-const-lost/workaround.asm b/docs/bugs/sdcc-z80-ret-const-lost/workaround.asm new file mode 100644 index 0000000..4ac8c9d --- /dev/null +++ b/docs/bugs/sdcc-z80-ret-const-lost/workaround.asm @@ -0,0 +1,142 @@ +;-------------------------------------------------------- +; File Created by SDCC : free open source ISO C Compiler +; Version 4.5.0 #15242 (Mac OS X x86_64) +;-------------------------------------------------------- + .module bt4 + + .optsdcc -mz80 sdcccall(1) +;-------------------------------------------------------- +; Public variables in this module +;-------------------------------------------------------- + .globl b_pop_level_guard + .globl _pop_level_guard + .globl _tk_gs_tile + .globl _tk_guard_room +;-------------------------------------------------------- +; special function registers +;-------------------------------------------------------- +;-------------------------------------------------------- +; ram data +;-------------------------------------------------------- + .area _DATA +_tk_guard_room:: + .ds 1 +_tk_gs_tile:: + .ds 8 +;-------------------------------------------------------- +; ram data +;-------------------------------------------------------- + .area _INITIALIZED +;-------------------------------------------------------- +; absolute external ram data +;-------------------------------------------------------- + .area _DABS (ABS) +;-------------------------------------------------------- +; global & static initialisations +;-------------------------------------------------------- + .area _HOME + .area _GSINIT + .area _GSFINAL + .area _GSINIT +;-------------------------------------------------------- +; Home +;-------------------------------------------------------- + .area _HOME + .area _HOME +;-------------------------------------------------------- +; code +;-------------------------------------------------------- + .area _CODE +;bt4.c:6: uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, +; --------------------------------- +; Function pop_level_guard +; --------------------------------- + b_pop_level_guard = 0 +_pop_level_guard:: + call ___sdcc_enter_ix + ld hl, #-5 + add hl, sp + ld sp, hl +;bt4.c:9: uint8_t res = 0; + ld -5 (ix), #0x00 +;bt4.c:10: if (room < 1 || room > TK_ROOMS) return 0; + ld a, 7 (ix) + sub a, #0x01 + jr C, 00101$ + ld a, #0x08 + sub a, 7 (ix) + jr NC, 00102$ +00101$: + xor a, a + jr 00109$ +00102$: +;bt4.c:11: if (tk_gs_tile[room - 1] < 30) { + ld bc, #_tk_gs_tile+0 + ld a, 7 (ix) + dec a + ld l, a + rlca + sbc a, a + ld h, a + add hl, bc + ld l, (hl) +;bt4.c:12: *tile = tk_gs_tile[room - 1]; *dir = 0; *color = 0; *skill = 1; + ld c, 8 (ix) + ld b, 9 (ix) + ld e, 10 (ix) + ld d, 11 (ix) + ld a, 12 (ix) + ld -4 (ix), a + ld a, 13 (ix) + ld -3 (ix), a + ld a, 14 (ix) + ld -2 (ix), a + ld a, 15 (ix) + ld -1 (ix), a +;bt4.c:11: if (tk_gs_tile[room - 1] < 30) { +;bt4.c:12: *tile = tk_gs_tile[room - 1]; *dir = 0; *color = 0; *skill = 1; + ld a,l + cp a,#0x1e + jr NC, 00107$ + ld (bc), a + xor a, a + ld (de), a + ld l, -4 (ix) + ld h, -3 (ix) + ld (hl), #0x00 + ld l, -2 (ix) + ld h, -1 (ix) + ld (hl), #0x01 +;bt4.c:13: res = 1; + ld -5 (ix), #0x01 + jr 00108$ +00107$: +;bt4.c:14: } else if (room == tk_guard_room) { + ld a, 7 (ix) + ld hl, #_tk_guard_room + sub a, (hl) + jr NZ, 00108$ +;bt4.c:15: *tile = 11; *dir = 0; *color = 0; *skill = 1; + ld a, #0x0b + ld (bc), a + xor a, a + ld (de), a + ld l, -4 (ix) + ld h, -3 (ix) + ld (hl), #0x00 + ld l, -2 (ix) + ld h, -1 (ix) + ld (hl), #0x01 +;bt4.c:16: res = 1; + ld -5 (ix), #0x01 +00108$: +;bt4.c:18: return res; + ld a, -5 (ix) +00109$: +;bt4.c:19: } + ld sp, ix + pop ix + ret + .area _CODE + .area _INITIALIZER + .area _CABS (ABS) diff --git a/docs/bugs/sdcc-z80-ret-const-lost/workaround.c b/docs/bugs/sdcc-z80-ret-const-lost/workaround.c new file mode 100644 index 0000000..389fead --- /dev/null +++ b/docs/bugs/sdcc-z80-ret-const-lost/workaround.c @@ -0,0 +1,19 @@ +#include +#define TK_ROOMS 8 +uint8_t tk_guard_room; +uint8_t tk_gs_tile[TK_ROOMS]; + +uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir, + uint8_t *color, uint8_t *skill) __banked +{ + uint8_t res = 0; + if (room < 1 || room > TK_ROOMS) return 0; + if (tk_gs_tile[room - 1] < 30) { + *tile = tk_gs_tile[room - 1]; *dir = 0; *color = 0; *skill = 1; + res = 1; + } else if (room == tk_guard_room) { + *tile = 11; *dir = 0; *color = 0; *skill = 1; + res = 1; + } + return res; +}