Files
Sprinter-SDCC/applications/PoP/roomtest/tests-host/scene.h
T
snark13 6673279cef 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>
2026-08-07 22:11:03 +03:00

103 lines
6.1 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.
/*
* scene.h — покадровый сценарный раннер для тестов физики Кида.
*
* Почему покадровый, а не «вызвать функцию и сверить возврат». Порт
* seg005/seg006 — это конечный автомат: диспетчер выбирает
* последовательность, интерпретатор проигрывает её кадр за кадром, физика
* между кадрами двигает персонажа и решает, не пора ли падать. Ошибка
* почти всегда проявляется не в одной функции, а в РАСХОЖДЕНИИ ТРАЕКТОРИИ
* через несколько кадров. Плюс главный риск ближайшего рефакторинга
* (Kid -> Char) — не арифметика, а то, КТО владеет окном Char внутри кадра;
* поймать это можно только прогоном полного кадра, как в главном цикле:
*
* pop_ctrl_tick(); // ввод -> control(): смена последовательности
* kid_tick(); // play_seq: следующий кадр
* pop_phys_tick(); // падение/приземление/стена
*
* Отсюда формат: сцена (комната + стартовая поза) + скрипт ввода, на
* выходе — трасса состояния Кида по кадрам. Сравнение трассы с эталоном
* и есть тест.
*/
#ifndef POP_TESTS_SCENE_H
#define POP_TESTS_SCENE_H
#include <stdint.h>
/* Одна запись трассы: то, чем оперируют решения движка. curr_seq —
* СМЕЩЕНИЕ от SEQTBL_BASE (адрес зависит от базы, смещение — нет). */
typedef struct {
uint8_t frame;
uint8_t x, y;
int8_t dir;
int8_t col, row;
uint8_t action;
uint16_t seq;
} sc_rec;
#define SC_TRACE_MAX 48
extern sc_rec sc_trace[SC_TRACE_MAX];
extern uint8_t sc_len;
/* Кнопки скрипта ввода — битовая маска на кадр. */
#define SC_L 0x01
#define SC_R 0x02
#define SC_U 0x04
#define SC_D 0x08
#define SC_SHIFT 0x10
/* Инициализация окружения набора: таблицы анимации, обнуление состояния.
* Звать один раз в начале прогона. */
void sc_init(void);
/* Задать комнату: fg[30] — коды тайлов по рядам (row*10+col), room —
* номер (нужен trob'ам). Модификаторы обнуляются; ставить их тест может
* сам через tk_modif. Соседи комнаты — стены (изолированная сцена). */
void sc_room(const uint8_t *fg, uint8_t room);
/* Поставить Кида: колонка/ряд задают позицию по центру тайла, как это
* делает do_startpos (seg003) — так сцена описывается в терминах карты, а
* не в пикселях. */
void sc_kid_at(uint8_t col, uint8_t row, int8_t dir);
/* То же, но с точным X: тайл задаёт ряд и пол под ногами, X — фазу внутри
* тайла (нужно, когда исход зависит от того, где именно Кид стоит). */
void sc_kid_at_x(uint8_t col, uint8_t row, uint8_t x, int8_t dir);
/* ---- Персонажи слота Guard (страж/скелет/тень/визирь) ---------------- *
* Физика у них ОБЩАЯ с Кидом (одни функции над Char, разные окна), поэтому
* тестировать её надо на обоих слотах. Поднимаем персонажа руками, как
* enter_guard (seg002:0112): данных уровня под тестом нет.
* charid — CHARID_2_GUARD / CHARID_1_SHADOW / CHARID_4_SKELETON;
* hp — сколько HP выдать (guardhp_max = guardhp_curr = hp). */
void sc_guard_at(uint8_t charid, uint8_t col, uint8_t row, int8_t dir, uint8_t hp);
/* Прогнать n кадров ПЕРСОНАЖА СЛОТА GUARD — порт play_guard_frame без ИИ:
* окно loadshad → load_fram_det_col → play_seq → saveshad, затем
* pop_guard_phys_tick(). ИИ (guards.c, банк 1) в набор не линкуется: он
* решает «куда идти», а тут проверяется «что с ним делает физика». */
void sc_guard_run(uint8_t n);
/* Прогнать n кадров с ЗАЖАТОЙ маской keys, записывая трассу.
* Возвращает число записанных кадров (обрезается по SC_TRACE_MAX). */
uint8_t sc_run(uint8_t keys, uint8_t n);
/* Сбросить трассу (перед новым сценарием). */
void sc_trace_clear(void);
/* Сверить трассу с эталоном: golden — плоский массив по 8 байт на кадр
* (frame,x,y,dir,col,row,action,seq_lo,seq_hi — 9), nrec записей.
* Расхождения уходят в отчёт tcheck с номером кадра и поля. */
void sc_expect(const uint8_t *golden, uint8_t nrec);
#define SC_REC_BYTES 9
/* Снятие эталона. Лог tcheck всего 768 байт — на все сценарии сразу трасс
* не хватит, поэтому за прогон снимается РОВНО ОДИН сценарий (его номер
* задаёт -DSC_RECORD=<n>), а перебором занимается record_golden.py.
* Формат: строка «### имя», дальше по строке на кадр, по два хекс-символа
* на байт. Раннер печатает лог только при провале — sc_dump ставит
* failed=1 намеренно. */
void sc_dump(const char *name);
#endif