PoP: скелет уровня 3, цвета стражей, окклюзия соперника; кэш соседних комнат

Скелет (L3-SKEL, ассеты + механика):
- pop_pack_guard.py получил параметр набора (GUARD/SKEL): атлас скелета
  poc/res/skel/g0..g3.atl (28 кадров), палитра — из его res750.pal (на ур. 3
  curr_guard_color = 0, оригинал палитру не подменяет);
- pop_guard_load выбирает набор по tbl_guard_type и перезагружается ПРИ СМЕНЕ
  УРОВНЯ (load_lev_spr, seg000:1092) — без этого скелет рисовался атласом
  стража и был невидим;
- load_frame: charid_4_skeleton идёт по таблице стража (seg006:529), тень —
  только в кадрах 150..189.  Пока ветка была одна (charid_2_guard), скелет
  получал image из таблицы Кида (180 при 28 спрайтах) и не рисовался;
- check_skel (seg002:1042), ветка charid_4 в enter_guard, возрождение в
  комнате 3 при падении (seg002:252), autocontrol_skeleton;
- leveldoor_open (seg007:456) — новый флаг, сбрасывается стартом уровня.

Цвета стражей (BUG-GUARD-COLOR-1, закрыт):
- все 7 палитр res10.bin -> pop_guard_pal.h, заливка 16 слотов по
  guards_color комнаты перед отрисовкой (set_chtab_palette, seg003:257).
  Проверено в MAME: ур. 2 комн. 11 = цвет 1, комн. 7 = цвет 3, полоса HP
  меняется вместе со стражем.  Грабля: gfx_pal_load отдаёт указатель в BIOS,
  а тот читает только #4000-#BFFF — таблицу из банка копируем в стек.

Кэш соседних комнат (BUG-SWORD-GHOST-1, закрыт):
- pop_map кэширует fg соседей слева/справа ЦЕЛИКОМ и резолвит col -10..19.
  Было -2..11, дальше мнимая стена: луч видимости упирался в неё (страж
  после follow_guard в col 12), Кид прятал меч посреди боя и не мог достать
  обратно.  +48 байт W2.

Окклюзия соперника:
- pop_fore_over_char получил проход other_overlay_tile (порядок midtable,
  seg008:1B06) и расширение перебора объединённым прямоугольником
  «персонаж + клинок + брызги» — падающий скелет больше не рисуется поверх
  кладки и верхней грани пола;
- клип полем 192 строк (reset_obj_clip, seg006:0507) для спрайта, клинка
  (общий pop_sword_draw) и брызг — спрайт не залезает на полосу HP;
- ROOMNAV после смерти Кида делает честный pop_start_level: телепорт
  «оживлял» мёртвого мимо старта уровня, оставляя живого скелета рядом с
  вернувшейся кучей костей.

Ассеты чомпера (под L3-CHOMP): весь набор кадров в атласе явным списком
(101-105 низ, 111-113 верх, 106-110 фронт, 114-123 кровь mono-силуэтом) —
render_room анимированные тайлы пропускает, и в атласе не было ни одного.
Число EMM-страниц не изменилось.

Тесты: tests-host все 5 наборов зелёные, t_char вырос до 65 проверок
(резолв колонок за краем комнаты, возрождение скелета); в testkit добавлен
гард «код наехал на данные» (DATA_LOC).

Доски: TASKS.md разнесён на TASKS_OPEN/TASKS_CLOSED, закрытые баги с
разбором корней — в bug_closed.md; заведены DRAW-CHAR (отрисовка одна на всех
Char, как физика после GUARD-PHYS) и L3-COLOR (зелёная кладка уровня 3:
level_var_palettes = ресурс 20, есть в MSDOS/PRINCE.DAT).

В roomtest.c временно оставлен автостоп на падении соперника (отладка
падений скелета) — помечен ВРЕМЕННО.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 22:11:03 +03:00
parent 18d177407b
commit 6673279cef
55 changed files with 3340 additions and 1304 deletions
+43
View File
@@ -33,7 +33,9 @@
#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
@@ -76,6 +78,23 @@ 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_guard_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 (банк) раз в
@@ -109,6 +128,12 @@ 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;
/* Боёвка: пометить попадания (обе стороны) и применить их. Порядок в
* кадре как в play_frame: hurting -> hurt -> do_delta_hp. */
void pop_check_sword_hurting(void) __banked;
@@ -119,6 +144,11 @@ void pop_check_sword_hurt(void) __banked;
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);
@@ -135,6 +165,13 @@ void pop_guard_enter(uint8_t room);
* 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);
/* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE,
* как kid_draw) и стереть прошлый (heal). Реализация — pop_gdraw.c
* (резидент W3), поэтому звать ТОЛЬКО из главного цикла, не из банка. */
@@ -144,6 +181,12 @@ 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;
void pop_guard_heal(void) __banked;
void pop_guard_draw(void) __banked;