MEM-BANK5: pop_ctrl.c → банк 5; чит [/] подгонки Кида по X
Три правки едут вместе намеренно: n_banks и --bank обязаны меняться атомарно, иначе промежуточный коммит — зависание (см. ниже). MEM-BANK5. CODE и DATA делят одно 32-КБ пространство W1+W2, поэтому килобайт кода, уехавший в банк, — это килобайт, доступный данным. Кандидат выбран не по размеру, а по частоте вызова: диспетчер управления дёргается раз в кадр на персонажа и горячих банк→банк переходов не создаёт (в отличие от pop_level, чей pop_level_tile зовётся из банка 2 на КАЖДЫЙ тайл). _CODE 26 780 -> 24 662 Б (−2 118) куча 180 -> 2 298 Б банк 5 2 211 / 16 384 (13.5 %) Шина control_* (8 глобалов) переехала в pop_state.c. Сегодня она уцелела бы и в pop_ctrl.c — банки собираются без --bank-data, их писучие данные остаются в общем _DATA, — но это флаг сборки, а не свойство кода, а шину трогают уже три банка: 5 пишет с клавиатуры, 1 подаёт синтетический ввод ИИ (autocontrol_*, seg002), 3 читает через pop_ctrl_shift_held. Заодно pop_ctrl.c наконец включает собственный заголовок — раньше объявления жили прямо в нём. ГРАБЛИ, на которые наступили (стоили дольше самой задачи): n_banks в roomtest.c захардкожен, и его надо править вместе с числом --bank. С n_banks=4 и пятым банком crt0 выделил четыре страницы, _bank_pages[5] остался нулём, и первый же вызов pop_ctrl_init() через трамплин отобразил в W3 страницу 0 и прыгнул на 0xC874 в мусор — исполнение забрело в дисковый код DSS и осталось крутить чтение секторов. Симптом: загрузка ресурсов проходит целиком (open=45 — все атласы), комната и Кид успевают нарисоваться из enter_room, а HP и номер комнаты уже нет, и kid_tick не вызывается ни разу. Ровно предупреждение из шапки runtime/bank.s. Сверку n_banks с реальным максимальным индексом банка записал в docs/TODO.md (Auto-banking) — это должно быть ошибкой сборки. DBG-CHEATS: [ (0x54) и ] (0x5B) двигают Кида на пиксель (seg000:1828), по фронту нажатия, под pop_cheats. Нужны потому, что мост MAME теряет нажатия при быстрой отправке и подогнать Кида в позу скриптом нельзя — на это упёрлись BUG-LOOSE-2 и BUG-GATE-PASS-1. ROOMNAV больше не зовёт pop_trob_reset: reset обнуляет room_seen, то есть чит ОТМАТЫВАЛ МИР (открытые/закрытые ворота, нажатые кнопки). Навигация обязана только телепортировать. Проверено в MAME: старт уровня 1 рисуется полностью (комната, Кид, HP, номер), бег вправо и падение на второй ряд отрабатывают, ] даёт x+1 и [ даёт x−1 по одному нажатию, Shift+→ — осторожный шаг (x 131 -> 142, колонка 4 -> 5) и при удержании 90 кадров не срывается в бег (x 142 -> 151), то есть pop_ctrl_shift_held работает через границу банк 3 -> банк 5. Наборы под ucsim: geom 39, grab 53, phys 1723 — все зелёные. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,14 +14,21 @@ MEMORY ?= huge # small-раскладка + банки кода в W
|
|||||||
# банк1→банк2→банк1 в MAME 2026-07-30). Теперь в банки можно уводить и
|
# банк1→банк2→банк1 в MAME 2026-07-30). Теперь в банки можно уводить и
|
||||||
# логику, которая рисует. См. docs/layout_plan_v2.md §2, memory
|
# логику, которая рисует. См. docs/layout_plan_v2.md §2, memory
|
||||||
# sdcc_banked_call_rules.
|
# sdcc_banked_call_rules.
|
||||||
EXTRA_FLAGS ?= --gfx 256 --bank 2=pop_bg.c --bank 4=pop_gdraw.c --bank 3=pop_map.c --bank 1=guards.c $(PROF_FLAGS)
|
# Банк 5 = pop_ctrl.c (MEM-BANK5). CODE и DATA делят одно 32-КБ
|
||||||
|
# пространство W1+W2, поэтому килобайт кода, уехавший в банк, — это
|
||||||
|
# килобайт, доступный данным. Кандидат выбран не по размеру, а по частоте
|
||||||
|
# вызова: диспетчер управления дёргается один раз за кадр на персонажа и
|
||||||
|
# горячих банк→банк переходов не создаёт (в отличие от pop_level, чей
|
||||||
|
# pop_level_tile зовётся из банка 2 на КАЖДЫЙ тайл). Шина control_*
|
||||||
|
# переехала в pop_state.c — см. там.
|
||||||
|
EXTRA_FLAGS ?= --gfx 256 --bank 2=pop_bg.c --bank 4=pop_gdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c $(PROF_FLAGS)
|
||||||
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||||
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||||
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||||
# MAME = время фазы в растровых строках.
|
# MAME = время фазы в растровых строках.
|
||||||
PROF ?= 1
|
PROF ?= 1
|
||||||
PROF_FLAGS := -DPROF_BORDER=$(PROF)
|
PROF_FLAGS := -DPROF_BORDER=$(PROF)
|
||||||
EXTRA_SRCS := pop_state.c pop_draw.c pop_kid.c pop_ctrl.c pop_level.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c
|
EXTRA_SRCS := pop_state.c pop_draw.c pop_kid.c pop_level.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c
|
||||||
|
|
||||||
BG_DIR := $(CURDIR)/../poc/res/bg
|
BG_DIR := $(CURDIR)/../poc/res/bg
|
||||||
KID_DIR := $(CURDIR)/../poc/res/kid
|
KID_DIR := $(CURDIR)/../poc/res/kid
|
||||||
|
|||||||
@@ -44,8 +44,8 @@
|
|||||||
| 4 | [L3-CHKP](#l3-chkp) | чекпойнт ур. 3 | корректный респавн ур. 3 |
|
| 4 | [L3-CHKP](#l3-chkp) | чекпойнт ур. 3 | корректный респавн ур. 3 |
|
||||||
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
||||||
| — | [L1-PASS](#l1-pass) | сквозной прогон ур. 1 + таблица 24 комнат | приёмка ур. 1 |
|
| — | [L1-PASS](#l1-pass) | сквозной прогон ур. 1 + таблица 24 комнат | приёмка ур. 1 |
|
||||||
| — | [DBG-CHEATS](#dbg-cheats) | `[`/`]` — подгонка Кида по X | отладка (BUG-GATE-PASS-1) |
|
| ✔ | [DBG-CHEATS](#dbg-cheats) | `[`/`]` — подгонка Кида по X | **СДЕЛАНО 2026-08-05** |
|
||||||
| — | [MEM-BANK5](#mem-bank5) | новый банк кода: W1/W2 осталось 38 Б кучи | берётся по факту нехватки места |
|
| ✔ | [MEM-BANK5](#mem-bank5) | `pop_ctrl.c` → банк 5 | **СДЕЛАНО 2026-08-05**: куча 180 Б → 2298 Б |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
* просто глобальный флаг: main ставит его при старте (на время разработки —
|
* просто глобальный флаг: main ставит его при старте (на время разработки —
|
||||||
* ВКЛЮЧЁН). Клавиши читов обрабатывает главный цикл.
|
* ВКЛЮЧЁН). Клавиши читов обрабатывает главный цикл.
|
||||||
*
|
*
|
||||||
* Реализовано: K — убить стража (seg000:786). Остальные читы оригинала
|
* Реализовано: K — убить стража (seg000:786), I/S/Shift+L (наши отладочные),
|
||||||
|
* [ / ] — подгонка Кида по X (seg000:1828). Остальные читы оригинала
|
||||||
* (время, перо, воскрешение, переход по комнатам) пока не портированы.
|
* (время, перо, воскрешение, переход по комнатам) пока не портированы.
|
||||||
*/
|
*/
|
||||||
#ifndef POP_CHEAT_H
|
#ifndef POP_CHEAT_H
|
||||||
@@ -20,6 +21,8 @@ extern uint8_t pop_cheats; /* 0 = выключены, иначе включ
|
|||||||
#define KBD_CHEAT_IMMO 0x43 /* I (PS/2 set 2) — бессмертие Кида (toggle) */
|
#define KBD_CHEAT_IMMO 0x43 /* I (PS/2 set 2) — бессмертие Кида (toggle) */
|
||||||
#define KBD_CHEAT_SWORD 0x1B /* S (PS/2 set 2) — выдать Киду меч */
|
#define KBD_CHEAT_SWORD 0x1B /* S (PS/2 set 2) — выдать Киду меч */
|
||||||
#define KBD_CHEAT_NEXTLVL 0x4B /* Shift+L (PS/2 set 2) — следующий уровень */
|
#define KBD_CHEAT_NEXTLVL 0x4B /* Shift+L (PS/2 set 2) — следующий уровень */
|
||||||
|
#define KBD_CHEAT_XDEC 0x54 /* [ (PS/2 set 2) — сдвинуть Кида на 1 px влево */
|
||||||
|
#define KBD_CHEAT_XINC 0x5B /* ] (PS/2 set 2) — сдвинуть Кида на 1 px вправо */
|
||||||
|
|
||||||
/* Последний уровень игры (tbl_* в pop_level рассчитаны на 0..15). */
|
/* Последний уровень игры (tbl_* в pop_level рассчитаны на 0..15). */
|
||||||
#define POP_LAST_LEVEL 15
|
#define POP_LAST_LEVEL 15
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <kbd_raw.h>
|
#include <kbd_raw.h>
|
||||||
|
#include "pop_ctrl.h" /* свой API (__banked) + объявления шины control_* */
|
||||||
#include "pop_kid.h"
|
#include "pop_kid.h"
|
||||||
#include "pop_map.h"
|
#include "pop_map.h"
|
||||||
#include "pop_guard.h" /* charid, состояние меча, seq стража */
|
#include "pop_guard.h" /* charid, состояние меча, seq стража */
|
||||||
@@ -73,15 +74,15 @@
|
|||||||
#define FRAME_109_CROUCH 109
|
#define FRAME_109_CROUCH 109
|
||||||
|
|
||||||
/* ---- Состояние управления (seg006 globals) ------------------------ *
|
/* ---- Состояние управления (seg006 globals) ------------------------ *
|
||||||
* control_x/y/shift — «сырое» состояние осей ЭТОГО кадра (абсолютно:
|
* Сами control_* ЖИВУТ В pop_state.c, а не здесь. Модуль уехал в БАНК 5,
|
||||||
* X LEFT/-1..RIGHT/+1); *_forward/backward/up/down/shift2 — конечный
|
* а шину пишут с трёх сторон (этот банк, guards.c из банка 1) и читают из
|
||||||
* автомат held/released/ignore (авто-повтор); ctrl1_* — их память между
|
* банка 3 (pop_map) — это ровно тот случай, для которого pop_state и
|
||||||
* кадрами (rest/save). */
|
* заведён. Сегодня оно работало бы и отсюда: банки собираются БЕЗ
|
||||||
int8_t control_x, control_y, control_shift;
|
* --bank-data, поэтому их писучие данные остаются в общем _DATA. Но это
|
||||||
/* НЕ static: в оригинале это глобалы, через которые ИИ стража подаёт
|
* флаг сборки, а не свойство кода: включат --bank-data ради экономии
|
||||||
* СИНТЕТИЧЕСКИЙ ввод (autocontrol_*, seg002), а дальше работает тот же
|
* W1/W2 — и шина молча уедет в страницу банка.
|
||||||
* control(), что и у игрока. Поэтому шина общая. */
|
*
|
||||||
int8_t control_forward, control_backward, control_up, control_down, control_shift2;
|
* ctrl1_* остаются здесь: их трогает только этот модуль. */
|
||||||
static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2;
|
static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2;
|
||||||
|
|
||||||
/* ---- seqtbl_offset_char: сменить последовательность Kid ------------ *
|
/* ---- seqtbl_offset_char: сменить последовательность Kid ------------ *
|
||||||
@@ -549,7 +550,7 @@ static void control_with_sword(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* control() — диспетчер по кадру (seg005.c:252) для АКТИВНОГО персонажа. */
|
/* control() — диспетчер по кадру (seg005.c:252) для АКТИВНОГО персонажа. */
|
||||||
void pop_control(void)
|
void pop_control(void) __banked
|
||||||
{
|
{
|
||||||
uint8_t f = Char.frame;
|
uint8_t f = Char.frame;
|
||||||
/* seg005 control(), самая первая ветка: персонаж УМИРАЕТ (alive >= 0).
|
/* seg005 control(), самая первая ветка: персонаж УМИРАЕТ (alive >= 0).
|
||||||
@@ -674,7 +675,7 @@ static void save_ctrl_1(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Публичный API ------------------------------------------------ */
|
/* ---- Публичный API ------------------------------------------------ */
|
||||||
int pop_ctrl_init(void)
|
int pop_ctrl_init(void) __banked
|
||||||
{
|
{
|
||||||
control_forward = control_backward = control_up = control_down = CONTROL_RELEASED;
|
control_forward = control_backward = control_up = control_down = CONTROL_RELEASED;
|
||||||
control_shift2 = CONTROL_RELEASED;
|
control_shift2 = CONTROL_RELEASED;
|
||||||
@@ -682,17 +683,17 @@ int pop_ctrl_init(void)
|
|||||||
return kbd_raw_open();
|
return kbd_raw_open();
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_ctrl_close(void)
|
void pop_ctrl_close(void) __banked
|
||||||
{
|
{
|
||||||
kbd_raw_close();
|
kbd_raw_close();
|
||||||
}
|
}
|
||||||
|
|
||||||
int8_t pop_ctrl_shift_held(void)
|
int8_t pop_ctrl_shift_held(void) __banked
|
||||||
{
|
{
|
||||||
return (int8_t)(control_shift == CONTROL_HELD);
|
return (int8_t)(control_shift == CONTROL_HELD);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop_ctrl_tick(void)
|
void pop_ctrl_tick(void) __banked
|
||||||
{
|
{
|
||||||
kbd_raw_sync(); /* снять залипшее после Rx-overrun SIO */
|
kbd_raw_sync(); /* снять залипшее после Rx-overrun SIO */
|
||||||
/* control_kid (seg006:0CD1), ПЕРВАЯ строка: HP кончились — персонаж
|
/* control_kid (seg006:0CD1), ПЕРВАЯ строка: HP кончились — персонаж
|
||||||
|
|||||||
@@ -18,19 +18,26 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* МОДУЛЬ ЖИВЁТ В БАНКЕ 5 (--bank 5=pop_ctrl.c), поэтому всё, что зовут
|
||||||
|
* снаружи, помечено __banked. Зовут с трёх сторон: главный цикл
|
||||||
|
* (резидент), guards.c (банк 1) и pop_map.c (банк 3) — банк→банк
|
||||||
|
* работает, трамплин сохраняет страницу окна на стеке. Диспетчер
|
||||||
|
* дёргается один раз за кадр на персонажа, так что цена перехода
|
||||||
|
* незаметна. Сама шина control_* лежит в pop_state.c (см. ниже). */
|
||||||
|
|
||||||
/* Открыть raw-канал клавиатуры и обнулить память авто-повтора.
|
/* Открыть raw-канал клавиатуры и обнулить память авто-повтора.
|
||||||
* 0 — OK, -1 — ошибка (EBUSY и пр., см. kbd_raw_open). */
|
* 0 — OK, -1 — ошибка (EBUSY и пр., см. kbd_raw_open). */
|
||||||
int pop_ctrl_init(void);
|
int pop_ctrl_init(void) __banked;
|
||||||
void pop_ctrl_close(void);
|
void pop_ctrl_close(void) __banked;
|
||||||
|
|
||||||
/* Один кадр ввода: прочитать клавиши -> обновить control_* -> прогнать
|
/* Один кадр ввода: прочитать клавиши -> обновить control_* -> прогнать
|
||||||
* диспетчер control() (может сменить Kid.curr_seq). Звать ПЕРЕД
|
* диспетчер control() (может сменить Kid.curr_seq). Звать ПЕРЕД
|
||||||
* kid_tick() (play_seq продолжит с новой последовательности). */
|
* kid_tick() (play_seq продолжит с новой последовательности). */
|
||||||
void pop_ctrl_tick(void);
|
void pop_ctrl_tick(void) __banked;
|
||||||
|
|
||||||
/* control_shift зажат ПРЯМО СЕЙЧАС? — для check_grab в физике (pop_map):
|
/* control_shift зажат ПРЯМО СЕЙЧАС? — для check_grab в физике (pop_map):
|
||||||
* зацеп за уступ в падении по Shift. Валидно после pop_ctrl_tick. */
|
* зацеп за уступ в падении по Shift. Валидно после pop_ctrl_tick. */
|
||||||
int8_t pop_ctrl_shift_held(void);
|
int8_t pop_ctrl_shift_held(void) __banked;
|
||||||
|
|
||||||
/* ---- Шина синтетического ввода (порт глобалов control_* из seg005) ----
|
/* ---- Шина синтетического ввода (порт глобалов control_* из seg005) ----
|
||||||
* В оригинале ИИ соперника (autocontrol_*, seg002) не имеет своего
|
* В оригинале ИИ соперника (autocontrol_*, seg002) не имеет своего
|
||||||
@@ -42,6 +49,9 @@ int8_t pop_ctrl_shift_held(void);
|
|||||||
/* Оси ЭТОГО кадра (абсолютные): X = -1 влево / +1 вправо, Y = -1 вверх /
|
/* Оси ЭТОГО кадра (абсолютные): X = -1 влево / +1 вправо, Y = -1 вверх /
|
||||||
* +1 вниз. Для ИИ «вперёд/назад» уже в системе персонажа, поэтому оси он
|
* +1 вниз. Для ИИ «вперёд/назад» уже в системе персонажа, поэтому оси он
|
||||||
* заполняет как есть, без flip_control_x. */
|
* заполняет как есть, без flip_control_x. */
|
||||||
|
/* Определены в pop_state.c (резидент W1/W2), а не в pop_ctrl.c: шину
|
||||||
|
* трогают три банка сразу, а данные банкового модуля живут в _DATA лишь
|
||||||
|
* пока сборка идёт без --bank-data. */
|
||||||
extern int8_t control_x, control_y, control_shift;
|
extern int8_t control_x, control_y, control_shift;
|
||||||
#define POP_CONTROL_HELD_FORWARD (-1)
|
#define POP_CONTROL_HELD_FORWARD (-1)
|
||||||
#define POP_CONTROL_HELD_BACKWARD 1
|
#define POP_CONTROL_HELD_BACKWARD 1
|
||||||
@@ -53,6 +63,6 @@ extern int8_t control_forward, control_backward, control_up, control_down,
|
|||||||
/* Диспетчер (seg005:252) для АКТИВНОГО персонажа Char. Игрок идёт через
|
/* Диспетчер (seg005:252) для АКТИВНОГО персонажа Char. Игрок идёт через
|
||||||
* pop_ctrl_tick (тот делает user_control с зеркалом «вперёд»), страж — из
|
* pop_ctrl_tick (тот делает user_control с зеркалом «вперёд»), страж — из
|
||||||
* своего тика напрямую. */
|
* своего тика напрямую. */
|
||||||
void pop_control(void);
|
void pop_control(void) __banked;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -11,9 +11,24 @@
|
|||||||
* Модуль обязан оставаться в W1/W2: его читают и главный цикл, и банки.
|
* Модуль обязан оставаться в W1/W2: его читают и главный цикл, и банки.
|
||||||
*/
|
*/
|
||||||
#include "pop_state.h"
|
#include "pop_state.h"
|
||||||
|
#include "pop_ctrl.h" /* объявления шины control_* (живёт здесь) */
|
||||||
|
|
||||||
uint8_t pop_loose_landed;
|
uint8_t pop_loose_landed;
|
||||||
|
|
||||||
|
/* ---- Шина управления (seg006 globals) ------------------------------ *
|
||||||
|
* Объявлены в pop_ctrl.h, а ЛЕЖАТ здесь: к ним обращаются ТРИ банка —
|
||||||
|
* pop_ctrl (банк 5) пишет с клавиатуры, guards.c (банк 1) подаёт
|
||||||
|
* синтетический ввод ИИ (autocontrol_*, seg002), pop_map (банк 3) читает
|
||||||
|
* через pop_ctrl_shift_held. Сегодня оно уцелело бы и в pop_ctrl.c
|
||||||
|
* (собираем без --bank-data, данные банков остаются в _DATA), но это
|
||||||
|
* флаг сборки: включат его — и шина уедет в страницу банка 5.
|
||||||
|
*
|
||||||
|
* control_x/y/shift — «сырое» состояние осей ЭТОГО кадра (абсолютно:
|
||||||
|
* X LEFT/-1..RIGHT/+1); *_forward/backward/up/down/shift2 — конечный
|
||||||
|
* автомат held/released/ignore (авто-повтор). */
|
||||||
|
int8_t control_x, control_y, control_shift;
|
||||||
|
int8_t control_forward, control_backward, control_up, control_down, control_shift2;
|
||||||
|
|
||||||
/* Чит «бессмертие» (клавиша I). Отладочный: Кид получает анимацию удара,
|
/* Чит «бессмертие» (клавиша I). Отладочный: Кид получает анимацию удара,
|
||||||
* но HP не теряет — иначе проверять боёвку приходится с трёх попаданий. */
|
* но HP не теряет — иначе проверять боёвку приходится с трёх попаданий. */
|
||||||
uint8_t pop_immortal;
|
uint8_t pop_immortal;
|
||||||
|
|||||||
@@ -49,11 +49,18 @@ __sfr __at (0xFE) io_border;
|
|||||||
|
|
||||||
/* Сколько банков грузит crt0_banked (читает его стартовый код).
|
/* Сколько банков грузит crt0_banked (читает его стартовый код).
|
||||||
* 1 = guards (ИИ/боёвка), 2 = pop_bg (фон, fore, loose), 3 = pop_map
|
* 1 = guards (ИИ/боёвка), 2 = pop_bg (фон, fore, loose), 3 = pop_map
|
||||||
* (коллизия/физика), 4 = pop_gdraw (отрисовка стража + полоса HP).
|
* (коллизия/физика), 4 = pop_gdraw (отрисовка стража + полоса HP),
|
||||||
|
* 5 = pop_ctrl (диспетчер управления).
|
||||||
* Отрисовка стража вынесена из банка 2 отдельно: тот подошёл к 16 КБ
|
* Отрисовка стража вынесена из банка 2 отдельно: тот подошёл к 16 КБ
|
||||||
* вплотную (16 021 из 16 384), а расти ему ещё есть куда — тайлы поздних
|
* вплотную (16 021 из 16 384), а расти ему ещё есть куда — тайлы поздних
|
||||||
* уровней, чомперы, зеркало. */
|
* уровней, чомперы, зеркало.
|
||||||
const uint8_t n_banks = 4;
|
*
|
||||||
|
* ДЕРЖАТЬ В СОГЛАСИИ с числом --bank в Makefile. Забыть — это не ошибка
|
||||||
|
* сборки, а зависание: crt0 выделит меньше страниц, _bank_pages[N] так и
|
||||||
|
* останется нулём, и трамплин прыгнет по 0xC000 в мусор (см. шапку
|
||||||
|
* runtime/bank.s). Проверено 2026-08-05: с n_banks=4 и пятым банком игра
|
||||||
|
* доходила до конца загрузки ресурсов и вставала намертво в DSS. */
|
||||||
|
const uint8_t n_banks = 5;
|
||||||
|
|
||||||
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
||||||
uint8_t pop_cheats;
|
uint8_t pop_cheats;
|
||||||
@@ -419,6 +426,7 @@ int main(void)
|
|||||||
uint8_t nav_prev = 0;
|
uint8_t nav_prev = 0;
|
||||||
#endif
|
#endif
|
||||||
uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0, nlvl_prev = 0;
|
uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0, nlvl_prev = 0;
|
||||||
|
uint8_t xdec_prev = 0, xinc_prev = 0;
|
||||||
|
|
||||||
if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */
|
if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */
|
||||||
puts("pop_bg_load failed");
|
puts("pop_bg_load failed");
|
||||||
@@ -505,21 +513,35 @@ int main(void)
|
|||||||
* тестировать уровни 2..15. NB: L без Shift — отладочный
|
* тестировать уровни 2..15. NB: L без Shift — отладочный
|
||||||
* «осторожный шаг вправо» (pop_ctrl KBD_DBG_STEPR); с
|
* «осторожный шаг вправо» (pop_ctrl KBD_DBG_STEPR); с
|
||||||
* Shift шаг тоже пройдёт, но уровень тут же сменится, так
|
* Shift шаг тоже пройдёт, но уровень тут же сменится, так
|
||||||
* что конфликта на практике нет. */
|
* что конфликта на практике нет;
|
||||||
|
* [ / ] — подвинуть Кида на ПИКСЕЛЬ влево/вправо (seg000:1828,
|
||||||
|
* `--Char.x` / `++Char.x`). Нужен потому, что мост MAME
|
||||||
|
* теряет нажатия при быстрой отправке, и подогнать Кида в
|
||||||
|
* нужную позу скриптом нельзя (на это упёрлись BUG-LOOSE-2
|
||||||
|
* и BUG-GATE-PASS-1). Пишем прямо в Kid.x: геометрию
|
||||||
|
* персонажа в этом порте задаёт не диспетчер (грабли
|
||||||
|
* L1-EXIT). Колонку не трогаем — её пересчитает физика
|
||||||
|
* следующего кадра. */
|
||||||
uint8_t k = kbd_raw_down(KBD_CHEAT_KILL);
|
uint8_t k = kbd_raw_down(KBD_CHEAT_KILL);
|
||||||
uint8_t i = kbd_raw_down(KBD_CHEAT_IMMO);
|
uint8_t i = kbd_raw_down(KBD_CHEAT_IMMO);
|
||||||
uint8_t s = kbd_raw_down(KBD_CHEAT_SWORD);
|
uint8_t s = kbd_raw_down(KBD_CHEAT_SWORD);
|
||||||
uint8_t nl = (uint8_t)(kbd_raw_down(KBD_CHEAT_NEXTLVL) &&
|
uint8_t nl = (uint8_t)(kbd_raw_down(KBD_CHEAT_NEXTLVL) &&
|
||||||
(kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT)));
|
(kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT)));
|
||||||
|
uint8_t xd = kbd_raw_down(KBD_CHEAT_XDEC);
|
||||||
|
uint8_t xi = kbd_raw_down(KBD_CHEAT_XINC);
|
||||||
if (pop_cheats && k && !cheat_prev) pop_guard_kill();
|
if (pop_cheats && k && !cheat_prev) pop_guard_kill();
|
||||||
if (pop_cheats && i && !immo_prev) pop_immortal = !pop_immortal;
|
if (pop_cheats && i && !immo_prev) pop_immortal = !pop_immortal;
|
||||||
if (pop_cheats && s && !sword_prev) pop_have_sword = 1;
|
if (pop_cheats && s && !sword_prev) pop_have_sword = 1;
|
||||||
if (pop_cheats && nl && !nlvl_prev && pop_current_level < POP_LAST_LEVEL)
|
if (pop_cheats && nl && !nlvl_prev && pop_current_level < POP_LAST_LEVEL)
|
||||||
pop_next_level = (uint8_t)(pop_current_level + 1);
|
pop_next_level = (uint8_t)(pop_current_level + 1);
|
||||||
|
if (pop_cheats && xd && !xdec_prev) Kid.x = (uint8_t)(Kid.x - 1);
|
||||||
|
if (pop_cheats && xi && !xinc_prev) Kid.x = (uint8_t)(Kid.x + 1);
|
||||||
cheat_prev = k;
|
cheat_prev = k;
|
||||||
immo_prev = i;
|
immo_prev = i;
|
||||||
sword_prev = s;
|
sword_prev = s;
|
||||||
nlvl_prev = nl;
|
nlvl_prev = nl;
|
||||||
|
xdec_prev = xd;
|
||||||
|
xinc_prev = xi;
|
||||||
}
|
}
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
PROF(0); /* чёрный: ждём vsync */
|
PROF(0); /* чёрный: ждём vsync */
|
||||||
@@ -530,8 +552,17 @@ int main(void)
|
|||||||
#ifdef ROOMNAV
|
#ifdef ROOMNAV
|
||||||
/* Обход комнат по номеру: '+' (0x79 numpad / 0x55 '=') — вперёд,
|
/* Обход комнат по номеру: '+' (0x79 numpad / 0x55 '=') — вперёд,
|
||||||
* '-' (0x7B / 0x4E) — назад. Edge по nav_prev: одно нажатие = одна
|
* '-' (0x7B / 0x4E) — назад. Edge по nav_prev: одно нажатие = одна
|
||||||
* комната. pop_trob_reset ДО enter_room — каждая комната смотрится в
|
* комната.
|
||||||
* исходном состоянии (пики убраны, ворота закрыты). */
|
*
|
||||||
|
* Здесь БЫЛ pop_trob_reset() перед enter_room — «чтобы каждая комната
|
||||||
|
* смотрелась в исходном состоянии». Убран: reset обнуляет room_seen,
|
||||||
|
* и модификаторы перечитываются из уровня заново, то есть чит
|
||||||
|
* ОТМАТЫВАЕТ МИР — открытые/закрытые ворота, выдвинутые пики, нажатые
|
||||||
|
* кнопки. Пока ворота с bg=1 ошибочно стартовали закрытыми, откат был
|
||||||
|
* не виден; после порта load_alter_mod он выглядит как баг «ворота
|
||||||
|
* снова открылись» (уровень 1, решётка комнаты 5 (0,9), которую
|
||||||
|
* закрывает стартовый триггер do_startpos). Навигация обязана только
|
||||||
|
* телепортировать; исходное состояние даёт перезапуск уровня. */
|
||||||
{
|
{
|
||||||
uint8_t nav = (uint8_t)((kbd_raw_down(0x79) || kbd_raw_down(0x55)) |
|
uint8_t nav = (uint8_t)((kbd_raw_down(0x79) || kbd_raw_down(0x55)) |
|
||||||
((kbd_raw_down(0x7B) || kbd_raw_down(0x4E)) << 1));
|
((kbd_raw_down(0x7B) || kbd_raw_down(0x4E)) << 1));
|
||||||
@@ -539,7 +570,6 @@ int main(void)
|
|||||||
uint8_t r = cur_room, i, c, pos = 20, any = 0xFF;
|
uint8_t r = cur_room, i, c, pos = 20, any = 0xFF;
|
||||||
r = (nav & 1) ? (uint8_t)(r == 24 ? 1 : r + 1)
|
r = (nav & 1) ? (uint8_t)(r == 24 ? 1 : r + 1)
|
||||||
: (uint8_t)(r == 1 ? 24 : r - 1);
|
: (uint8_t)(r == 1 ? 24 : r - 1);
|
||||||
pop_trob_reset();
|
|
||||||
enter_room(r);
|
enter_room(r);
|
||||||
/* Предпочитаем обычный пол; иначе — любой tile_is_floor
|
/* Предпочитаем обычный пол; иначе — любой tile_is_floor
|
||||||
* (seg001): всё, кроме пустоты/стены/верхов колонны и двери. */
|
* (seg001): всё, кроме пустоты/стены/верхов колонны и двери. */
|
||||||
|
|||||||
@@ -61,6 +61,35 @@
|
|||||||
|
|
||||||
## Auto-banking (memory/banking_roadmap.md)
|
## Auto-banking (memory/banking_roadmap.md)
|
||||||
|
|
||||||
|
- [ ] **Сверять `n_banks` с реальным числом банков — ошибкой сборки.**
|
||||||
|
Сейчас число банков задаётся ДВАЖДЫ и вручную: флагами `--bank N=…`
|
||||||
|
у `sprinter-cc` и константой `const uint8_t n_banks = N;` в
|
||||||
|
приложении (её читает `crt0_banked` при старте, чтобы выделить
|
||||||
|
EMM-страницы и загрузить в них образы банков). Рассогласование не
|
||||||
|
даёт ни ошибки компиляции, ни ошибки линковки — только зависание в
|
||||||
|
рантайме: `_bank_pages[N]` остаётся нулём, трамплин отображает в
|
||||||
|
окно страницу 0 и прыгает по 0xC000 в мусор (предупреждение об этом
|
||||||
|
уже есть в шапке `runtime/bank.s`).
|
||||||
|
**Найдено 2026-08-05** на PoP/roomtest: добавили пятый банк
|
||||||
|
(`--bank 5=pop_ctrl.c`), забыли `n_banks` — игра доходила до конца
|
||||||
|
загрузки ресурсов и вставала намертво в дисковом коде DSS. Диагноз
|
||||||
|
занял заметно больше, чем сама правка: симптом (зависание в чужом
|
||||||
|
коде) никак не указывает на причину.
|
||||||
|
Сверять надо не количество, а **максимальный индекс** банка: `--bank`
|
||||||
|
нумеруются с 1 и в принципе могут идти с дырами, а `crt0_banked`
|
||||||
|
заполняет `_bank_pages[1..n_banks]` подряд.
|
||||||
|
Варианты, от простого к правильному:
|
||||||
|
1. **Проверка после линковки** — вытащить значение `_n_banks` из
|
||||||
|
`.ihx`/`.map` (это `const` в `_CODE`, адрес известен из карты) и
|
||||||
|
сравнить с `max(N)` по флагам; не совпало — `sprinter-cc` падает
|
||||||
|
с внятным сообщением. Ничего не ломает в существующих
|
||||||
|
приложениях, ставится сразу.
|
||||||
|
2. **Генерировать самим `sprinter-cc`** — он уже так делает, когда
|
||||||
|
банков нет (`const unsigned char n_banks = 0;`). Правильнее по
|
||||||
|
сути (единственный источник истины), но требует убрать константу
|
||||||
|
из всех банковых приложений, иначе дубль символа: `tests/banked`,
|
||||||
|
`banktest`, `banklocl`, `bankedbg`, `applications/PoP/roomtest`.
|
||||||
|
|
||||||
Phase 1 — file-level bin-packing (`toolchain/auto_bank.py`) — когда
|
Phase 1 — file-level bin-packing (`toolchain/auto_bank.py`) — когда
|
||||||
проект перерастёт ~30 KB кода: парсинг размеров из .rel/.map,
|
проект перерастёт ~30 KB кода: парсинг размеров из .rel/.map,
|
||||||
first-fit-decreasing, уважение `#pragma codeseg BANKn`, перелинковка,
|
first-fit-decreasing, уважение `#pragma codeseg BANKn`, перелинковка,
|
||||||
|
|||||||
Reference in New Issue
Block a user