Files
Sprinter-SDCC/applications/PoP/roomtest/pop_guard.h
T
snark13 67d62e3e8b Уровни 12 и 13: тень, Джафар, падающие плиты + разбор слоёв
Уровень 12 (тень) — порт seg002/seg006:
- check_shadow: подъём тени в комнате 15 по условию «меч подобран»,
  init_shad_12, вход падением (seq 7);
- autocontrol_shadow_level12: ждать Кида, бой, сближение, СЛИЯНИЕ;
- общий урон (ранил тень — ранил себя) и check_killed_shadow
  (убил тень — убил себя);
- таймер вспышки слияния 42 -> -1, sword_disappears при уходе из
  комнаты 18, появление плит в комнатах 2/13 после слияния.

Переход 12 -> 13 бесшовный: двери у уровня нет, он кончается фактом
попадания в комнату 23 (tbl_seamless_exit); флаг pop_seamless не даёт
сбросить HP.  Чит навигации по комнатам этот триггер придерживает —
иначе комнату 23 двенадцатого уровня не посмотреть в принципе.

Уровень 13 (Джафар): guard_notice_timer (фора после встречи),
on_guard_killed + Jaffar_exit, check_fall_flo (гряда плит сверху) и три
исключения loose-механики.  Плюс спрайты визиря VIZIER.DAT — без них он
рисовался обычным стражем; заодно цвет из данных комнаты применяется
только к обычному стражу, как в оригинале.

Падающие плиты — семь дефектов, найденных прогонами:
- MOB_MAX 4 -> 14: check_fall_flo роняет шесть плит разом, лишние
  терялись без щебня;
- одиночные сигналы посадки/провала больше не затираются в кадре;
- честный loose_fall: сбитая плита рождается в том же кадре, от места
  удара, с половинной скоростью;
- при снятии плиты метится и сосед справа (висел передний торец);
- heal и отрисовка кусков разнесены на два прохода;
- куски рисуются ПОСЛЕ фона, порядок между собой — по убыванию y
  (compare_curr_objs для пары 0x80 сортирует наоборот);
- wall_pattern убран из ceil_over_kid_tile: у оригинала узор из
  draw_tile_bottom идёт в фон, а не в передний слой.

Хост-тесты: два новых набора, t_shadow (45 проверок) и t_jaffar (44).
TK_ROOMS 8 -> 24 — спецсобытия адресуют комнаты по реальным номерам.

Осознанные расхождения (docs/impl_diff.md): нет мигания Кида спрайтами
тени при слиянии, чит навигации не запускает бесшовный переход, чит K
убивает через штатный путь смерти.

Открытым остаётся разбор слоёв: MOB-CLIP-RIGHT, MID-OVERLAY-LAYER,
GUARD-FALLOUT-VICTORY, BG-ONCE (BUGS_OPEN.md / TASKS_OPEN.md).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 18:21:52 +03:00

