Резидент: pop_kid/pop_guard/main расколоты по частоте вызова (куча 6669 Б)

MEM-COLD3, шаги 3-5.  Кандидаты выбирались не по размеру, а по тому, КТО
зовёт: если единственный вызывающий уже в банке, код едет к нему и трамплин
не появляется вовсе.

* pop_kdraw.c -> БАНК 4 (к pop_cdraw.c): клинок в руке (pop_sword_draw) и
  блит спрайта chtab_2 по id (pop_kid_img_blit).  Оба звал ровно pop_cdraw,
  теперь вызовы прямые — контракт и условие корректности в _pop_kdraw.h.
* pop_kboot.c -> БАНК 8: загрузка атласов Кида и страницы kid_data.bin
  (раз за игру).  Дескриптор страницы — _pop_kid.h.
* pop_guard_cold.c -> БАНК 8: страж идёт за Кидом + вход стража в комнату.
  Оба зовёт roomtest_cold.c из того же банка — снова без трамплина.
* Из main в банк 8 уехали pop_boot (загрузка ресурсов, палитра, первый
  экран), pop_level_switch (смена уровня) и pop_shutdown.  FIRST_LEVEL
  переехал в pop_tune.h — константа нужна обеим половинам цикла.

_CODE 23005 -> 19861, куча 3525 -> 6669 Б.  Банк 8 — 6.5 из 16 КБ.
tests-host 6/6 (в наборы добавлены eng_pop_kboot и eng_pop_guard_cold).
This commit is contained in:
2026-08-12 22:03:34 +03:00
parent 5d31ed086f
commit 147cb185b1
15 changed files with 603 additions and 424 deletions
+165
View File
@@ -0,0 +1,165 @@
/*
* pop_guard_cold.c — переходы стража МЕЖДУ КОМНАТАМИ (банк 8): страж идёт
* за Кидом (follow_guard, seg002:039E) и вход стража в комнату
* (enter_guard, seg002:0112 + pos_guards, seg003).
*
* Обе зовутся РАЗ НА КОМНАТУ и обе — из roomtest_cold.c, который лежит в
* том же банке 8: значит в резиденте им делать нечего, а трамплин на
* вызове не появляется (объявления в pop_guard.h без __banked; условие
* корректности — оба модуля в одном банке, см. _pop_kdraw.h про ту же
* связку). Состояние стража (Guard, guard_skill, …) живёт в pop_guard.c
* и видно отсюда как обычные глобалы: данные банков — в общем _DATA.
*/
#include <stdint.h>
#include "pop_guard.h"
#include "pop_geom.h"
#include "pop_level.h"
#include "pop_kid.h" /* Char/play_seq — интерпретатор общий */
#include "pop_map.h"
#include "pop_state.h"
#include "pop_tile.h" /* POP_TILE_DIV — колонка по X без деления */
#include "kid_data.h"
static int8_t guard_col_from_x(uint8_t xpos); /* определена ниже */
/* ---- Страж ИДЁТ ЗА КИДОМ в соседнюю комнату ------------------------- *
* Порт ветки стража из exit_room (seg002:0340..0382) + follow_guard
* (seg002:039E). Своим ходом персонаж слота Guard из комнаты не уходит
* (его физика гейтится x ∈ [44,211), см. pop_guard_phys_tick), но когда
* комнату меняет КИД, движок может перенести стража следом — именно так в
* оригинале страж из комнаты 3 оказывается в комнате 2 за отступающим
* Кидом.
*
* Условия оригинала, все обязательные:
* - страж есть, ЖИВ и с ВЫНУТЫМ МЕЧОМ (то есть идёт бой);
* - переход ВБОК: вверх и вниз оригинал не пускает никогда (там условия
* `curr_row >= 0` и `curr_row < 3` истинны всегда → leave_guard);
* - в целевой комнате НЕТ своего ЖИВОГО стража (иначе их стало бы два:
* проверка `guards_tile >= 30 || guards_seq_hi != 0` — «пусто или
* труп»);
* - страж прижат к тому краю, в который ушёл Кид: x < 91 при уходе
* влево, x >= 165 при уходе вправо.
*
* side — куда ушёл КИД (0 = left, 1 = right, 2 = up, 3 = down), то есть
* roomleave_result оригинала. Возврат: 1 — страж перенесён в new_room
* (вызывающий НЕ должен звать ни pop_guard_leave, ни pop_guard_enter),
* 0 — остаётся, всё как раньше. */
uint8_t pop_guard_follow(uint8_t new_room, uint8_t side)
{
uint8_t tile, color, skill;
int8_t dir;
if (Guard.charid == 0) return 0; /* dir_56_none */
if (Guard.alive >= 0 || Guard.sword != SWORD_2_DRAWN) return 0;
if (side > 1) return 0; /* только вбок */
/* «В целевой комнате уже есть живой страж» = страж по данным есть И это
* не запомненный труп (у трупа сохранена последовательность). */
if (pop_level_guard(new_room, &tile, &dir, &color, &skill) &&
pop_guard_state_seq(new_room) == 0) return 0;
if (side == 0) { if (Guard.x >= 91) return 0; } /* Кид ушёл влево */
else { if (Guard.x < 165) return 0; } /* Кид ушёл вправо */
/* follow_guard: сначала «стража нет» в ОБЕИХ комнатах (в оригинале
* guards_tile = 0xFF). Без этого он раздвоится: в старой комнате его
* поднимут из данных при возврате, а в новой — при следующем входе. */
pop_guard_state_save(Guard.room, 30, 0, 0, 0, 0, 0);
pop_guard_state_save(new_room, 30, 0, 0, 0, 0, 0);
/* goto_other_room (seg002:0486) для стража: комната + перенос X через
* шов (влево +140, вправо −140). Ряд при боковом переходе не меняется;
* колонку пересчитываем из новой X, как enter_guard. */
Guard.room = new_room;
Guard.x = (uint8_t)(side == 0 ? Guard.x + 140 : Guard.x - 140);
Guard.curr_col = guard_col_from_x(Guard.x);
return 1;
}
/* get_tile_div_mod_m7 (seg006:697): колонка комнаты по X персонажа —
* floor((xpos 7 58) / 14), где 58 = pop_x_bump[FIRST_ONSCREEN_COLUMN]. */
static int8_t guard_col_from_x(uint8_t xpos)
{
/* Таблицей (резидентная POP_TILE_DIV: −58 уже внутри значений) — тот же
* приём, что в tile_at_kid/get_tile_div_mod. Здесь он кроет ПОЧТИ всё:
* xpos — байт, так что медленный хвост остаётся только для xpos < 7. */
if (xpos >= 7) return POP_TILE_DIV[xpos - 7];
{
int16_t x = (int16_t)xpos - 7 - pop_x_bump[FIRST_ONSCREEN_COLUMN];
int16_t col = x / TILE_SIZEX;
if (x % TILE_SIZEX < 0) --col;
return (int8_t)col;
}
}
void pop_guard_enter(uint8_t room)
{
uint8_t tile, color, skill;
int8_t dir;
pop_guard_reset();
/* load_lev_spr (seg000:1112): tbl_guard_type == 1 — стражей на уровне
* НЕТ вообще (14/15), их графика даже не грузится. Без этой проверки
* на таком уровне полезли бы стражи из данных комнат. */
if (pop_level_guard_type(pop_current_level) < 0) return;
if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return;
/* Цвет — ДО отрисовки комнаты (как redraw_screen, seg003:255): слоты
* палитры общие на экран, и смена посреди кадра дала бы стража в новой
* палитре с полосой HP в старой. */
pop_guard_set_palette(color);
/* enter_guard (seg002:0112) + pos_guards (seg003): X считается из
* колонки тайла, а не берётся из файла. */
Guard.room = room;
Guard.curr_row = (int8_t)(tile / 10);
Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1];
Guard.x = (uint8_t)(pop_x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
Guard.curr_col = (int8_t)(tile % 10);
Guard.direction = dir;
/* seg002:196: на уровне со скелетами слот поднимает СКЕЛЕТА, и он
* встаёт сразу активным — с вынутым мечом (seq_63), а не в стойке
* покоя (seq_77). Стойку ставит ветка ниже, вместе с seq трупа. */
Guard.charid = (pop_level_guard_type(pop_current_level) == 2)
? CHARID_4_SKELETON : CHARID_2_GUARD;
Guard.sword = (Guard.charid == CHARID_4_SKELETON)
? SWORD_2_DRAWN : 0; /* sword_0_sheathed */
Guard.alive = -1;
Guard.action = 1; /* actions_1_run_jump */
Guard.fall_x = Guard.fall_y = 0;
/* enter_guard (seg002:214): мастерство из данных уровня, 12 градаций;
* всё, что вне диапазона, оригинал сводит к 3. */
guard_skill = (uint8_t)(skill < NUM_GUARD_SKILLS ? skill : 3);
/* get_guard_hp (seg002:54): extrastrength[skill] + tbl_guard_hp[уровень].
* extrastrength (seg002:38) ненулевой только у skill 4 (=1). */
guardhp_max = guardhp_curr =
(uint8_t)(pop_level_guard_hp(pop_current_level) + (guard_skill == 4 ? 1 : 0));
guardhp_delta = 0;
/* enter_guard (seg002:0208). Живого поднимаем стандартной стойкой; у
* ЗАПОМНЕННОГО МЁРТВОГО берём сохранённую последовательность — он лежит
* ровно там, где лёг (порт ветки guards_seq_hi != 0). */
{
uint16_t saved_seq = pop_guard_state_seq(room);
/* У ЗАПОМНЕННОГО трупа X берём сохранённую: pos_guards (seg003:620)
* пересчитывает её из колонки только при загрузке уровня, а дальше
* ею владеет leave_guard. Иначе тело при возврате прыгает в центр
* тайла. */
if (saved_seq) Guard.x = pop_guard_state_x(room);
/* ...и КОЛОНКУ пересчитываем из X (enter_guard, seg002:180:
* `Char.curr_col = get_tile_div_mod_m7(Char.x)`). Брать её из
* тайла нельзя: leave_guard сохраняет get_tilepos(0, row), то
* есть колонку 0 всегда. У живого стража это незаметно (X там
* сам считается из колонки), а у ЗАПОМНЕННОГО ТРУПА X
* настоящая, и curr_col оставался нулём при теле, лежащем в
* колонке 2. Тайл трупа шёл в обходе раньше тайла Кида, и Кид
* рисовался ПОВЕРХ тела вместо того, чтобы пройти за ним
* (BUG-DRAWORDER-1). Колонка нужна не только отрисовке — на
* неё смотрят коллизия и check_can_guard_see_kid. */
Guard.curr_col = guard_col_from_x(Guard.x);
pop_loadshad();
if (saved_seq) Char.curr_seq = saved_seq;
else pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE);
pop_saveshad();
pop_guard_play(); /* до первого кадра */
/* enter_guard (seg002:218): кадр смерти -> страж поднят ТРУПОМ. */
if (Guard.frame == 185 || Guard.frame == 177 || Guard.frame == 178) {
Guard.alive = 1;
guardhp_curr = 0;
}
}
}