QuickSave F6/F9 в roomtest; bank_load_file/bank_save_file/gfx_w0_page_prepare; sprinter-cc: авто n_banks
roomtest: - QuickSave/QuickLoad (F6/F9): снапшот 'POPQ' v3 в POP.SAV/POP.BAK на HDD, транзакционная запись (POP.NEW -> rename, откат при ошибке), XOR-контрольная сумма payload'а; сериализация всех игровых переменных через W0-примитивы pop_qs_*; pop_qsave_process() на границе кадра вне Char-окон - pop_qsave_restore_room(): полная перезагрузка комнаты после загрузки (карта/края/швы, сброс bake-кэша, перерисовка обеих страниц, инвалидация кэшей спрайтов и HP) - сериализаторы в pop_map/pop_loose_mob/pop_trob/pop_guard_ai (+ восстановление инвариантов: mobs_live, trob_drawn, redraw) - immortal-чит 2 уровня: уровень 2 поглощает только малый урон Kid'а libc/libbgi: - bank_load_file()/bank_save_file() — резидентное файловое I/O в банк, без правила W3 (путь читается до переключения страницы) - gfx_w0_page_prepare(page) — подготовка W0-окна (IRQ/NMI-стабы) одной функцией; atlas_load.c и roomtest переведены на новые примитивы; ручные ISR-стабы удалены sprinter-cc / сборка: - --bank N=FILE.c: автогенерация n_banks (_n_banks_auto.c), ручные const n_banks удалены из тестов - roomtest/app.mk: ресурсы через stamp-файлы (.resource-stamps/) — один запуск упаковщика на группу вместо N под -B; HDD_PACK_ARGS
This commit is contained in:
@@ -35,7 +35,32 @@ 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 4=pop_kdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 5=pop_tile_cold.c --bank 6=pop_trob.c --bank 7=pop_redraw.c --bank 8=roomtest_cold.c --bank 8=pop_level_cold.c --bank 8=pop_kboot.c --bank 8=pop_guard_cold.c --bank 8=pop_shadow.c --bank 8=pop_sfx_cold.c $(PROF_FLAGS)
|
||||
# Единственный список банковых исходников: из него строятся И ключи
|
||||
# sprinter-cc, И зависимости roomtest.exe. Раньше эти списки жили отдельно,
|
||||
# поэтому добавленные pop_shadow.c/pop_sfx_cold.c компилировались, но make не
|
||||
# замечал их последующие изменения и оставлял устаревший exe.
|
||||
BANK1_SRCS := guards.c
|
||||
BANK2_SRCS := pop_bg.c
|
||||
BANK3_SRCS := pop_map.c
|
||||
BANK4_SRCS := pop_cdraw.c pop_kdraw.c
|
||||
BANK5_SRCS := pop_ctrl.c pop_tile_cold.c
|
||||
BANK6_SRCS := pop_trob.c
|
||||
BANK7_SRCS := pop_room.c pop_redraw.c
|
||||
BANK8_SRCS := roomtest_cold.c pop_level_cold.c pop_kboot.c pop_guard_cold.c \
|
||||
pop_shadow.c pop_sfx_cold.c pop_qsave.c pop_qsave_coldio.c \
|
||||
pop_level_qsave.c
|
||||
BANKED_SRCS := $(BANK1_SRCS) $(BANK2_SRCS) $(BANK3_SRCS) $(BANK4_SRCS) \
|
||||
$(BANK5_SRCS) $(BANK6_SRCS) $(BANK7_SRCS) $(BANK8_SRCS)
|
||||
BANK_FLAGS := $(foreach f,$(BANK1_SRCS),--bank 1=$(f)) \
|
||||
$(foreach f,$(BANK2_SRCS),--bank 2=$(f)) \
|
||||
$(foreach f,$(BANK3_SRCS),--bank 3=$(f)) \
|
||||
$(foreach f,$(BANK4_SRCS),--bank 4=$(f)) \
|
||||
$(foreach f,$(BANK5_SRCS),--bank 5=$(f)) \
|
||||
$(foreach f,$(BANK6_SRCS),--bank 6=$(f)) \
|
||||
$(foreach f,$(BANK7_SRCS),--bank 7=$(f)) \
|
||||
$(foreach f,$(BANK8_SRCS),--bank 8=$(f))
|
||||
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) \
|
||||
$(BANK_FLAGS) $(PROF_FLAGS)
|
||||
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||
@@ -57,7 +82,7 @@ POS ?= 2
|
||||
ifneq ($(strip $(ROOM)),)
|
||||
PROF_FLAGS += -DDBG_START_ROOM=$(ROOM) -DDBG_START_POS=$(POS)
|
||||
endif
|
||||
EXTRA_SRCS := pop_vflip.c pop_pace.c pop_sfx.c pop_state.c pop_draw.c pop_tile.c pop_kid.c pop_level.c pop_geom.c pop_guard.c
|
||||
EXTRA_SRCS := pop_vflip.c pop_pace.c pop_sfx.c pop_state.c pop_draw.c pop_tile.c pop_kid.c pop_level.c pop_geom.c pop_guard.c pop_qsave_io.c
|
||||
|
||||
BG_DIR := $(CURDIR)/../poc/res/bg
|
||||
KID_DIR := $(CURDIR)/../poc/res/kid
|
||||
@@ -127,47 +152,121 @@ SND_DATA := $(foreach n,0 1 2 3 4 5 6 7,$(SND_DIR)/s$(n).bin)
|
||||
|
||||
EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(SKEL_DATA) \
|
||||
$(VIZIER_DATA) $(SHADOW_DATA) $(SND_DATA) $(LVL_DATA)
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
|
||||
# Образ HDD для MAME: ресурсы раскладываются ПО КАТАЛОГАМ (8.3, как в DSS),
|
||||
# чтобы корень диска не зарастал десятками .atl.
|
||||
# BG\ — фон (env/wall/fore/pot)
|
||||
# KID\ — персонаж (kid0..27, палитра, меч, таблицы анимации)
|
||||
# GUARD\ — стражи (появятся здесь же)
|
||||
# LEVELS\ — уровни
|
||||
hdd: $(EXAMPLE).exe
|
||||
$(PROJ_ROOT)/toolchain/make_hdd.sh $(PROJ_ROOT)/mame/v306/IMG/test_hdd.chd \
|
||||
$(EXAMPLE).exe \
|
||||
$(foreach f,$(BG_DATA),BG:$(f)) \
|
||||
$(foreach f,$(KID_DATA) $(KID_BIN),KID:$(f)) \
|
||||
$(foreach f,$(GUARD_DATA),GUARD:$(f)) \
|
||||
$(foreach f,$(SKEL_DATA),SKEL:$(f)) \
|
||||
$(foreach f,$(VIZIER_DATA),VIZIER:$(f)) \
|
||||
$(foreach f,$(SHADOW_DATA),SHADOW:$(f)) \
|
||||
$(foreach f,$(SND_DATA),SND:$(f)) \
|
||||
$(foreach f,$(LVL_DATA),LEVELS:$(f))
|
||||
HDD_PACK_ARGS := $(EXAMPLE).exe \
|
||||
$(foreach f,$(BG_DATA),BG:$(f)) \
|
||||
$(foreach f,$(KID_DATA) $(KID_BIN),KID:$(f)) \
|
||||
$(foreach f,$(GUARD_DATA),GUARD:$(f)) \
|
||||
$(foreach f,$(SKEL_DATA),SKEL:$(f)) \
|
||||
$(foreach f,$(VIZIER_DATA),VIZIER:$(f)) \
|
||||
$(foreach f,$(SHADOW_DATA),SHADOW:$(f)) \
|
||||
$(foreach f,$(SND_DATA),SND:$(f)) \
|
||||
$(foreach f,$(LVL_DATA),LEVELS:$(f))
|
||||
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
|
||||
TC := $(PROJ_ROOT)/applications/PoP/toolchain
|
||||
$(BG_DATA): $(TC)/pop_pack_bg.py $(TC)/render_room.py
|
||||
|
||||
# Упаковщики создают сразу ГРУППУ файлов. Обычное multi-output правило
|
||||
# заставляло GNU make при `-B` запускать один и тот же упаковщик отдельно для
|
||||
# каждого .atl/.bin. Stamp-файл представляет один запуск генератора; все
|
||||
# созданные им файлы зависят от этого stamp без собственных рецептов.
|
||||
RESOURCE_STAMP_DIR := .resource-stamps
|
||||
BG_STAMP := $(RESOURCE_STAMP_DIR)/bg
|
||||
GUARD_STAMP := $(RESOURCE_STAMP_DIR)/guard
|
||||
SKEL_STAMP := $(RESOURCE_STAMP_DIR)/skel
|
||||
VIZIER_STAMP := $(RESOURCE_STAMP_DIR)/vizier
|
||||
SHADOW_STAMP := $(RESOURCE_STAMP_DIR)/shadow
|
||||
SOUND_STAMP := $(RESOURCE_STAMP_DIR)/sound
|
||||
KID_STAMP := $(RESOURCE_STAMP_DIR)/kid
|
||||
KID_BIN_STAMP := $(RESOURCE_STAMP_DIR)/kid-bin
|
||||
|
||||
$(RESOURCE_STAMP_DIR):
|
||||
mkdir -p $@
|
||||
|
||||
$(BG_STAMP): $(TC)/pop_pack_bg.py $(TC)/render_room.py | $(RESOURCE_STAMP_DIR)
|
||||
cd $(TC) && python3 pop_pack_bg.py dungeon && python3 pop_pack_bg.py palace
|
||||
$(GUARD_DATA): $(TC)/pop_pack_guard.py
|
||||
touch $@
|
||||
$(BG_DATA): $(BG_STAMP)
|
||||
@test -f $@ || { $(MAKE) -B $(BG_STAMP); test -f $@; }
|
||||
|
||||
$(GUARD_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
|
||||
cd $(TC) && python3 pop_pack_guard.py
|
||||
touch $@
|
||||
$(GUARD_DATA): $(GUARD_STAMP)
|
||||
@test -f $@ || { $(MAKE) -B $(GUARD_STAMP); test -f $@; }
|
||||
|
||||
$(SKEL_DATA): $(TC)/pop_pack_guard.py
|
||||
$(SKEL_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
|
||||
cd $(TC) && python3 pop_pack_guard.py SKEL
|
||||
touch $@
|
||||
$(SKEL_DATA): $(SKEL_STAMP)
|
||||
@test -f $@ || { $(MAKE) -B $(SKEL_STAMP); test -f $@; }
|
||||
|
||||
$(VIZIER_DATA): $(TC)/pop_pack_guard.py
|
||||
$(VIZIER_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
|
||||
cd $(TC) && python3 pop_pack_guard.py VIZIER
|
||||
touch $@
|
||||
$(VIZIER_DATA): $(VIZIER_STAMP)
|
||||
@test -f $@ || { $(MAKE) -B $(VIZIER_STAMP); test -f $@; }
|
||||
|
||||
$(SHADOW_DATA) pop_shadow_atlas.h: $(TC)/pop_pack_shadow.py $(TC)/pop_pack_bg.py $(TC)/pop_pack_kid.py
|
||||
$(SHADOW_STAMP): $(TC)/pop_pack_shadow.py $(TC)/pop_pack_bg.py $(TC)/pop_pack_kid.py | $(RESOURCE_STAMP_DIR)
|
||||
cd $(TC) && python3 pop_pack_shadow.py
|
||||
touch $@
|
||||
$(SHADOW_DATA) pop_shadow_atlas.h: $(SHADOW_STAMP)
|
||||
@test -f $@ || { $(MAKE) -B $(SHADOW_STAMP); test -f $@; }
|
||||
|
||||
$(SND_DATA) pop_sound_tbl.h: $(TC)/pop_pack_sound.py
|
||||
$(SOUND_STAMP): $(TC)/pop_pack_sound.py | $(RESOURCE_STAMP_DIR)
|
||||
cd $(TC) && python3 pop_pack_sound.py
|
||||
touch $@
|
||||
$(SND_DATA) pop_sound_tbl.h: $(SOUND_STAMP)
|
||||
@test -f $@ || { $(MAKE) -B $(SOUND_STAMP); test -f $@; }
|
||||
|
||||
$(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
|
||||
$(KID_STAMP): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py | $(RESOURCE_STAMP_DIR)
|
||||
cd $(TC) && python3 pop_pack_kid.py
|
||||
kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
|
||||
cd $(TC) && python3 pop_extract_kid_data.py
|
||||
touch $@
|
||||
$(KID_DATA): $(KID_STAMP)
|
||||
@test -f $@ || { $(MAKE) -B $(KID_STAMP); test -f $@; }
|
||||
|
||||
$(EXAMPLE).exe: roomtest_cold.c roomtest_cold.h guards.c pop_guard.c pop_guard_cold.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_vflip.c pop_vflip.h pop_kid.c pop_kdraw.c pop_kboot.c _pop_kid.h _pop_kdraw.h 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)
|
||||
$(KID_BIN_STAMP): $(TC)/pop_extract_kid_data.py | $(RESOURCE_STAMP_DIR)
|
||||
cd $(TC) && python3 pop_extract_kid_data.py
|
||||
touch $@
|
||||
kid_data.h $(KID_BIN): $(KID_BIN_STAMP)
|
||||
@test -f $@ || { $(MAKE) -B $(KID_BIN_STAMP); test -f $@; }
|
||||
|
||||
# Явные цели позволяют собирать ресурсы без `make -B`, который по смыслу
|
||||
# принудительно пересобирает также libc, libbgi и само приложение.
|
||||
resources-bg: $(BG_DATA)
|
||||
resources-kid: $(KID_DATA) $(KID_BIN)
|
||||
resources-actors: $(GUARD_DATA) $(SKEL_DATA) $(VIZIER_DATA) $(SHADOW_DATA)
|
||||
resources-sound: $(SND_DATA)
|
||||
resources-levels: $(LVL_DATA)
|
||||
resources: resources-bg resources-kid resources-actors resources-sound resources-levels
|
||||
resources-rebuild:
|
||||
$(MAKE) -B resources
|
||||
|
||||
# Принудительная перелинковка только roomtest, без глобального `-B` и без
|
||||
# принудительной пересборки библиотек/ресурсов.
|
||||
relink: resources
|
||||
$(MAKE) -W Makefile $(EXAMPLE).exe
|
||||
|
||||
# HDD_PACK_ARGS содержит все эти файлы; make обязан подготовить их до запуска
|
||||
# упаковщика и при чистой сборке, а не полагаться на старое содержимое дерева.
|
||||
hdd: resources
|
||||
|
||||
# sprinter-cc собирает все модули одним вызовом и depfile пока не выдаёт.
|
||||
# Банковые .c берём из того же списка, что формирует --bank; любой локальный
|
||||
# заголовок также обязан перелинковать приложение. wildcard намеренно
|
||||
# широкий: цена лишней полной сборки ниже риска получить stale-бинарник.
|
||||
LOCAL_HEADERS := $(wildcard *.h)
|
||||
$(EXAMPLE).exe: Makefile $(PROJ_ROOT)/app.mk $(BANKED_SRCS) $(LOCAL_HEADERS) \
|
||||
$(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)
|
||||
|
||||
clean-resources:
|
||||
rm -rf $(RESOURCE_STAMP_DIR)
|
||||
|
||||
.PHONY: resources resources-rebuild resources-bg resources-kid resources-actors relink \
|
||||
resources-sound resources-levels clean-resources
|
||||
|
||||
@@ -20,10 +20,7 @@ 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. */
|
||||
/* Файловый обмен с W3 делает общий резидентный bank_load_file(), поэтому
|
||||
* оркестратор загрузки kid_data.bin целиком живёт в холодном банке 8. */
|
||||
|
||||
#endif /* _POP_KID_H */
|
||||
|
||||
@@ -69,13 +69,9 @@ 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);
|
||||
/* ПРАВИЛО ОКНА W3. Банковый модуль исполняется ИЗ W3 и сам менять окно
|
||||
* не может. Файлы в EMM читает резидентный bank_load_file(), после его
|
||||
* возврата холодный код продолжает подготовку страницы через W0. */
|
||||
|
||||
/* Живая копия стражей из данных уровня (загрузка/рестарт) — в холодной. */
|
||||
void pop_gstate_init(void) __banked;
|
||||
|
||||
@@ -19,8 +19,4 @@ extern uint8_t pop_snd_ok;
|
||||
* cbl_open() холодной половиной. */
|
||||
int pop_sfx_fill(uint16_t n);
|
||||
|
||||
/* Прочитать файл целиком в ФИЗИЧЕСКУЮ страницу (через W3). Резидентный:
|
||||
* банковый код, переключив W3, вырезал бы сам себя. */
|
||||
int pop_page_read(const char *path, uint8_t phys, uint16_t want);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -265,6 +265,12 @@ void pop_check_mouse(void) __banked
|
||||
static uint8_t demo_time; /* кадров с начала записи (0xFE = стоп) */
|
||||
static uint8_t demo_index; /* текущая запись */
|
||||
|
||||
void pop_guard_ai_qsave(pop_qs_io_t *io) __banked
|
||||
{
|
||||
pop_qs_u8(io, &demo_time);
|
||||
pop_qs_u8(io, &demo_index);
|
||||
}
|
||||
|
||||
#define SHADOW_SKILL 3
|
||||
#define SHADOW_HP 4
|
||||
#define SEQ_2_STAND 2
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Уровень 4, комната 22 - страж идет навстречу Киду и попадает сам в челюсти - должен быть инстинк самосохранения
|
||||
|
||||
Перед решеткой Кид делает лишний мелкий шаг.
|
||||
|
||||
Уровень 5, комната 2 - с разбега чуть раньше - иногда попадаем в стену (некорректное отображение падения)
|
||||
|
||||
Уровень 7, комната 9 - падает плита -1,1 - звук потери HP у Кида (причем раз даже когда Кид просто бъет по плите стоя на 0,0)
|
||||
|
||||
Уровень 10, комната 1 - когда Кид стоя на кнопке 1,8 роняет плиту 0,8 на кнопку 1,8 - кнопка ломается (превращается в щебень)
|
||||
но двери которые она должна открыть остаются закрытыми
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include "pop_char.h" /* pop_char_t — общий проход отрисовки поверх Char */
|
||||
#include "pop_qsave.h"
|
||||
|
||||
/* ---- Вертикальный layout (PoP-экран 320×200 в нашем 320×256) --------- *
|
||||
* Игровой экран 200px центрируем по высоте: сдвиг всего рисунка вниз на
|
||||
@@ -178,6 +179,7 @@ void pop_loose_mob_spawn(int row, int col) __banked;
|
||||
void pop_loose_mob_spawn_at(uint8_t room, int row, int col) __banked;
|
||||
void pop_loose_mob_tick(void) __banked;
|
||||
void pop_loose_mob_reset(void) __banked; /* убрать все куски (СТАРТ УРОВНЯ, mobs_count=0) */
|
||||
void pop_loose_mob_qsave(pop_qs_io_t *io) __banked;
|
||||
|
||||
/* Забыть, на какой странице сделана первая запечка тайлов (см. bake_copy в
|
||||
* pop_room.c). Звать при смене комнаты/уровня: номера тайлов повторяются, и
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "kid_data.h" /* kframe */
|
||||
#include "pop_char.h"
|
||||
#include "pop_geom.h" /* pop_rnd_t — сид бросков боёвки */
|
||||
#include "pop_qsave.h"
|
||||
|
||||
/* seqids (types.h:1052+) — последовательности боя и стойки. */
|
||||
#define SEQ_55_DRAW_SWORD 55
|
||||
@@ -303,6 +304,7 @@ void pop_check_sword_hurt(void) __banked;
|
||||
/* Текущий кадр стража (порт cur_frame для Char): заполняет логика в W1/W2,
|
||||
* читает отрисовка в резиденте W3. image == 255 — рисовать нечего. */
|
||||
extern kframe pop_gframe;
|
||||
void pop_guard_ai_qsave(pop_qs_io_t *io) __banked;
|
||||
void pop_guard_load_frame(void);
|
||||
|
||||
/* check_guard_fallout (seg002:0241): убрать персонажа слота Guard, если он
|
||||
|
||||
@@ -17,11 +17,6 @@
|
||||
#include "pop_vflip.h"
|
||||
#include "pop_guard.h" /* pop_guard_vflip_load — зеркала соперника */
|
||||
|
||||
/* ISR-стаб W0-страницы (тот же приём, что atlas_load): при прерывании с
|
||||
* замапленной в W0 страницей CPU прыгает на 0x0038 → там обязан быть JP на
|
||||
* _gfx_w0_isr (восстановит окно). */
|
||||
extern void _gfx_w0_isr(void);
|
||||
|
||||
#define KID_MAXPAGES 28
|
||||
|
||||
int pop_kid_load(uint8_t npages) __banked
|
||||
@@ -50,9 +45,21 @@ void pop_kid_free(void) __banked
|
||||
kid_npages = 0;
|
||||
}
|
||||
|
||||
/* ЗАГРУЗКА kid_data.bin (pop_kid_data_load) осталась в РЕЗИДЕНТЕ: она
|
||||
* маппит страницу в W3 на время чтения, а этот модуль сам в W3 — см.
|
||||
* то же правило в _pop_kid.h. */
|
||||
int pop_kid_data_load(const char *path) __banked
|
||||
{
|
||||
uint8_t blk, page;
|
||||
int n;
|
||||
blk=mem_alloc_pages(1);
|
||||
if (!blk) return -1;
|
||||
page=mem_get_page(blk,0);
|
||||
n=bank_load_file(page,KD_DATA_OFF,path,16384-KD_DATA_OFF);
|
||||
if (n < (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) {
|
||||
mem_free_block(blk); return -1;
|
||||
}
|
||||
gfx_w0_page_prepare(page);
|
||||
kdat_blk=blk; kdat_page=page; kdat_ok=1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pop_kid_data_free(void) __banked
|
||||
{
|
||||
|
||||
@@ -61,8 +61,6 @@ static const uint16_t kid_seq_off[KID_NSEQ] = KID_SEQ_OFF_INIT;
|
||||
* Это ровно устройство оригинала: load_frame (seg006) заполняет cur_frame,
|
||||
* и коллизия/отрисовка работают с ним, а не с таблицей.
|
||||
* Страница адресуется как у pop_level: данные с 0x100, ниже — ISR-стаб. */
|
||||
extern void _gfx_w0_isr(void);
|
||||
|
||||
/* Адрес кадра i в замапленной странице. Считаем В uint16_t и кастуем ОДИН
|
||||
* раз: запись `(const uint8_t *)CONST + (uint16_t)i * 5u` SDCC 4.5 собирает
|
||||
* НЕВЕРНО — умножение делает в 16 битах (add hl,hl / add hl,bc), а потом
|
||||
@@ -449,35 +447,3 @@ void kid_tick(void)
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
int pop_kid_data_load(const char *path) __banked;
|
||||
|
||||
/* Прочитать кадр из таблицы страницы данных: tbl_off — KID_BIN_FRAMES_OFF
|
||||
* (Kid) или KID_BIN_GFRAMES_OFF (страж, своя таблица frame_tbl_guard).
|
||||
|
||||
@@ -17,10 +17,6 @@
|
||||
#include <sprinter_mem.h>
|
||||
#include <sprite.h> /* 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"
|
||||
|
||||
@@ -138,53 +134,3 @@ 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;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#ifndef POP_LEVEL_H
|
||||
#define POP_LEVEL_H
|
||||
|
||||
#include "pop_qsave.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Загрузить уровень из файла в EMM-страницу. 0 — OK, -1 — ошибка
|
||||
@@ -32,6 +34,7 @@ void pop_level_free(void) __banked;
|
||||
* level). Уровень 1..15; 0 — демо-уровень, в скоуп не входит. Читают
|
||||
* потабличные функции ниже, стражи (HP/тип) и стартовая логика. */
|
||||
extern uint8_t pop_current_level;
|
||||
void pop_level_qsave(pop_qs_io_t *io);
|
||||
|
||||
/* Загрузить уровень НОМЕРОМ: освободить страницу прошлого, взять
|
||||
* `LEVELS\res20NN.bin` (fallback `a:\res20NN.bin`) и выставить
|
||||
|
||||
@@ -18,16 +18,8 @@
|
||||
#include "_pop_level.h"
|
||||
#include "pop_geom.h" /* pop_x_bump / FIRST_ONSCREEN_COLUMN — pos_guards */
|
||||
|
||||
/* 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. */
|
||||
/* Низкоуровневое чтение через W3 выполняет резидентный bank_load_file();
|
||||
* остальная одноразовая подготовка уровня безопасно остаётся в банке 8. */
|
||||
|
||||
void pop_level_free(void) __banked
|
||||
{
|
||||
@@ -58,6 +50,31 @@ int8_t pop_level_guard_type(uint8_t n) __banked { return n < 16 ? tbl_guard_typ
|
||||
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]; }
|
||||
|
||||
static int level_read_file(const char *path)
|
||||
{
|
||||
uint8_t blk, page;
|
||||
uint16_t i;
|
||||
int n;
|
||||
blk=mem_alloc_pages(1);
|
||||
if (!blk) return -1;
|
||||
page=mem_get_page(blk,0);
|
||||
n=bank_load_file(page,LVL_DATA_OFF,path,16384-LVL_DATA_OFF);
|
||||
if (n < (int)MIN_SIZE) { mem_free_block(blk); return -1; }
|
||||
gfx_w0_page_prepare(page);
|
||||
gfx_w0_map(page);
|
||||
for (i=0; i<256; i++) {
|
||||
pop_dl1[i]=((const uint8_t *)LVL_DATA_OFF)[BP_LINKLOC+i];
|
||||
pop_dl2[i]=((const uint8_t *)LVL_DATA_OFF)[BP_LINKMAP+i];
|
||||
}
|
||||
for (i=0; i<24u*ROOM_TILES; i++)
|
||||
((uint8_t *)LVL_PRISTINE_OFF)[i]=
|
||||
((const uint8_t *)(LVL_DATA_OFF+BP_FG))[i];
|
||||
gfx_w0_unmap();
|
||||
pop_lvl_blk=blk; pop_lvl_page=page; pop_lvl_ok=1;
|
||||
pop_gstate_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pop_level_load_num(uint8_t n) __banked
|
||||
{
|
||||
/* Имена патчим на месте, а не через sprintf: printf-семейство тянет в
|
||||
@@ -71,10 +88,10 @@ int pop_level_load_num(uint8_t n) __banked
|
||||
pri[13] = alt[9] = (char)('0' + n % 10);
|
||||
|
||||
/* Старую страницу отпускаем ТОЛЬКО после успешной загрузки новой:
|
||||
* pop_level_read_file сам аллоцирует страницу, и если файла нет — остаёмся
|
||||
* level_read_file сам аллоцирует страницу, и если файла нет — остаёмся
|
||||
* на текущем уровне вместо падения в пустой уровень. */
|
||||
pop_lvl_ok = 0; /* чтобы level_load_path не мешался */
|
||||
if (pop_level_read_file(pri) != 0 && pop_level_read_file(alt) != 0) {
|
||||
if (level_read_file(pri) != 0 && level_read_file(alt) != 0) {
|
||||
pop_lvl_blk = saved_blk; pop_lvl_page = saved_page; pop_lvl_ok = saved_ok;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Сериализация уровня для QuickSave (банк 8).
|
||||
*
|
||||
* Код выполняется только по F6/F9 и вынесен из горячего резидентного
|
||||
* pop_level.c. Данные уровня и снимка используют одно окно W0, поэтому
|
||||
* foretable переносится небольшими блоками через стековый буфер W2.
|
||||
*/
|
||||
#include <sprite.h>
|
||||
#include "pop_level.h"
|
||||
#include "_pop_level.h"
|
||||
|
||||
void pop_level_qsave(pop_qs_io_t *io)
|
||||
{
|
||||
uint8_t tmp[30];
|
||||
uint16_t off;
|
||||
pop_qs_bytes(io, pop_dl1, sizeof(pop_dl1));
|
||||
pop_qs_bytes(io, pop_dl2, sizeof(pop_dl2));
|
||||
pop_qs_bytes(io, pop_gstate, sizeof(pop_gstate));
|
||||
if (!pop_lvl_ok) { io->failed = 1; return; }
|
||||
/* W0 одновременно не может держать страницу уровня и страницу снимка. */
|
||||
for (off = 0; off < 24u * ROOM_TILES; off += sizeof(tmp)) {
|
||||
uint8_t i;
|
||||
if (!io->reading || io->apply) {
|
||||
gfx_w0_map(pop_lvl_page);
|
||||
if (!io->reading)
|
||||
for (i=0; i<sizeof(tmp); i++)
|
||||
tmp[i]=((uint8_t *)(LVL_DATA_OFF+BP_FG))[off+i];
|
||||
gfx_w0_unmap();
|
||||
}
|
||||
pop_qs_bytes(io,tmp,sizeof(tmp));
|
||||
if (io->reading && io->apply && !io->failed) {
|
||||
gfx_w0_map(pop_lvl_page);
|
||||
for (i=0; i<sizeof(tmp); i++)
|
||||
((uint8_t *)(LVL_DATA_OFF+BP_FG))[off+i]=tmp[i];
|
||||
gfx_w0_unmap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3394,3 +3394,16 @@ void pop_check_mirror(void) __banked
|
||||
/* Char остался ОТРАЖЁННЫМ — как в оригинале: обратно он не сохраняется,
|
||||
* а следующий тик перезагружает окно из Kid. */
|
||||
}
|
||||
|
||||
/* Закрытое состояние физики, которое влияет на следующие кадры. Таблицы
|
||||
* коллизии и кэши соседей намеренно не входят: их пересчитает вход в комнату. */
|
||||
void pop_map_qsave(pop_qs_io_t *io) __banked
|
||||
{
|
||||
pop_qs_u8(io, &is_screaming);
|
||||
pop_qs_u8(io, &grab_timer);
|
||||
pop_qs_i8(io, &pickup_obj_type);
|
||||
pop_qs_u16(io, &loose_seed.lo);
|
||||
pop_qs_u16(io, &loose_seed.hi);
|
||||
pop_qs_u8(io, &last_loose_snd);
|
||||
if (io->reading && io->apply) pop_coll_invalidate();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include "pop_char.h" /* pop_char_t — окклюзия ворот спрашивает про персонажа */
|
||||
#include "pop_qsave.h"
|
||||
|
||||
/* Задать карту текущей комнаты: fg[30] = коды тайлов (row*10+col, 3×10).
|
||||
* col<0/>9 или row<0/>2 трактуются как стена (край уровня). Массив
|
||||
@@ -197,6 +198,7 @@ void pop_check_knock(void) __banked;
|
||||
* links.down — сменить комнату (pop_room_load+перерисовка) и продолжить;
|
||||
* иначе — выпал из уровня (респавн). Снять флаг после обработки. */
|
||||
extern uint8_t pop_fell_out;
|
||||
void pop_map_qsave(pop_qs_io_t *io) __banked;
|
||||
|
||||
/* Пер-кадровая физика ПОСЛЕ kid_tick(play_seq): fall_accel + fall_speed +
|
||||
* determine_col + check_action (триггер падения / do_fall / приземление).
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* pop_qsave.c — файловая транзакция и схема игрового снимка.
|
||||
* Живёт в холодном банке: в обычном кадре исполняется только проверка
|
||||
* резидентного флага запроса, вся тяжёлая работа бывает по F6/F9.
|
||||
*/
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h> /* rename */
|
||||
#include <sprinter_mem.h>
|
||||
#include <sprite.h>
|
||||
#include "pop_qsave.h"
|
||||
#include "pop_kid.h"
|
||||
#include "pop_guard.h"
|
||||
#include "pop_map.h"
|
||||
#include "pop_level.h"
|
||||
#include "pop_trob.h"
|
||||
#include "pop_bg.h"
|
||||
#include "pop_state.h"
|
||||
#include "pop_tile.h"
|
||||
#include "pop_sfx.h"
|
||||
#include "pop_pace.h"
|
||||
#include "roomtest_cold.h"
|
||||
|
||||
#define QS_VERSION 3
|
||||
#define QS_SAVE 1
|
||||
#define QS_LOAD 2
|
||||
#define QS_LIMIT 4096
|
||||
#define QS_BASE 0x100
|
||||
|
||||
extern uint8_t pop_qsave_request;
|
||||
|
||||
static void qs_char(pop_qs_io_t *io, pop_char_t *c)
|
||||
{
|
||||
pop_qs_u8(io, &c->frame); pop_qs_u8(io, &c->x); pop_qs_u8(io, &c->y);
|
||||
pop_qs_i8(io, &c->direction); pop_qs_i8(io, &c->curr_col);
|
||||
pop_qs_i8(io, &c->curr_row); pop_qs_u8(io, &c->action);
|
||||
pop_qs_i8(io, &c->fall_x); pop_qs_i8(io, &c->fall_y);
|
||||
pop_qs_u8(io, &c->room); pop_qs_u8(io, &c->repeat);
|
||||
pop_qs_u8(io, &c->charid); pop_qs_u8(io, &c->sword);
|
||||
pop_qs_i8(io, &c->alive); pop_qs_u16(io, &c->curr_seq);
|
||||
}
|
||||
|
||||
static void qs_payload(pop_qs_io_t *io)
|
||||
{
|
||||
uint8_t speed=pop_speed_mode, immortal=pop_immortal, sound=pop_snd_want;
|
||||
|
||||
/* Пользовательские параметры входят в снимок, а не остаются свойством
|
||||
* текущего запуска. Читаем сначала во временные байты: повреждённые
|
||||
* значения не должны частично применить остальное игровое состояние. */
|
||||
pop_qs_u8(io, &speed); pop_qs_u8(io, &immortal); pop_qs_u8(io, &sound);
|
||||
if (io->reading && io->apply) {
|
||||
if (speed >= POP_SPEED_MODES || immortal > 2 || sound > 1) {
|
||||
io->failed=1; return;
|
||||
}
|
||||
pop_speed_mode=speed; pop_immortal=immortal; pop_snd_want=sound;
|
||||
}
|
||||
|
||||
qs_char(io, &Kid); qs_char(io, &Guard);
|
||||
pop_qs_u8(io, &cur_room); pop_qs_u8(io, &kid_room);
|
||||
pop_qs_u8(io, &pop_checkpoint); pop_qs_u8(io, &pop_next_level);
|
||||
pop_qs_i8(io, &knock);
|
||||
|
||||
pop_qs_u8(io, &hitp_curr); pop_qs_u8(io, &hitp_max);
|
||||
pop_qs_u8(io, &hitp_beg_lev); pop_qs_i8(io, &hitp_delta);
|
||||
pop_qs_u8(io, &guardhp_curr); pop_qs_u8(io, &guardhp_max);
|
||||
pop_qs_i8(io, &guardhp_delta); pop_qs_u8(io, &guard_skill);
|
||||
pop_qs_i8(io, &guard_refrac); pop_qs_i8(io, &justblocked);
|
||||
pop_qs_i8(io, &kid_sword_strike); pop_qs_i8(io, &offguard);
|
||||
pop_qs_i8(io, &holding_sword); pop_qs_u8(io, &pop_guard_hurt);
|
||||
pop_qs_i8(io, &can_guard_see_kid); pop_qs_i8(io, &is_guard_notice);
|
||||
pop_qs_i8(io, &pop_united_shadow); pop_qs_u8(io, &pop_shadow_init);
|
||||
pop_qs_i8(io, &pop_guard_notice_timer);
|
||||
pop_qs_u16(io, &pop_fight_seed.lo); pop_qs_u16(io, &pop_fight_seed.hi);
|
||||
|
||||
pop_qs_u8(io, &pop_kid_dead); pop_qs_u8(io, &pop_kid_hurt);
|
||||
pop_qs_u8(io, &pop_have_sword); pop_qs_u8(io, &pop_item_taken);
|
||||
pop_qs_u8(io, &pop_flash_time); pop_qs_u8(io, &pop_flash_color);
|
||||
pop_qs_u8(io, &pop_feather); pop_qs_u8(io, &pop_upside);
|
||||
pop_qs_u8(io, &pop_upside_want); pop_qs_u8(io, &pop_upside_dirty);
|
||||
pop_qs_u8(io, &pop_leave_dir); pop_qs_u8(io, &pop_leave_timer);
|
||||
pop_qs_u8(io, &pop_loose_fell); pop_qs_u8(io, &pop_ceil_fell);
|
||||
pop_qs_u8(io, &pop_debris_at); pop_qs_u8(io, &pop_fell_out);
|
||||
pop_qs_bytes(io, pop_loose_modif, sizeof(pop_loose_modif));
|
||||
pop_qs_bytes(io, pop_ceil_modif, sizeof(pop_ceil_modif));
|
||||
|
||||
pop_qs_u8(io, &pop_loose_landed); pop_qs_u8(io, &pop_loose_exit);
|
||||
pop_qs_u8(io, &pop_loose_exit_room); pop_qs_u8(io, &pop_droppedout);
|
||||
pop_qs_u16(io, &pop_leveldoor_open); pop_qs_i8(io, &pop_jumped_mirror);
|
||||
pop_qs_u8(io, &pop_seamless); pop_qs_u8(io, &pop_nav_hold);
|
||||
|
||||
pop_level_qsave(io);
|
||||
pop_trob_qsave(io);
|
||||
pop_loose_mob_qsave(io);
|
||||
pop_map_qsave(io);
|
||||
pop_guard_ai_qsave(io);
|
||||
}
|
||||
|
||||
static void qs_init(pop_qs_io_t *io, uint8_t page, uint16_t limit,
|
||||
uint8_t reading, uint8_t apply)
|
||||
{
|
||||
io->page=page; io->pos=QS_BASE; io->limit=(uint16_t)(QS_BASE+limit);
|
||||
io->checksum=0;
|
||||
io->count=0; io->reading=reading; io->apply=apply; io->failed=0;
|
||||
}
|
||||
|
||||
static uint8_t qs_header_read(pop_qs_io_t *io, uint8_t *level)
|
||||
{
|
||||
uint8_t h[7];
|
||||
if (!pop_qs_raw_get(io,h,sizeof(h))) return 0;
|
||||
if (h[0]!='P' || h[1]!='O' || h[2]!='P' || h[3]!='Q' ||
|
||||
h[4] != QS_VERSION || h[5] < 1 || h[5] > 15 || h[6] != 0) return 0;
|
||||
*level = h[5]; return 1;
|
||||
}
|
||||
|
||||
static uint8_t qs_validate_page(uint8_t page, uint16_t size,
|
||||
uint8_t *level, uint16_t *payload_size)
|
||||
{
|
||||
pop_qs_io_t io;
|
||||
uint8_t tail[4];
|
||||
uint16_t n, sum;
|
||||
if (size < 11) return 0;
|
||||
qs_init(&io,page,size,1,0);
|
||||
if (!qs_header_read(&io,level)) return 0;
|
||||
io.pos=(uint16_t)(QS_BASE+size-4);
|
||||
if (!pop_qs_raw_get(&io,tail,4)) return 0;
|
||||
n = (uint16_t)(tail[0] | ((uint16_t)tail[1] << 8));
|
||||
sum = (uint16_t)(tail[2] | ((uint16_t)tail[3] << 8));
|
||||
if (n != (uint16_t)(size-11) ||
|
||||
sum != pop_qs_checksum_page(page,QS_BASE+7,n)) return 0;
|
||||
*payload_size=n; return 1;
|
||||
}
|
||||
|
||||
static uint8_t qs_apply(uint8_t page, uint16_t size, uint16_t payload_size,
|
||||
uint8_t *level)
|
||||
{
|
||||
pop_qs_io_t io;
|
||||
qs_init(&io,page,size,1,1);
|
||||
if (!qs_header_read(&io,level)) return 0;
|
||||
qs_payload(&io);
|
||||
return (uint8_t)(!io.failed && io.count == payload_size &&
|
||||
io.pos == (uint16_t)(QS_BASE+7+payload_size));
|
||||
}
|
||||
|
||||
static int8_t qs_save(uint8_t page)
|
||||
{
|
||||
pop_qs_io_t io;
|
||||
uint8_t tail[4];
|
||||
const uint8_t h[7]={'P','O','P','Q',QS_VERSION,pop_current_level,0};
|
||||
uint16_t size;
|
||||
qs_init(&io,page,QS_LIMIT,0,0);
|
||||
pop_qs_raw_put(&io,h,sizeof(h));
|
||||
qs_payload(&io);
|
||||
tail[0]=(uint8_t)io.count; tail[1]=(uint8_t)(io.count >> 8);
|
||||
io.checksum=pop_qs_checksum_page(page,QS_BASE+7,io.count);
|
||||
tail[2]=(uint8_t)io.checksum; tail[3]=(uint8_t)(io.checksum >> 8);
|
||||
pop_qs_raw_put(&io,tail,4); size=(uint16_t)(io.pos-QS_BASE);
|
||||
if (io.failed) return -1;
|
||||
unlink("POP.NEW");
|
||||
if (bank_save_file(page,QS_BASE,"POP.NEW",size) != size) {
|
||||
unlink("POP.NEW"); return -1;
|
||||
}
|
||||
unlink("POP.BAK");
|
||||
(void)rename("POP.SAV", "POP.BAK");
|
||||
if (rename("POP.NEW", "POP.SAV") != 0) {
|
||||
(void)rename("POP.BAK", "POP.SAV"); return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int8_t qs_load_one(const char *name, uint8_t page)
|
||||
{
|
||||
uint8_t level;
|
||||
uint16_t payload_size;
|
||||
int size=bank_load_file(page,QS_BASE,name,QS_LIMIT);
|
||||
if (size <= 0 || !qs_validate_page(page,(uint16_t)size,&level,
|
||||
&payload_size)) return -1;
|
||||
if (level != pop_current_level) {
|
||||
pop_next_level = level;
|
||||
if (pop_level_switch() != 0 || pop_current_level != level) return -1;
|
||||
}
|
||||
if (!qs_apply(page,(uint16_t)size,payload_size,&level)) return -1;
|
||||
Char = Kid; Opp = Guard;
|
||||
pop_qsave_restore_room(cur_room);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int8_t pop_qsave_process(void) __banked
|
||||
{
|
||||
uint8_t request = pop_qsave_request;
|
||||
uint8_t blk,page;
|
||||
int8_t rc=-1;
|
||||
pop_qsave_request = 0;
|
||||
if (!request) return 0;
|
||||
blk=mem_alloc_pages(1);
|
||||
if (!blk) return -1;
|
||||
page=mem_get_page(blk,0);
|
||||
gfx_w0_page_prepare(page);
|
||||
pop_sfx_pause();
|
||||
if (request == QS_SAVE) rc = qs_save(page);
|
||||
else {
|
||||
rc = qs_load_one("POP.SAV",page);
|
||||
if (rc < 0) rc = qs_load_one("POP.BAK",page);
|
||||
}
|
||||
pop_sfx_start();
|
||||
mem_free_block(blk);
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* pop_qsave.h — потоковый QuickSave/QuickLoad одного игрового слота.
|
||||
*
|
||||
* На диске используются POP.NEW, POP.SAV и POP.BAK. Формат не содержит
|
||||
* указателей, файловых дескрипторов и графических кэшей; многобайтные поля
|
||||
* записываются явно little-endian. Внутренние функции pop_qs_* нужны
|
||||
* модулям-владельцам закрытого состояния.
|
||||
*/
|
||||
#ifndef POP_QSAVE_H
|
||||
#define POP_QSAVE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t page;
|
||||
uint16_t pos;
|
||||
uint16_t limit;
|
||||
uint16_t checksum;
|
||||
uint16_t count;
|
||||
uint8_t reading;
|
||||
uint8_t apply;
|
||||
uint8_t failed;
|
||||
} pop_qs_io_t;
|
||||
|
||||
void pop_qs_bytes(pop_qs_io_t *io, void *data, uint16_t size);
|
||||
void pop_qs_u8(pop_qs_io_t *io, uint8_t *value);
|
||||
void pop_qs_i8(pop_qs_io_t *io, int8_t *value);
|
||||
void pop_qs_u16(pop_qs_io_t *io, uint16_t *value);
|
||||
void pop_qs_i16(pop_qs_io_t *io, int *value);
|
||||
void pop_qs_raw_put(pop_qs_io_t *io, const uint8_t *data, uint8_t size);
|
||||
uint8_t pop_qs_raw_get(pop_qs_io_t *io, uint8_t *data, uint8_t size);
|
||||
uint16_t pop_qs_checksum_page(uint8_t page, uint16_t pos, uint16_t size);
|
||||
|
||||
/* Вызывается в безопасной границе кадра. Возвращает: 0 — запроса нет,
|
||||
* 1 — сохранено/загружено, -1 — ошибка. */
|
||||
int8_t pop_qsave_process(void) __banked;
|
||||
void pop_qsave_request_save(void);
|
||||
void pop_qsave_request_load(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Холодная часть ввода-вывода QuickSave (банк 8).
|
||||
*
|
||||
* Эти функции вызываются только банковым оркестратором pop_qsave.c, поэтому
|
||||
* им не место в дефицитном резидентном _CODE. Здесь только доступ через W0:
|
||||
* переключающие W3 DSS-операции обязаны оставаться в pop_qsave_io.c.
|
||||
*/
|
||||
#include "pop_qsave.h"
|
||||
#include <sprite.h>
|
||||
|
||||
void pop_qs_raw_put(pop_qs_io_t *io, const uint8_t *data, uint8_t size)
|
||||
{
|
||||
uint8_t *q, i;
|
||||
if (io->pos + size > io->limit) { io->failed=1; return; }
|
||||
gfx_w0_map(io->page); q=(uint8_t *)io->pos;
|
||||
for (i=0; i<size; i++) q[i]=data[i];
|
||||
gfx_w0_unmap(); io->pos += size;
|
||||
}
|
||||
|
||||
uint8_t pop_qs_raw_get(pop_qs_io_t *io, uint8_t *data, uint8_t size)
|
||||
{
|
||||
uint8_t *q, i;
|
||||
if (io->pos + size > io->limit) return 0;
|
||||
gfx_w0_map(io->page); q=(uint8_t *)io->pos;
|
||||
for (i=0; i<size; i++) data[i]=q[i];
|
||||
gfx_w0_unmap(); io->pos += size; return 1;
|
||||
}
|
||||
|
||||
/* XOR8 достаточно для обнаружения случайной порчи транзакционного файла;
|
||||
* размер payload проверяется отдельно, старший байт содержит инверсию XOR. */
|
||||
uint16_t pop_qs_checksum_page(uint8_t page, uint16_t pos, uint16_t size)
|
||||
{
|
||||
uint8_t *p, sum=0;
|
||||
gfx_w0_map(page); p=(uint8_t *)pos;
|
||||
while(size--) sum ^= *p++;
|
||||
gfx_w0_unmap();
|
||||
return (uint16_t)(sum | ((uint16_t)(uint8_t)~sum << 8));
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* Резидентная часть QuickSave: часто вызываемые межбанковые примитивы
|
||||
* потока и флаг запроса. Файловый обмен с W3 теперь общий в libc. */
|
||||
#include "pop_qsave.h"
|
||||
#include <sprite.h>
|
||||
#include <string.h>
|
||||
|
||||
uint8_t pop_qsave_request;
|
||||
void pop_qsave_request_save(void) { pop_qsave_request=1; }
|
||||
void pop_qsave_request_load(void) { pop_qsave_request=2; }
|
||||
|
||||
void pop_qs_bytes(pop_qs_io_t *io, void *data, uint16_t size)
|
||||
{
|
||||
uint8_t *p=(uint8_t *)data, *q;
|
||||
if (io->failed || io->pos + size > io->limit) { io->failed=1; return; }
|
||||
gfx_w0_map(io->page); q=(uint8_t *)io->pos;
|
||||
if (!io->reading) memcpy(q,p,size);
|
||||
else if (io->apply) memcpy(p,q,size);
|
||||
gfx_w0_unmap(); io->pos += size; io->count += size;
|
||||
}
|
||||
void pop_qs_u8(pop_qs_io_t *io, uint8_t *v) { pop_qs_bytes(io,v,1); }
|
||||
void pop_qs_i8(pop_qs_io_t *io, int8_t *v) { pop_qs_bytes(io,v,1); }
|
||||
void pop_qs_u16(pop_qs_io_t *io, uint16_t *value)
|
||||
{
|
||||
uint8_t b[2]; b[0]=(uint8_t)*value; b[1]=(uint8_t)(*value >> 8);
|
||||
pop_qs_bytes(io,b,2);
|
||||
if (io->reading && io->apply && !io->failed)
|
||||
*value=(uint16_t)(b[0] | ((uint16_t)b[1] << 8));
|
||||
}
|
||||
void pop_qs_i16(pop_qs_io_t *io, int *value)
|
||||
{
|
||||
uint16_t v=(uint16_t)*value; pop_qs_u16(io,&v);
|
||||
if (io->reading && io->apply && !io->failed) *value=(int)v;
|
||||
}
|
||||
@@ -1350,6 +1350,28 @@ void pop_loose_mob_reset(void) __banked
|
||||
* мапит и размапливает W0. Инвалидируется сменой комнаты. */
|
||||
static uint8_t mob_link_room, mob_link_up, mob_link_dn;
|
||||
|
||||
void pop_loose_mob_qsave(pop_qs_io_t *io) __banked
|
||||
{
|
||||
uint8_t i, live = 0;
|
||||
for (i = 0; i < MOB_MAX; i++) {
|
||||
mob_t *m = &mobs[i];
|
||||
pop_qs_u8(io, &m->active);
|
||||
pop_qs_i16(io, &m->x); pop_qs_i16(io, &m->y);
|
||||
pop_qs_i8(io, &m->speed); pop_qs_i8(io, &m->row);
|
||||
pop_qs_i8(io, &m->col); pop_qs_u8(io, &m->room);
|
||||
if (io->reading && io->apply) {
|
||||
m->clean = m->defer = 0;
|
||||
m->draw_y = m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
|
||||
if (m->active) live++;
|
||||
}
|
||||
}
|
||||
if (io->reading && io->apply) {
|
||||
mobs_live = live;
|
||||
pop_mob_busy = (uint8_t)(live != 0);
|
||||
mob_link_room = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void mob_links(void)
|
||||
{
|
||||
if (mob_link_room == pop_t_room) return;
|
||||
@@ -1955,4 +1977,4 @@ void pop_room_draw(uint8_t room_num,
|
||||
pop_t_clip_top = 0;
|
||||
pop_t_bake_rest = 0;
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
* ставит базовую страницу W1, значит хендлер обязан быть в базе;
|
||||
* - pop_sfx_play дёргается из горячих мест (play_seq на каждый шаг
|
||||
* Кида, физика, боёвка) — трамплин там был бы чистой потерей;
|
||||
* - pop_page_read маппит W3 на время чтения, а банковый код сам живёт
|
||||
* в W3 и вырезал бы себя из-под ног (та же причина, по которой
|
||||
* резидентен pop_level_read_file, см. _pop_level.h).
|
||||
* Файловое чтение страниц выполняет общий резидентный bank_load_file().
|
||||
* Холодная половина (аллокация страниц, cbl_open) — в pop_sfx_cold.c.
|
||||
*/
|
||||
|
||||
@@ -198,21 +196,3 @@ void pop_sfx_stop(void)
|
||||
sfx_left = 0;
|
||||
IRQ_ENABLE();
|
||||
}
|
||||
|
||||
/* ---- чтение файла в ФИЗИЧЕСКУЮ страницу ------------------------------ *
|
||||
* Резидентно по причине из шапки: маппим W3, читаем, возвращаем окно.
|
||||
* Ровно то же делает atlas_load, поэтому вызов из банка безопасен.
|
||||
* 0 — прочитано ожидаемое число байт, -1 — нет. */
|
||||
int pop_page_read(const char *path, uint8_t phys, uint16_t want)
|
||||
{
|
||||
uint8_t saved_w3;
|
||||
int fd, n;
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
saved_w3 = _io_page_w3;
|
||||
sprinter_page_w3(phys);
|
||||
n = read(fd, (uint8_t *)0xC000, want);
|
||||
_io_page_w3 = saved_w3;
|
||||
close(fd);
|
||||
return (n == (int)want) ? 0 : -1;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ int pop_sfx_init(void) __banked
|
||||
for (i = 0; i < POP_SND_PAGES; i++) {
|
||||
pop_snd_page[i] = mem_get_page(blk, i);
|
||||
if (!pop_snd_page[i] ||
|
||||
pop_page_read(pop_snd_files[i], pop_snd_page[i], 16384) != 0) {
|
||||
bank_load_file(pop_snd_page[i],0,pop_snd_files[i],16384) != 16384) {
|
||||
mem_free_block(blk);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -651,13 +651,34 @@ uint8_t pop_trob_room_active(uint8_t room) __banked
|
||||
* анимацию или нет). */
|
||||
#define TORCH_ANIM_DIV 1
|
||||
|
||||
#if TORCH_ANIM_DIV > 1
|
||||
static uint8_t torch_tick;
|
||||
#if TORCH_ANIM_DIV > 1
|
||||
#define TORCH_ANIM_NOW ((uint8_t)(torch_tick % TORCH_ANIM_DIV) == 0)
|
||||
#else
|
||||
#define TORCH_ANIM_NOW 1
|
||||
#endif
|
||||
|
||||
void pop_trob_qsave(pop_qs_io_t *io) __banked
|
||||
{
|
||||
uint8_t i;
|
||||
pop_qs_bytes(io, room_modif, sizeof(room_modif));
|
||||
pop_qs_bytes(io, room_seen, sizeof(room_seen));
|
||||
pop_qs_u8(io, &trobs_count);
|
||||
for (i = 0; i < TROBS_MAX; i++) {
|
||||
pop_qs_u8(io, &trobs[i].room);
|
||||
pop_qs_u8(io, &trobs[i].tilepos);
|
||||
pop_qs_i8(io, &trobs[i].type);
|
||||
}
|
||||
pop_qs_u16(io, &trob_seed.lo);
|
||||
pop_qs_u16(io, &trob_seed.hi);
|
||||
pop_qs_u8(io, &torch_tick);
|
||||
if (io->reading && io->apply) {
|
||||
if (trobs_count > TROBS_MAX) { io->failed = 1; return; }
|
||||
for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0;
|
||||
pop_redraw_reset();
|
||||
}
|
||||
}
|
||||
|
||||
void pop_process_trobs(uint8_t cur_room) __banked
|
||||
{
|
||||
uint8_t i, w = 0;
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#ifndef POP_TROB_H
|
||||
#define POP_TROB_H
|
||||
|
||||
#include "pop_qsave.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define POP_NROOMS 24
|
||||
@@ -26,6 +28,7 @@
|
||||
/* Сброс всего состояния (новый уровень / рестарт): забыть все комнаты и
|
||||
* очистить список trob. */
|
||||
void pop_trob_reset(void) __banked;
|
||||
void pop_trob_qsave(pop_qs_io_t *io) __banked;
|
||||
|
||||
/* Указатель на persistent modifier-массив комнаты room (1..24), 30 байт.
|
||||
* При ПЕРВОМ обращении лениво инициализируется из backtable уровня
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "pop_vflip.h" /* зеркальный набор персонажей (зелье инверсии) */
|
||||
#include "pop_pace.h" /* фиксированный логический кадр по лучу */
|
||||
#include "roomtest_cold.h" /* холодная половина цикла — в банке */
|
||||
#include "pop_qsave.h"
|
||||
|
||||
__sfr __at (0xFE) io_border;
|
||||
|
||||
@@ -62,8 +63,8 @@ __sfr __at (0xFE) io_border;
|
||||
#define PROF(n) ((void)0)
|
||||
#endif
|
||||
|
||||
/* Сколько банков грузит crt0_banked (читает его стартовый код).
|
||||
* 1 = guards (ИИ/боёвка), 2 = pop_bg (ГОРЯЧАЯ половина фона: fore-проход,
|
||||
/* Раскладка банков; их число crt0_banked получает автоматически из
|
||||
* --bank флагов: 1 = guards (ИИ/боёвка), 2 = pop_bg (ГОРЯЧАЯ половина фона: fore-проход,
|
||||
* оверлеи, кладка), 3 = pop_map (коллизия/физика), 4 = pop_cdraw
|
||||
* (отрисовка персонажей + полоса HP), 5 = pop_ctrl (диспетчер управления),
|
||||
* 6 = pop_trob (ловушки/анимация тайлов), 7 = pop_room (ХОЛОДНАЯ половина
|
||||
@@ -72,14 +73,7 @@ __sfr __at (0xFE) io_border;
|
||||
* отладочная навигация — см. roomtest_cold.h).
|
||||
* Отрисовка персонажей и холодная половина фона вынесены из банка 2
|
||||
* отдельно: тот дважды подходил к 16 КБ вплотную, а расти ему ещё есть
|
||||
* куда — тайлы поздних уровней, чомперы, зеркало.
|
||||
*
|
||||
* ДЕРЖАТЬ В СОГЛАСИИ с числом --bank в Makefile. Забыть — это не ошибка
|
||||
* сборки, а зависание: crt0 выделит меньше страниц, _bank_pages[N] так и
|
||||
* останется нулём, и трамплин прыгнет по 0xC000 в мусор (см. шапку
|
||||
* runtime/bank.s). Проверено 2026-08-05: с n_banks=4 и пятым банком игра
|
||||
* доходила до конца загрузки ресурсов и вставала намертво в DSS. */
|
||||
const uint8_t n_banks = 8;
|
||||
* куда — тайлы поздних уровней, чомперы, зеркало. */
|
||||
|
||||
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
||||
uint8_t pop_cheats;
|
||||
@@ -115,6 +109,8 @@ static void kbd_idle(void)
|
||||
#define SEQ_STAND 2
|
||||
#define ROOM_1 1
|
||||
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
|
||||
#define KBD_QUICKSAVE 0x0B /* F6, PS/2 set 2 */
|
||||
#define KBD_QUICKLOAD 0x01 /* F9, PS/2 set 2 */
|
||||
|
||||
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
|
||||
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
|
||||
@@ -250,6 +246,8 @@ int main(void)
|
||||
* железо), pop_pace_* молча откатываются на прежние gfx_wait_vsync. */
|
||||
pop_pace_arm();
|
||||
|
||||
{
|
||||
uint8_t qs_save_prev = 0, qs_load_prev = 0;
|
||||
while (!kbd_raw_down(KBD_ESC)) {
|
||||
/* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один
|
||||
* переключ). Выключение возвращает показ на страницу 0. */
|
||||
@@ -286,6 +284,18 @@ int main(void)
|
||||
/* Читы — В БАНКЕ (roomtest_cold.c): они дёргаются раз в кадр, а места
|
||||
* в резиденте W1/W2 нет (см. TASKS_OPEN, MEM). */
|
||||
pop_cheat_tick();
|
||||
/* F6/F9 только ставят запрос; файловая операция выполняется здесь,
|
||||
* на границе кадров — вне окон Char и до heal/tick/draw. */
|
||||
{
|
||||
uint8_t s = kbd_raw_down(KBD_QUICKSAVE);
|
||||
uint8_t l = kbd_raw_down(KBD_QUICKLOAD);
|
||||
int8_t qrc;
|
||||
if (s && !qs_save_prev) pop_qsave_request_save();
|
||||
if (l && !qs_load_prev) pop_qsave_request_load();
|
||||
qs_save_prev = s; qs_load_prev = l;
|
||||
qrc = pop_qsave_process();
|
||||
if (qrc > 0) dead_frames = 0;
|
||||
}
|
||||
if (frozen) {
|
||||
PROF(0); /* чёрный: ждём vsync */
|
||||
/* Заморозка — тоже полноценный логический кадр: иначе якорь
|
||||
@@ -733,6 +743,7 @@ int main(void)
|
||||
front = back;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pop_shutdown(); /* снять хук, закрыть ввод/графику, отдать ресурсы */
|
||||
return 0;
|
||||
|
||||
@@ -602,6 +602,69 @@ void enter_room_side(uint8_t room, uint8_t side) __banked
|
||||
* Страж такой смены не «видит» — он остаётся в своей комнате. */
|
||||
void enter_room(uint8_t room) __banked { enter_room_side(room, 0xFF); }
|
||||
|
||||
void pop_qsave_restore_room(uint8_t room) __banked
|
||||
{
|
||||
uint8_t i, pg, *room_mod;
|
||||
gfx_rect_t full;
|
||||
uint8_t lft, rgt, up;
|
||||
|
||||
pop_room_load(room, room_fg, room_bg, lcol_fg, lcol_bg,
|
||||
rcol_fg, rcol_bg, below_fg);
|
||||
pop_map_set(room_fg);
|
||||
lft = pop_room_link(room, 0); rgt = pop_room_link(room, 1);
|
||||
up = pop_room_link(room, 2);
|
||||
pop_map_set_edges(lft, rgt, up, pop_room_link(room, 3));
|
||||
pop_room_set_below(below_fg);
|
||||
if (up) {
|
||||
uint8_t *um = pop_trob_modif(up);
|
||||
pop_level_access_begin();
|
||||
for (i = 0; i < 10; i++)
|
||||
above_fg[i] = pop_level_tile_raw(up, (uint8_t)(20 + i));
|
||||
pop_level_access_end();
|
||||
for (i = 0; i < 10; i++) above_mod[i] = um[20 + i];
|
||||
pop_room_set_above(above_fg, above_mod);
|
||||
pop_map_set_above(above_fg);
|
||||
} else {
|
||||
pop_room_set_above(0, 0); pop_map_set_above(0);
|
||||
}
|
||||
pop_map_set_room(room);
|
||||
pop_loose_reset();
|
||||
pop_trob_room_changed();
|
||||
pop_bake_forget();
|
||||
pop_loose_mob_room_changed();
|
||||
room_mod = pop_trob_modif(room);
|
||||
if (lft) {
|
||||
uint8_t *m = pop_trob_modif(lft);
|
||||
for (i = 0; i < 3; i++) {
|
||||
lcol_bg[i] = m[9 + i * 10];
|
||||
seam_sig[i] = seam_row_sig(m, lcol_fg, i);
|
||||
}
|
||||
}
|
||||
if (rgt) {
|
||||
uint8_t *m = pop_trob_modif(rgt);
|
||||
rcol_bg[0] = m[0]; rcol_bg[1] = m[10]; rcol_bg[2] = m[20];
|
||||
}
|
||||
seam_redraw = seam_rows = 0;
|
||||
pg = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||||
gfx_set_draw_page(pg);
|
||||
setfillstyle(SOLID_FILL, BLACK); bar(0, 0, 319, 255);
|
||||
pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg);
|
||||
gfx_set_visible_page(pg);
|
||||
gfx_set_draw_page((uint8_t)(pg ^ 1));
|
||||
full.x = 0; full.y = 0; full.w = 320; full.h = 256;
|
||||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||||
cur_room = room; kid_room = Kid.room;
|
||||
/* Обе страницы теперь содержат только свежий фон. Старые габариты
|
||||
* персонажей относятся к кадру до QuickLoad и не должны запускать heal
|
||||
* поверх восстановленной комнаты. */
|
||||
pop_cd[POP_CH_KID].valid[0] = pop_cd[POP_CH_KID].valid[1] = 0;
|
||||
pop_cd[POP_CH_KID].ovalid[0] = pop_cd[POP_CH_KID].ovalid[1] = 0;
|
||||
pop_cd[POP_CH_OPP].valid[0] = pop_cd[POP_CH_OPP].valid[1] = 0;
|
||||
pop_cd[POP_CH_OPP].ovalid[0] = pop_cd[POP_CH_OPP].ovalid[1] = 0;
|
||||
pop_cd_init();
|
||||
pop_hp_invalidate();
|
||||
}
|
||||
|
||||
/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos
|
||||
* (seg003:028A) — поставить Кида в стартовую позицию УРОВНЯ. Один вход и
|
||||
* для запуска, и для рестарта после смерти / выпадения из уровня: в
|
||||
|
||||
@@ -47,6 +47,9 @@ extern uint8_t pop_checkpoint; /* checkpoint (seg000:270) — ставит у
|
||||
* Обе в банке 8: зовутся раз на комнату, а кода там на полтора килобайта. */
|
||||
void enter_room_side(uint8_t room, uint8_t side) __banked;
|
||||
void enter_room(uint8_t room) __banked;
|
||||
/* Пересобрать только производные кэши и обе страницы после QuickLoad.
|
||||
* Игровые таймеры, trobs, mobs и персонажей не изменяет. */
|
||||
void pop_qsave_restore_room(uint8_t room) __banked;
|
||||
|
||||
/* ---- холодная половина ----------------------------------------------- */
|
||||
|
||||
|
||||
@@ -27,6 +27,17 @@
|
||||
#include <sprite.h>
|
||||
#include <sprinter.h>
|
||||
#include <sprinter_mem.h>
|
||||
#include "../pop_qsave.h"
|
||||
|
||||
/* Сериализаторы приватного состояния входят в те же крупные модули, что и
|
||||
* физика/ИИ. Сами QuickSave-тесты работают на target с EMM; обычным
|
||||
* ucsim-сценам нужны только символы, и вызывать их они не должны. */
|
||||
void pop_qs_bytes(pop_qs_io_t *io, void *p, uint16_t n)
|
||||
{ (void)io; (void)p; (void)n; }
|
||||
void pop_qs_u8(pop_qs_io_t *io, uint8_t *p) { (void)io; (void)p; }
|
||||
void pop_qs_i8(pop_qs_io_t *io, int8_t *p) { (void)io; (void)p; }
|
||||
void pop_qs_u16(pop_qs_io_t *io, uint16_t *p) { (void)io; (void)p; }
|
||||
void pop_qs_i16(pop_qs_io_t *io, int *p) { (void)io; (void)p; }
|
||||
|
||||
#include "pop_kid.h"
|
||||
#include "pop_guard.h"
|
||||
@@ -35,6 +46,8 @@
|
||||
#include "pop_trob.h"
|
||||
#include "pop_level.h"
|
||||
#include "pop_redraw.h"
|
||||
#include "pop_pace.h"
|
||||
#include "pop_sfx.h"
|
||||
#include "_pop_draw.h"
|
||||
#include "pop_cdraw.h"
|
||||
#include "stubs.h"
|
||||
@@ -49,6 +62,7 @@ void _gfx_w0_isr(void) { }
|
||||
|
||||
void gfx_w0_map(uint8_t page) { (void)page; }
|
||||
void gfx_w0_unmap(void) { }
|
||||
void gfx_w0_page_prepare(uint8_t page) { (void)page; }
|
||||
|
||||
void gfx_blit_cols(int x, int y, const void *img, uint8_t flip)
|
||||
{ (void)x; (void)y; (void)img; (void)flip; }
|
||||
@@ -95,6 +109,12 @@ int read(int fd, void *b, int n)
|
||||
|
||||
int close(int fd) { (void)fd; return 0; }
|
||||
|
||||
int bank_load_file(uint8_t page, uint16_t off, const char *path, uint16_t max)
|
||||
{
|
||||
(void)page; (void)off; (void)path;
|
||||
return kid_blob_size <= max ? (int)kid_blob_size : (int)max;
|
||||
}
|
||||
|
||||
/* ---- клавиатура: управляемая тестом --------------------------------- */
|
||||
|
||||
uint16_t tk_keys[TK_KEYS_MAX];
|
||||
@@ -124,6 +144,13 @@ void tk_key_down(uint16_t code)
|
||||
if (tk_keys[i] == 0) { tk_keys[i] = code; return; }
|
||||
}
|
||||
|
||||
/* Темп кадра и звук относятся к окружению реальной машины. Физика и
|
||||
* управление только оставляют выборки луча и заявки эффектов; host-тесты
|
||||
* проверяют их игровую логику, поэтому здесь достаточно no-op контрактов. */
|
||||
void pop_beam_sample(void) __preserves_regs(b, c, d, e) { }
|
||||
void pop_sfx_play(uint8_t id) { (void)id; }
|
||||
void pop_sfx_stop(void) { }
|
||||
|
||||
/* ---- 2. соседние модули движка -------------------------------------- */
|
||||
|
||||
/* room_modif текущей комнаты: пики (счётчик фазы), ворота (высота),
|
||||
@@ -167,8 +194,9 @@ void pop_start_chompers(uint8_t room, int8_t row) __banked
|
||||
{ (void)room; (void)row; }
|
||||
|
||||
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); }
|
||||
uint8_t button_type, uint8_t modifier,
|
||||
uint8_t snd) __banked
|
||||
{ (void)modifier; (void)snd; log_put(TK_BUTTON, room, tilepos, button_type); }
|
||||
|
||||
void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) __banked
|
||||
{ log_put(TK_REDRAW, tilepos, kind, pages); }
|
||||
|
||||
@@ -84,19 +84,21 @@ TC_TEST(char_take_hp_goes_to_active)
|
||||
pop_saveshad();
|
||||
}
|
||||
|
||||
/* Чит бессмертия — только Киду: страж обязан гибнуть и с ним (иначе
|
||||
* отладочный режим ломает боёвку). */
|
||||
TC_TEST(char_immortal_covers_kid_only)
|
||||
/* Уровень 2 чита гасит только мелкий урон Киду. Мгновенная смерть
|
||||
* (count=100) проходит всегда, а на стража чит не действует вовсе. */
|
||||
TC_TEST(char_immortal_level2_covers_kid_only)
|
||||
{
|
||||
sc_room(room_flat, 1);
|
||||
sc_kid_at(3, 1, -1);
|
||||
sc_guard_at(CHARID_2_GUARD, 6, 1, -1, 3);
|
||||
pop_immortal = 1;
|
||||
pop_immortal = 2;
|
||||
|
||||
pop_loadkid();
|
||||
hitp_delta = 0;
|
||||
tc_check(pop_take_hp(100), 0, "kid: бессмертен", __LINE__);
|
||||
tc_check(pop_take_hp(1), 0, "kid: мелкий урон погашен", __LINE__);
|
||||
tc_check(hitp_delta, 0, "kid: HP не тронуто", __LINE__);
|
||||
tc_check(pop_take_hp(100), 1, "kid: мгновенная смерть проходит", __LINE__);
|
||||
tc_check(hitp_delta, -3, "kid: смертельная дельта", __LINE__);
|
||||
pop_savekid();
|
||||
|
||||
pop_loadshad();
|
||||
@@ -497,7 +499,7 @@ void main(void)
|
||||
{
|
||||
sc_init();
|
||||
TC_RUN(char_take_hp_goes_to_active);
|
||||
TC_RUN(char_immortal_covers_kid_only);
|
||||
TC_RUN(char_immortal_level2_covers_kid_only);
|
||||
TC_RUN(char_delta_hp_applies_per_character);
|
||||
TC_RUN(char_kid_survives_two_row_fall);
|
||||
TC_RUN(char_guard_dies_from_two_row_fall);
|
||||
|
||||
Reference in New Issue
Block a user