372 lines
24 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.
/*
* pop_guard.h — СОСТОЯНИЕ стража и точки входа в его логику.
*
* Разделение по окнам (docs/layout_plan_v2.md §4, подтверждено пробником
* tests/w3bankgfx):
* - СОСТОЯНИЕ живёт здесь, в W1/W2. Писучие статики __banked-модуля
* линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк —
* только код;
* - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c;
* - ОТРИСОВКА стража — в pop_cdraw.c (банк 4), общая на всех Char: её зовёт
* только главный цикл, а из банка резидент недостижим. Так код
* отрисовки не занимает дефицитные W1/W2 (куча).
*
* Поля char_type — как в оригинале (types.h:302), чтобы порт seg005/seg006
* ложился один в один.
*/
#ifndef POP_GUARD_H
#define POP_GUARD_H
#include <stdint.h>
#include "kid_data.h" /* kframe */
#include "pop_char.h"
#include "pop_geom.h" /* pop_rnd_t — сид бросков боёвки */
/* seqids (types.h:1052+) — последовательности боя и стойки. */
#define SEQ_55_DRAW_SWORD 55
#define SEQ_56_GUARD_FORWARD 56 /* шаг вперёд с мечом (страж) */
#define SEQ_57_BACK_WITH_SWORD 57
#define SEQ_58_GUARD_STRIKE 58
#define SEQ_60_TURN_WITH_SWORD 60
#define SEQ_61_PARRY_AFTER_STRIKE 61
#define SEQ_62_PARRY 62
#define SEQ_66_STRIKE_AFTER_PARRY 66
#define SEQ_71_DYING 71
#define SEQ_75_STRIKE 75 /* удар (Кид) */
#define SEQ_63_GUARD_ACTIVE_AFTER_FALL 63 /* стойка АКТИВНОГО (скелет) */
#define SEQ_77_GUARD_STAND_INACTIVE 77
#define SEQ_88_SKEL_WAKE_UP 88 /* скелет встаёт (спецсобытие) */
#define SEQ_80_STAND_FLIPPED 80
#define SEQ_86_FORWARD_WITH_SWORD 86 /* шаг вперёд с мечом (Кид) */
#define SEQ_87_GUARD_BECOME_INACT 87
#define SEQ_90_EN_GARDE 90
#define SEQ_92_PUT_SWORD_AWAY 92
#define SEQ_93_PUT_SWORD_AWAY_FAST 93
/* frameids (types.h:911+) и состояния меча (types.h:332). */
#define FRAME_150_PARRY 150
#define FRAME_151_STRIKE_1 151
#define FRAME_152_STRIKE_2 152
#define FRAME_153_STRIKE_3 153
#define FRAME_157_WALK_WITH_SWORD 157
#define FRAME_158_STAND_WITH_SWORD 158
#define FRAME_161_PARRY 161
#define FRAME_162_BLOCK_TO_STRIKE 162
#define FRAME_165_WALK_WITH_SWORD 165
#define FRAME_166_STAND_INACTIVE 166
#define FRAME_167_BLOCKED 167
#define FRAME_168_BACK 168
#define FRAME_169_BEGIN_BLOCK 169
#define FRAME_170_STAND_WITH_SWORD 170
#define FRAME_171_STAND_WITH_SWORD 171
#define SWORD_0_SHEATHED 0
#define SWORD_2_DRAWN 2
/* charids (types.h:320) — нужны хотя бы для «скелет не убивается читом». */
#define CHARID_0_KID 0
#define CHARID_1_SHADOW 1
#define CHARID_2_GUARD 2
/* charid_3 не используется: Джафар (уровень 13) — обычный charid_2_guard,
* своим charid оригинал помечает ТОЛЬКО скелета (enter_guard, seg002:196).
* Отличают Джафара набор спрайтов (VIZIER.DAT), skill 9 и три события. */
#define CHARID_4_SKELETON 4
#define CHARID_5_PRINCESS 5
/* МЫШЬ (спецсобытие уровня 8) — тоже персонаж слота Guard: своей таблицы
* кадров у неё нет, кадры 186..188 лежат в таблице КИДА (load_frame,
* seg006:0293 — общий case с charid_0_kid), а спрайты (images 130..132) —
* в атласах Кида, страница kid16.atl. Поэтому отрисовка её подхватывает
* без единой правки: pop_frame_tbl_is_guard(24,…) отдаёт 0. */
#define CHARID_24_MOUSE 24
/* dir_56_none (types.h:402): «направления нет» — им clear_char убирает
* персонажа со сцены. Значение 0x56 в int8_t = 86, то есть заведомо не
* dir_0_right (0) и не dir_FF_left (-1). */
#define DIR_56_NONE 0x56
/* Спецсобытие «зеркало» (custom->mirror_*, дефолты SDLPoP): уровень 4,
* комната 4, колонка 4, верхний ряд. Здесь, а не в pop_trob.c: константы
* нужны и постановке тайла (банк 6), и ИИ тени (банк 1). */
#define MIRROR_LEVEL 4
#define MIRROR_ROOM 4
#define MIRROR_COL 4
#define MIRROR_ROW 0
#define MIRROR_TILEPOS (MIRROR_ROW * 10 + MIRROR_COL)
/* Спецсобытие «тень крадёт зелье» (custom->shadow_steal_*, дефолты SDLPoP):
* уровень 5, комната 24. Тень появляется при ВХОДЕ в комнату, если зелье
* (колонка 3, верхний ряд) ещё на месте; ждёт, пока откроются ворота
* (колонка 1, верхний ряд), идёт к зелью, выпивает его и уходит за левый
* край. Боя нет. Как и MIRROR_*, константы нужны в двух банках. */
#define SHADOW_STEAL_LEVEL 5
#define SHADOW_STEAL_ROOM 24
#define SHADOW_POTION_TILEPOS 3 /* (кол 3, ряд 0) — зелье */
#define SHADOW_DOOR_TILEPOS 1 /* (кол 1, ряд 0) — ворота */
#define SHADOW_DOOR_OPEN_MIN 80 /* openness, при которой тень идёт */
/* Спецсобытие «тень захлопывает решётку» (custom->shadow_step_*, дефолты
* SDLPoP): уровень 6, комната 1. Кид жмёт opener (1,7), решётка (1,2)
* поднимается, он прыгает через пропасть и цепляется за её порожек — и
* ровно в этот момент тень делает ОДИН осторожный шаг с границы (1,0)-(1,1)
* на closer (1,1), роняя решётку. Обе плиты комнаты ведут на тот же тайл
* 12 (проверено по LINKLOC/LINKMAP уровня). */
#define SHADOW_STEP_LEVEL 6
#define SHADOW_STEP_ROOM 1
/* Спецсобытие «выход/вход падением» (custom->falling_*, дефолты SDLPoP).
* Уровень 6 не заканчивается дверью: Кид проваливается вниз из комнаты 1 и
* этим переходит на уровень 7, где появляется в комнате 17 — но экран сразу
* переводится на комнату ПОД ней, так что он влетает сверху. Комната 3
* (шахта под комнатой 1) в переходе не участвует: leave_room перехватывает
* уход раньше, чем сработала бы связь вниз. */
#define FALLING_EXIT_LEVEL 6
#define FALLING_EXIT_ROOM 1
#define FALLING_ENTRY_LEVEL 7
#define FALLING_ENTRY_ROOM 17
/* Комната, которую проходят СВЕРХУ ВНИЗ спуском через зацеп: Кид висит на
* кромке кнопки (0,2), отпускает и цепляется за кромку (2,2). Нужна ЧИТУ
* навигации (`+`/`` в roomtest.c): он ставит Кида на нижний пол, а отсюда
* этот спуск не проверить — для этой комнаты старт в ряду 0. */
#define GRAB_DOWN_LEVEL 7
#define GRAB_DOWN_ROOM 14
/* Единственный уровень, где тень — БОЕВОЙ персонаж (seg003:702). На всех
* остальных (4 зеркало, 5 зелье, 6 плита) она Кида «не видит»: см.
* pop_check_can_guard_see_kid.
*
* Сцена целиком (seg002:0070 + seg002:1184): в комнате 15 лежит МЕЧ (0,1);
* пока он на месте, тень не появляется. Кид его берёт — тень падает в ту
* же комнату, и дальше либо бой (у обоих клинки), либо, если Кид уберёт
* меч, — слияние. Комната 18 при уходе ВПРАВО «съедает» меч с карты
* (sword_disappears), чтобы вернуться и подобрать его второй раз было
* нельзя. Проверено по res2012.bin, ../docs/levels_12_15_plan.md §7.3. */
#define SHADOW_FIGHT_LEVEL 12
#define SHADOW_FIGHT_ROOM 15
#define SHADOW_SWORD_TILEPOS 1 /* (кол 1, ряд 0) — меч уровня 12 */
#define SWORD_GONE_ROOM 18 /* уход ВПРАВО отсюда убирает меч */
/* Слияние (autocontrol_shadow_level12): дистанция, вспышка, её длительность. */
#define SHADOW_UNITE_DIST 10
#define SHADOW_UNITE_TIME 42
/* Уровень 13 — ДЖАФАР (custom->jaffar_*, дефолты SDLPoP). Своего ИИ у него
* нет вовсе: `autocontrol_Jaffar` (seg002:085A) — это `autocontrol_guard`,
* то есть обычный бой стражи. Отличают его только данные (VIZIER.DAT,
* skill 9, 6 HP — всё уже в pop_level_cold.c) и три события ниже.
*
* Встреча: Кид уходит ВПРАВО из комнаты 3 — а `комната 3.right = 1`, где
* Джафар и стоит (проверено по res2013.bin, ../docs/levels_12_15_plan.md
* §7.2). Ему даётся фора JAFFAR_NOTICE_TIME кадров: пока таймер идёт, он не
* встаёт в боевую стойку, и это единственная пауза во всей сцене.
*
* Выход: смерть Джафара сама дверь НЕ открывает — она ставит
* pop_leveldoor_open = 2, а дверь открывает КНОПКА комнаты 24 (tilepos 0,
* реальный opener), «нажимаемая» при уходе Кида ВЛЕВО. */
#define JAFFAR_LEVEL 13
#define JAFFAR_MEET_ROOM 3 /* уход ВПРАВО отсюда будит Джафара */
#define JAFFAR_NOTICE_TIME 28 /* ≈2,3 с форы (28/12) */
#define JAFFAR_EXIT_ROOM 24
#define JAFFAR_EXIT_TILEPOS 0
#define JAFFAR_FLASH_TIME 18
/* Градаций мастерства стража (seg002: таблицы вероятностей по 12 записей). */
#define NUM_GUARD_SKILLS 12
/* Тип персонажа — в pop_char.h (общий с Кидом и Char). */
extern pop_char_t Guard; /* страж ТЕКУЩЕЙ комнаты (как в оригинале) */
extern uint8_t guardhp_curr, guardhp_max;
extern int8_t guardhp_delta; /* <0 = в этом кадре отняли HP */
/* take_hp (seg006:06BD): отнять count HP у АКТИВНОГО персонажа (Char) —
* Киду из `hitp_*`, любому другому из `guardhp_*`. Возврат 1 = HP кончились
* (для вызывающего это «умер»); саму дельту применяет pop_do_delta_hp раз в
* кадр, как в оригинале.
*
* Живёт в резиденте W1/W2, потому что зовут ОБА банка: боёвка (guards.c,
* банк 1) и физика (pop_map.c, банк 3) — раньше у каждого была своя копия,
* причём у pop_map неверная (правила hitp_curr напрямую, мимо дельты, и не
* знала про не-Кидов). Чит бессмертия ловится здесь же. */
uint8_t pop_take_hp(uint8_t count);
/* Взводится в кадре, когда СТРАЖУ убавили HP (guardhp_delta < 0 оригинала):
* по нему pop_char_draw рисует «брызги» удара, как draw_guard в seg008:1654.
* Снимает главный цикл после отрисовки — стража могло не оказаться в
* отрисованной комнате, и тогда флаг обязан просто пропасть. */
extern uint8_t pop_guard_hurt;
/* Видит ли страж Кида (порт can_guard_see_kid, seg003:688):
* 0 — не видит, 1 — видит, но не пойдёт (между ними loose/чомпер/дыра),
* 2 — видит и пойдёт. Считает pop_check_can_guard_see_kid (банк) раз в
* кадр ДО логики персонажей; читают ИИ (autocontrol_*) и боёвка. */
extern int8_t can_guard_see_kid;
/* Кид «нашумел» (порт is_guard_notice, seg000:364): заставляет неактивного
* стража обернуться, даже если Кид у него за спиной. Ставит источник шума,
* снимает сам ИИ. */
extern int8_t is_guard_notice;
/* Боевые таймеры/уровень мастерства (seg000:295..311). Все убывают раз в
* кадр в autocontrol_opponent — как в оригинале.
* guard_skill — 0..11, из данных уровня; индекс в таблицы вероятностей;
* guard_refrac — «отдышка» стража: пока >0, он не атакует и не наступает;
* justblocked — Кид только что парировал (влияет на вероятность блока);
* kid_sword_strike — Кид только что бил (стража это придерживает). */
extern uint8_t guard_skill;
extern int8_t guard_refrac, justblocked, kid_sword_strike;
/* Состояние меча КИДА (seg005/seg006):
* offguard — меч убран НАМЕРЕННО (вниз в бою): пока стоит, Кид не
* достаёт клинок автоматически, даже видя стража;
* holding_sword — клинок в руке (для индикатора HP стража). */
extern int8_t offguard, holding_sword;
/* Спецсобытие уровня 12 — СЛИЯНИЕ С ТЕНЬЮ (united_with_shadow, seg000:288).
* Значение читается как три разных состояния, и это НЕ флаг:
* 0 — тень ещё отдельный персонаж (или уровень не 12);
* >0 — идёт вспышка слияния, счётчик кадров (42 → 1), убывает в
* pop_shadow_timer; пока он идёт, Кид мигает спрайтами тени;
* −1 — «уже слились», навсегда до конца уровня. Именно по нему
* check_shadow больше не поднимает тень, а в комнатах 2/13
* появляются недостающие плиты пола.
* Переход 0 → −1 идёт ЧЕРЕЗ счётчик: таймер, досчитав до нуля, уводит его
* ещё на единицу вниз (seg003:503) — отсюда и знаковый тип.
*
* pop_shadow_init (shadow_initialized, seg000:309) — «тень уже дождалась
* Кида в комнате 15»: до этого autocontrol_shadow_level12 роняет её заново
* каждый кадр, чтобы Кид застал падение, а не готовую стойку. */
extern int8_t pop_united_shadow;
extern uint8_t pop_shadow_init;
/* Сид бросков боёвки (свой домен последовательности, см. pop_geom.h). */
extern pop_rnd_t pop_fight_seed;
/* Луч видимости стража — раз в кадр, ДО тиков персонажей (в оригинале
* check_can_guard_see_kid стоит в play_frame перед play_kid_frame). */
void pop_check_can_guard_see_kid(void) __banked;
/* check_skel (seg002:1042) — СПЕЦСОБЫТИЕ уровня 3: скелета нет в данных
* уровня (guards_tile пуст во всех комнатах), он поднимается сам, когда Кид
* доходит до нужной колонки комнаты 1 при открытой двери уровня. Звать раз
* в кадр, ДО логики персонажей (порядок play_frame, seg000:868). */
void pop_check_skel(void) __banked;
/* Спецсобытие уровня 8 — МЫШЬ (do_timers seg003:0545 + do_mouse seg003:0ABA).
* Условие запуска: дверь уровня открыта И Кид в комнате 16; тогда счётчик
* pop_leveldoor_open растёт кадрами, и на 150-м мышь появляется у правого
* края верхнего ряда. Звать раз в кадр рядом с pop_check_skel. */
void pop_check_mouse(void) __banked;
/* check_shadow (seg002:0064) — СПЕЦСОБЫТИЕ входа в комнату: на «шадоу»-
* уровнях слот соперника занимает ТЕНЬ, а не страж из данных. Звать ВМЕСТО
* pop_guard_enter при входе в комнату; вернула 1 — событие обработано и
* стража поднимать НЕ надо.
*
* Тонкость оригинала, которую легко потерять: на уровне 5 в комнате 24
* страж В ДАННЫХ ЕСТЬ (`guards_tile[23] = 8`), но enter_guard там не
* вызывается НИКОГДА — ни когда тень поднимается, ни когда зелье уже
* выпито (в обоих случаях seg002:0064 делает return). Поэтому 1
* возвращается и во втором случае тоже. */
uint8_t pop_check_shadow(uint8_t room) __banked;
/* sword_disappears (seg002:0536) — спецсобытие уровня 12: уход ВПРАВО из
* комнаты 18 убирает меч из комнаты 15. Звать при уходе вбок ДО смены
* комнаты, рядом с чекпойнтом уровня 3. */
void pop_sword_disappears(void) __banked;
/* meet_Jaffar (seg002:0544) — уход ВПРАВО из комнаты 3 уровня 13 даёт
* Джафару фору; звать рядом с pop_sword_disappears (та же ветка leave_room).
* Jaffar_exit (seg002:0517) — уход ВЛЕВО при pop_leveldoor_open == 2 жмёт
* кнопку комнаты 24 и открывает дверь уровня. */
void pop_meet_jaffar(void) __banked;
void pop_jaffar_exit(void) __banked;
/* check_killed_shadow (seg006:2033) — спецсобытие уровня 12: смерть тени
* убивает Кида и наоборот. Звать раз в кадр ДО тиков персонажей, рядом с
* pop_check_skel; на прочих уровнях выходит первой строкой. */
void pop_check_killed_shadow(void) __banked;
/* timers (seg003:0735) — кадровые счётчики спецсобытий: вспышка слияния с
* тенью и фора Джафара. Звать раз в кадр из главного цикла. Резидент: два
* вычитания, банк ради них не разворачиваем. */
void pop_frame_timers(void);
/* guard_notice_timer (seg000:307) — фора Джафара после встречи: пока >0, он
* не встаёт в боевую стойку. Ставит pop_meet_jaffar, убавляет
* pop_frame_timers, читает autocontrol_guard_inactive. */
extern int8_t pop_guard_notice_timer;
/* Боёвка: пометить попадания (обе стороны) и применить их. Порядок в
* кадре как в play_frame: hurting -> hurt -> do_delta_hp. */
void pop_check_sword_hurting(void) __banked;
void pop_check_sword_hurt(void) __banked;
/* Текущий кадр стража (порт cur_frame для Char): заполняет логика в W1/W2,
* читает отрисовка в резиденте W3. image == 255 — рисовать нечего. */
extern kframe pop_gframe;
void pop_guard_load_frame(void);
/* check_guard_fallout (seg002:0241): убрать персонажа слота Guard, если он
* провалился ниже комнаты. Звать раз в кадр после физики и HP — как
* play_frame (seg000:884). */
void pop_guard_fallout(void);
/* Есть ли живой страж в отрисованной комнате (для главного цикла). */
uint8_t pop_guard_present(void);
/* Атласы стража (GUARD\\g0..g4.atl) — грузить ДО initgraph, как Kid.
* 0 — OK, -1 — ошибка (тогда страж просто не рисуется). */
int pop_guard_load(void) __banked;
/* Зеркальный ПО ВЕРТИКАЛИ набор соперника (файлы *_v.atl) — для зелья
* инверсии; зовётся из pop_vflip_load_all (pop_vflip.h). Живёт рядом с
* pop_guard_load: там же gp[] и выбор GUARD/SKEL. */
int pop_guard_vflip_load(void) __banked;
void pop_guard_free(void) __banked;
/* Вход в комнату: поднять стража из данных уровня (порт enter_guard,
* seg002:0112) либо убрать его, если в комнате стража нет. */
void pop_guard_enter(uint8_t room);
/* Запомнить стража УХОДЯЩЕЙ комнаты (порт leave_guard). Звать в начале
* enter_room, пока Guard ещё держит прежнего. */
void pop_guard_leave(void);
/* Страж идёт ЗА КИДОМ в соседнюю комнату — порт ветки стража из exit_room
* (seg002:0340) + follow_guard (seg002:039E). Звать ВМЕСТО пары
* leave/enter при смене комнаты: 1 = страж уже перенесён в new_room (ни
* leave, ни enter звать нельзя), 0 = остаётся в старой (всё как раньше).
* side — куда ушёл КИД: 0 left, 1 right, 2 up, 3 down. */
uint8_t pop_guard_follow(uint8_t new_room, uint8_t side);
/* Полоса HP (Кид слева, страж справа) в статус-полосе. Рисует главный цикл
* ПОСЛЕ персонажей — она ничего не перекрывает. */
void pop_hp_draw(void) __banked;
/* Полоса стёрта перерисовкой фона (вход в комнату) — обновить обе страницы. */
void pop_hp_invalidate(void) __banked;
/* Цвет стража — подменой 16 слотов палитры (порт set_chtab_palette,
* seg003:257): спрайты лежат в индексах POP_GUARD_PAL_BASE + v, а цвет
* задаёт level.guards_color[room-1] (1..7; 0 = не менять). Звать ДО
* отрисовки комнаты, как это делает redraw_screen. */
void pop_guard_set_palette(uint8_t color) __banked;
/* Отрисовка соперника — общая на всех Char: pop_char_draw(POP_CH_OPP) и
* компания, см. pop_cdraw.h. Метрики его последнего кадра (в т.ч. ширина
* спрайта для char_width_half) — pop_cd[POP_CH_OPP]. */
/* Убрать стража со сцены (смена комнаты / инициализация). */
void pop_guard_reset(void);
/* Чит «убить стража» (seg000:786, K): скелета не берёт. Вызывать только
* при включённых читах — проверку делает вызывающий. */
void pop_guard_kill(void);
/* do_delta_hp (seg003): применить дельту HP, накопленную за кадр. Звать
* раз в кадр ПОСЛЕ тиков персонажей (в оригинале — в play_frame). */
void pop_do_delta_hp(void);
/* Тик логики стража — В БАНКЕ (guards.c). Состояние берёт отсюда. */
void pop_guard_tick(void) __banked;
#endif