Files
Sprinter-SDCC/applications/PoP/roomtest/tests-host/stubs.c
T
Александр Петров 3fe083331f 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>
2026-08-05 12:32:55 +03:00

197 lines
8.6 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.
/*
* stubs.c — окружение движка для прогона под ucsim_z80.
*
* Под тестом крутится НАСТОЯЩАЯ физика (pop_map) с настоящим
* интерпретатором последовательностей (pop_kid): это единственный способ
* проверить порт seg005/seg006, потому что вся его суть — в точных
* величинах кадров и seqtbl. А вот всё, что вокруг, в ucsim не существует:
* видеопамять, EMM-страницы, файлы, клавиатура. Здесь они заглушены.
*
* Три категории:
* 1. libc/libbgi (графика, память, файлы, клавиатура) — пустышки. Код
* отрисовки из pop_kid.c линкуется, но не вызывается.
* 2. Соседние модули движка (pop_bg/pop_trob/pop_level/pop_redraw) —
* минимальные модели: room_modif живёт здесь массивом, пометки
* перерисовки и падающие куски ЖУРНАЛИРУЮТСЯ (тест может проверить,
* что физика их дёрнула), уровень отдаёт стартовую комнату.
* 3. Состояние стража — просто данные: страж в тестах физики не тикает,
* но pop_map/pop_ctrl на его поля смотрят.
*
* Клавиатура сделана НЕ пустышкой: `tk_keys` — набор «нажатых» скан-кодов,
* и `kbd_raw_down` отвечает по нему. Так под тест попадают и read_input, и
* read_user_control, то есть весь путь ввода, а не только диспетчер.
*/
#include <stdint.h>
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include <sprinter.h>
#include <sprinter_mem.h>
#include "pop_kid.h"
#include "pop_guard.h"
#include "pop_bg.h"
#include "pop_map.h"
#include "pop_trob.h"
#include "pop_level.h"
#include "pop_redraw.h"
#include "_pop_draw.h"
#include "stubs.h"
#include "kidblob.h"
/* ---- 1. libc / libbgi ---------------------------------------------- */
uint8_t _gfx_visible_page, _gfx_draw_page, _gfx_bank;
/* ISR-стаб W0-страницы: продукт берёт его АДРЕС и вписывает в страницу.
* Здесь достаточно, чтобы символ существовал. */
void _gfx_w0_isr(void) { }
void gfx_w0_map(uint8_t page) { (void)page; }
void gfx_w0_unmap(void) { }
void gfx_blit_cols(int x, int y, const void *img, uint8_t flip)
{ (void)x; (void)y; (void)img; (void)flip; }
void gfx_blit_cols_part(int x, int y, const void *img, uint8_t flip,
int skip, int rows)
{ (void)x; (void)y; (void)img; (void)flip; (void)skip; (void)rows; }
void gfx_blit_cols_part_w(int x, int y, const void *img, uint8_t flip,
int skip, int rows, uint8_t maxw)
{ (void)x; (void)y; (void)img; (void)flip; (void)skip; (void)rows; (void)maxw; }
void gfx_blit_cols_part_noclip(int x, int y, const void *img, uint8_t flip,
uint8_t skip, uint8_t rows)
{ (void)x; (void)y; (void)img; (void)flip; (void)skip; (void)rows; }
int atlas_load(atlas_t *a, const char *path) { (void)a; (void)path; return -1; }
void atlas_free(atlas_t *a) { (void)a; }
const void *atlas_image(const atlas_t *a, uint8_t idx)
{ (void)a; (void)idx; return 0; }
/* EMM: страниц нет, но блок обязан выделиться — иначе pop_kid_data_load
* сдастся на первом же шаге и молча оставит kdat_ok = 0, а play_seq тогда
* просто ничего не делает (и трасса выходит из одного застывшего кадра). */
uint8_t mem_alloc_pages_bios(uint8_t n) { (void)n; return 1; }
void mem_free_block_bios(uint8_t blk) { (void)blk; }
uint8_t mem_get_page_bios(uint8_t blk, uint8_t idx) { (void)blk; (void)idx; return 1; }
/* Файловая система: единственный «файл» — таблицы анимации. Сделано не
* пустышкой, а рабочим чтением, чтобы pop_kid_data_load отработал целиком
* (включая правку ISR-стаба страницы) и сам взвёл свой kdat_ok — тест не
* лезет во внутренние статики продукта. Данные, по которым потом ходят
* аксессоры, кладёт kid_blob_install по KID_BLOB_ADDR: на устройстве туда
* же их приводит маппинг W0. */
int open(const char *p, int f) { (void)p; (void)f; return 3; }
int read(int fd, void *b, int n)
{
unsigned char *d = (unsigned char *)b;
const unsigned char *s = kid_blob;
unsigned int i, cnt = kid_blob_size;
if (fd != 3) return -1;
if ((unsigned int)n < cnt) cnt = (unsigned int)n;
for (i = 0; i < cnt; i++) d[i] = s[i];
return (int)cnt;
}
int close(int fd) { (void)fd; return 0; }
/* ---- клавиатура: управляемая тестом --------------------------------- */
uint16_t tk_keys[TK_KEYS_MAX];
int kbd_raw_open(void) { return 0; }
void kbd_raw_close(void) { }
void kbd_raw_sync(void) { }
uint8_t kbd_raw_down(uint16_t code)
{
uint8_t i;
for (i = 0; i < TK_KEYS_MAX; i++)
if (tk_keys[i] == code) return 1;
return 0;
}
void tk_keys_clear(void)
{
uint8_t i;
for (i = 0; i < TK_KEYS_MAX; i++) tk_keys[i] = 0;
}
void tk_key_down(uint16_t code)
{
uint8_t i;
for (i = 0; i < TK_KEYS_MAX; i++)
if (tk_keys[i] == 0) { tk_keys[i] = code; return; }
}
/* ---- 2. соседние модули движка -------------------------------------- */
/* room_modif текущей комнаты: пики (счётчик фазы), ворота (высота),
* зелья (тип). Физика читает и пишет его напрямую через pop_trob_modif. */
uint8_t tk_modif[30];
uint8_t tk_start_room;
uint8_t *pop_trob_modif(uint8_t room) { (void)room; return tk_modif; }
uint8_t pop_level_start_room(void) { return tk_start_room; }
/* Журнал: что физика попросила у соседей. Тест сверяет ФАКТ и параметры
* (какой тайл помечен, где заспавнен кусок) — заглушка-пустышка этого не
* дала бы, а именно такие баги (BUG-GATE-ANIM-1) мы и ловим. */
tk_log_t tk_log;
static void log_put(uint8_t kind, uint8_t a, uint8_t b, uint8_t c)
{
if (tk_log.n >= TK_LOG_MAX) { tk_log.overflow = 1; return; }
tk_log.e[tk_log.n].kind = kind;
tk_log.e[tk_log.n].a = a;
tk_log.e[tk_log.n].b = b;
tk_log.e[tk_log.n].c = c;
tk_log.n++;
}
void tk_log_clear(void) { tk_log.n = 0; tk_log.overflow = 0; }
uint8_t tk_log_count(uint8_t kind)
{
uint8_t i, c = 0;
for (i = 0; i < tk_log.n; i++) if (tk_log.e[i].kind == kind) c++;
return c;
}
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type)
{ log_put(TK_TROB, room, tilepos, (uint8_t)type); }
void pop_trigger_button(uint8_t room, uint8_t tilepos,
uint8_t button_type, uint8_t modifier)
{ (void)modifier; log_put(TK_BUTTON, room, tilepos, button_type); }
void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages)
{ log_put(TK_REDRAW, tilepos, kind, pages); }
void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages)
{ log_put(TK_REDRAW_ABOVE, col, kind, pages); }
void pop_loose_mob_spawn(int row, int col) __banked
{ log_put(TK_MOB_SPAWN, (uint8_t)row, (uint8_t)col, 0); }
void pop_loose_mob_tick(void) __banked { }
void pop_loose_mob_room_changed(void) __banked { }
uint8_t pop_loose_mob_pos(int *col, int *y) __banked
{ (void)col; (void)y; return 0; }
/* Отрисовочные хвосты, которые физика дёргает напрямую. */
void pop_fore_set_clip(int x, int y, int w, int h) __banked
{ (void)x; (void)y; (void)w; (void)h; }
void pop_clip_sprite(int x, int w, int sy_top, int h) __banked
{ (void)x; (void)w; (void)sy_top; (void)h; }
void pop_heal_fast(int x, int y, int w, int h)
{ (void)x; (void)y; (void)w; (void)h; }
/* ---- 3. состояние стража -------------------------------------------- */
pop_char_t Guard;
kframe pop_gframe;
int8_t can_guard_see_kid;
int8_t guard_refrac, kid_sword_strike;
int8_t offguard, holding_sword;