Files
Sprinter-SDCC/applications/PoP/roomtest/roomtest_cold.c
T
snark13 361c9d9f78 Мышь уровня 8 + холодная половина главного цикла в банк 8
Спецсобытие уровня 8 (SDLPoP seg003:0545/0ABA, seg002:07EB): когда дверь
уровня открыта, а Кид остаётся в комнате 16, через 150 кадров приходит мышь,
пробегает справа налево по верхнему ряду, наступает на кнопку (0,7) —
решётка (0,3) открывается — и убегает.

Ассетов не потребовалось: кадры 186..188 идут по таблице Кида, их спрайты
(images 130..132) уже лежат в kid16.atl, отрисовка подхватывает мышь сама.

- guards.c: pop_check_mouse, autocontrol_mouse, ветки мыши в
  autocontrol_opponent / play_guard / check_can_guard_see_kid;
- pop_leveldoor_open стал word, как в оригинале (в него же считается
  задержка — байт переполнился бы через 255 кадров);
- leave_guard больше не записывает тень и мышь в данные уровня (seg002:02F5);
- полосы HP у мыши нет (seg000:1159);
- tests-host/t_mouse: 17 проверок, единственный набор с guards.c.

Разгрузка резидента (куча в huge упала до 879 Б): pop_start_level,
find_start_level_door, чит-навигация по комнатам и лейбл номера уехали в
roomtest_cold.c (--bank 8, n_banks = 8).  _CODE 25653 -> 24825, куча -> 1707 Б;
кадровый путь не тронут.

Проверено: tests-host 6/6, make size-check OK, живьём в MAME (мышь появилась,
нажала кнопку, решётка поехала вверх, мышь ушла).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:21:20 +03:00

