/* * 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 /* Одна запись трассы: то, чем оперируют решения движка. 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=), а перебором занимается record_golden.py. * Формат: строка «### имя», дальше по строке на кадр, по два хекс-символа * на байт. Раннер печатает лог только при провале — sc_dump ставит * failed=1 намеренно. */ void sc_dump(const char *name); #endif