tests-host: покадровый харнесс сценариев Кида (физика + зацеп)
Проверять физику Кида глазами в MAME дорого и ненадёжно: ошибка почти всегда не в одной функции, а в РАСХОЖДЕНИИ ТРАЕКТОРИИ через несколько кадров. Харнесс гоняет тот же кадр, что и главный цикл (pop_ctrl_tick -> kid_tick -> pop_phys_tick -> pop_loose_tick), и сравнивает трассу состояния с эталоном. - scene.c/.h — раннер: комната + стартовая поза + скрипт ввода -> трасса; sc_kid_at_x задаёт точный X (исход часто зависит от фазы внутри тайла). - stubs.c/.h — libc/libbgi/соседние модули; read() реально отдаёт kid_data.bin (иначе kdat_ok=0 и play_seq молчит — трасса замирает). - t_phys.c — 9 характеризующих сценариев, 1723 сверки (golden/). - t_grab.c — окно зацепа: существует, достижимо коротким шагом, не зависит от рисунка нажатий. - record_golden.py — снятие эталона по одному сценарию за прогон. - testkit/host-tests.mk — CODE_LOC настраиваемый, EXTRA_INC/EXTRA_CFLAGS. Именно харнесс дал доказательство, что физика зацепа у нас верна, и тем самым перевёл поиск BUG-GRAB-1 на клавиатуру. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/* Прогнать 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
|
||||
Reference in New Issue
Block a user