250 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* roomtest_cold.c — ХОЛОДНАЯ половина главного цикла, собирается в БАНК
* (--bank 8=roomtest_cold.c, окно W3). Здесь всё, что отрабатывает раз на
* уровень или только по отладочной клавише; кадровый путь остался в
* roomtest.c. Зачем разделили и что осталось снаружи — см. roomtest_cold.h.
*
* Правила банка те же, что у guards.c: состояние — в W1/W2 (данные этого
* модуля тоже, --bank-data не включён), звать можно резидент W1/W2 и другие
* __banked-модули через трамплины.
*/
#include <graphics.h>
#include <gfx.h>
#include "roomtest_cold.h"
#include "pop_bg.h" /* pop_loose_mob_reset — банк 7, зовём через трамплин */
#include "pop_kid.h"
#include "pop_map.h"
#include "pop_level.h"
#include "pop_trob.h"
#include "pop_guard.h"
#include "pop_state.h"
#include "pop_geom.h"
#include "pop_tune.h"
#define SEQ_STAND 2
#define SEQ_TURN 5
#define SEQ_FALL 7
#define SEQ_RUN 84
#define ENTRY_POSE_FALL 1 /* tbl_entry_pose[1] */
#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */
#define TILE_LEVELDOOR_LEFT 16 /* tiles_16_level_door_left */
#define TILE_EMPTY 0
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
/* find_start_level_door (seg003:02E6): дверь уровня В СТАРТОВОЙ КОМНАТЕ —
* это дверь, через которую Кид ВОШЁЛ, поэтому она открыта полностью
* (modif = 43) и заводится trob'ом типа 3 (start_level_door, seg007:0D72).
* Без этого на уровне 2 Кид материализуется внутри глухой закрытой створки:
* его стартовый тайл (комната 5, ряд 1, колонка 3) — правая половина
* той самой двери. */
static void find_start_level_door(uint8_t room)
{
uint8_t *m = pop_trob_modif(room);
uint8_t tp;
if (!m) return;
/* Батч-доступ к странице уровня тут не нужен: функция отрабатывает раз
* за уровень, а pop_add_trob сам лезет в W0. */
for (tp = 0; tp < 30; tp++) {
if (pop_level_tile(room, tp) != TILE_LEVELDOOR_LEFT) continue;
m[tp] = 43; /* створка поднята полностью */
pop_add_trob(room, tp, 3); /* ...и тут же поедет ВНИЗ (type>=3) */
}
}
/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos
* (seg003:028A).
*
* Позиция берётся ИЗ ДАННЫХ уровня (pop_level_start_*), а не из #define:
* start_pos — тайл 0..29 (row*10+col), start_dir — направление, в котором
* персонаж должен ОКАЗАТЬСЯ.
*
* Направление ИНВЕРТИРУЕТСЯ (`Char.direction = ~level.start_dir`): персонаж
* ставится спиной и доворачивается кадрами позы входа. Сама поза — из
* tbl_entry_pose (data.h:848 = {0,1,0,…,2,…}):
* 1 (уровень 1) — падение внутрь (seq_7_fall) + нажатие кнопки room5(0,2);
* это оно захлопывает решётку за спиной в первой комнате;
* 2 (уровень 13) — вбегание (seq_84_run);
* иначе — разворот на месте (seq_5_turn).
* Таблица индексируется НОМЕРОМ УРОВНЯ — pop_level_entry_pose(). */
void pop_start_level(void) __banked
{
uint8_t entry_pose, start_room, pos, seq = SEQ_TURN, chkp;
int8_t dir;
pop_leveldoor_open = 0; /* seg003:97 — дверь прошлого уровня не в счёт */
pop_feather = 0; /* seg003:189 — перо не переживает старт уровня */
entry_pose = pop_level_entry_pose(pop_current_level);
start_room = pop_level_start_room();
pos = pop_level_start_pos();
dir = (int8_t)~pop_level_start_dir();
/* Особое событие: старт с чекпойнта (do_startpos, seg003:0141).
* Числа — в pop_tune.h. Плиту снимаем ПОСЛЕ pop_level_reset_tiles()
* ниже, иначе рестарт её вернёт. */
chkp = (uint8_t)(pop_current_level == POP_CHKP_LEVEL && pop_checkpoint);
if (chkp) {
start_room = POP_CHKP_RESPAWN_ROOM;
pos = POP_CHKP_RESPAWN_TILEPOS;
dir = (int8_t)~POP_CHKP_RESPAWN_DIR; /* стартует в ПРОТИВОположную
* и разворачивается seq_5 */
}
/* Рестарт уровня = load_level() заново (play_level, seg003:57): в
* исходное возвращаются И модификаторы (пики/ворота), И сами тайлы —
* разбитые плиты целы, выпитые зелья на месте (BUG-RESPAWN-1), и
* убитые стражи снова живы на своих тайлах (BUG-RESPAWN-2). */
pop_trob_reset();
pop_level_reset_tiles();
pop_level_reset_guards();
pop_guard_reset(); /* start_level: Guard.direction = none */
pop_loose_mob_reset(); /* start_level: mobs_count = 0 (seg003:88) */
/* «Особое событие: убрать loose-плиту» (do_startpos): без этого путь в
* комнату-чекпойнт после респавна остался бы непройденным. */
if (chkp)
pop_level_set_tile(POP_CHKP_CLEAR_ROOM,
(uint8_t)(POP_CHKP_CLEAR_ROW * 10 + POP_CHKP_CLEAR_COL),
TILE_EMPTY);
/* Кнопку жмём ДО отрисовки комнаты — как в оригинале (do_startpos
* отрабатывает раньше первого draw_room), иначе шов кадр показывал бы
* ещё не тронутое состояние решётки. */
if (entry_pose == ENTRY_POSE_FALL) {
/* get_tile(5,2,0) + trigger_button(0,0,-1) — «нажать» кнопку room5,
* col2, row0 её же типом и модификатором (в оригинале и то и другое
* берётся из curr_* после get_tile). */
uint8_t tp = 2; /* row0*10 + col2 */
pop_trigger_button(5, tp, pop_level_tile(5, tp), pop_trob_modif(5)[tp]);
seq = SEQ_FALL;
} else if (entry_pose == ENTRY_POSE_RUN) {
seq = SEQ_RUN;
}
/* Тоже ДО отрисовки комнаты: дверь, через которую вошли, обязана быть
* открыта уже в первом кадре (в оригинале find_start_level_door стоит
* между do_startpos и draw_level_first). */
find_start_level_door(start_room);
enter_room(start_room);
kid_init(seq, (int8_t)(pos % 10), (int8_t)(pos / 10), dir);
/* Спецсобытие «вход падением» (set_start_pos, seg003:0196): на 7-м
* уровне Кид ставится в комнату 17, а экран тут же переводится на
* комнату ПОД ней (`goto_other_room(3)`: y = 189, ряд пересчитать) —
* он влетает в неё сверху, продолжая падение с шестого уровня. */
if (pop_current_level == FALLING_ENTRY_LEVEL &&
start_room == FALLING_ENTRY_ROOM) {
uint8_t below = pop_room_link(start_room, LINK_DOWN);
if (below) {
Kid.y = (uint8_t)(Kid.y - 189);
Kid.curr_row = pop_y_to_row((int16_t)Kid.y);
enter_room(below);
}
}
Kid.sword = 0; /* set_start_pos: sword_0_sheathed */
/* play_level (seg003:106): have_sword = level >= have_sword_from_level
* (=2) — начиная со второго уровня меч у Кида уже есть. */
pop_have_sword = (uint8_t)(pop_current_level >= 2);
pop_kid_hp_reset();
}
/* ---- ОТЛАДКА: обход комнат по номеру -------------------------------- *
*
* Нужен, чтобы пройти все комнаты уровня и собрать список багов отрисовки,
* включая недостижимые обычным путём.
*
* Здесь БЫЛ pop_trob_reset() перед enter_room — «чтобы каждая комната
* смотрелась в исходном состоянии». Убран: reset обнуляет room_seen, и
* модификаторы перечитываются из уровня заново, то есть чит ОТМАТЫВАЕТ МИР
* — открытые/закрытые ворота, выдвинутые пики, нажатые кнопки. Навигация
* обязана только телепортировать; исходное состояние даёт перезапуск. */
uint8_t pop_dbg_roomnav(uint8_t dir) __banked
{
uint8_t r, i, c, pos = 20, any = 0xFF;
/* Кид МЁРТВ: сначала честный рестарт уровня, потом телепорт. Навигация
* только переставляет Кида (kid_init + сброс HP), то есть «оживляет» его
* мимо start_level — а тот обязан вернуть тайлы, модификаторы и ОЧИСТИТЬ
* слот соперника (seg003:100: Guard.direction = dir_56_none). Без этого
* после смерти оставался живой скелет рядом с вернувшейся кучей костей. */
if (pop_kid_dead) pop_start_level();
r = cur_room;
r = (dir == 1) ? (uint8_t)(r == 24 ? 1 : r + 1)
: (uint8_t)(r == 1 ? 24 : r - 1);
enter_room(r);
/* Куда поставить Кида в новой комнате: пол С МАКСИМАЛЬНЫМ Y — сначала
* НИЖНИЙ ряд (2), потом 1, и только потом 0 (пользователь 2026-08-11).
* Раньше брался первый пол подряд, то есть почти всегда (0,0) — верхний
* левый угол: это и «прилёт» в проём/ворота, и, на уровне 5, посадка
* вплотную к тени спецсобытия, которая ждёт в колонке −1 ряда 0. Вместо
* сцены кражи зелья начиналась схватка, которой в этой комнате быть не
* должно. Снизу вверх нужно и самой навигации: в верхнем ряду пола чаще
* нет, и Кид проваливается из комнаты сразу после телепорта.
*
* ИСКЛЮЧЕНИЕ — комната 14 уровня 7 (пользователь 2026-08-12): её проходят
* СВЕРХУ ВНИЗ (с ряда 0 на ряд 2 через зацеп за кромки), и с нижнего ряда
* этот спуск не проверить вовсе. Пол в ряду 0 там есть, так что Кид не
* проваливается.
*
* В каждом ряду сначала ищем ОБЫЧНЫЙ пол, и только если его там нет —
* любой проходимый tile_is_floor (seg001: всё, кроме пустоты, верха
* колонны, верха двери, стены и «половинок» 26..29). */
{
int8_t row, step = -1, first = 2;
if (pop_current_level == GRAB_DOWN_LEVEL &&
r == GRAB_DOWN_ROOM) { first = 0; step = 1; }
for (row = first; row >= 0 && row <= 2 && any == 0xFF;
row = (int8_t)(row + step)) {
uint8_t fallback = 0xFF;
for (i = (uint8_t)(row * 10); i < (uint8_t)(row * 10 + 10); i++) {
c = (uint8_t)(room_fg[i] & 0x1F);
if (c == 1) { any = i; break; }
if (fallback == 0xFF && c && c != 9 && c != 12 &&
c != 20 && !(c >= 26 && c <= 29)) fallback = i;
}
if (any == 0xFF) any = fallback;
}
}
if (any != 0xFF) pos = any;
kid_init(SEQ_STAND, (int8_t)(pos % 10), (int8_t)(pos / 10), 0);
pop_kid_hp_reset();
/* ВЫХОД ИЗ БОЯ (BUG-CHEAT-FIGHT-1). Телепорт с вынутым мечом заклинивал
* управление намертво: pop_control при Kid.sword == SWORD_2_DRAWN уходит
* в control_with_sword, а там единственный выход — ветка «убрать меч» на
* кадре FRAME_171_STAND_WITH_SWORD. После kid_init(SEQ_STAND) кадр
* обычный, стража в новой комнате нет, swordfight не запускается — и
* диспетчер каждый кадр не делает НИЧЕГО. Это болезнь ЧИТА: в оригинале
* телепорта между комнатами нет, попасть в режим боя без соперника
* нечем. Меч в инвентаре (pop_have_sword) НЕ теряем — только состояние
* схватки. */
Kid.sword = SWORD_0_SHEATHED;
holding_sword = 0;
offguard = 0;
guard_refrac = 0;
return 1;
}
/* ---- ОТЛАДКА: номер отрисованной комнаты в верхнем борте ------------- */
static uint8_t room_label_sig = 0xFF; /* последняя показанная комната */
static uint8_t room_label_redraw; /* остаток кадров (2 = обе страницы) */
void pop_dbg_room_label(uint8_t room) __banked
{
uint8_t prev = room_label_sig, i;
room_label_sig = room; /* СНАЧАЛА сохранить (sdcc_z80_cmp_store_a_bug) */
if (room != prev) room_label_redraw = 2;
if (!room_label_redraw) return;
/* Номер ПАЛОЧКАМИ, а не outtextxy: текст тянет системный знакогенератор
* (_gfx_font_buf, 2 КБ статики в W2) и сажает кучу. Слева десятки,
* справа единицы: || |||| = 24. */
setfillstyle(SOLID_FILL, BLACK);
bar(100, 0, 175, 5);
/* 0x57, а НЕ WHITE: палитра игровая, запись 15 в ней чёрная
* (kid.pal: 0x0F = 0,0,0; чистый белый = 0x57). */
setfillstyle(SOLID_FILL, 0x57);
for (i = 0; i < (uint8_t)(room / 10); i++) bar(100 + i * 5, 0, 101 + i * 5, 5);
for (i = 0; i < (uint8_t)(room % 10); i++) bar(130 + i * 5, 0, 131 + i * 5, 5);
room_label_redraw--;
}