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:
@@ -3,6 +3,48 @@
|
|||||||
TESTKIT := $(abspath $(CURDIR)/../../../../testkit)
|
TESTKIT := $(abspath $(CURDIR)/../../../../testkit)
|
||||||
ENGINE_DIR := $(abspath $(CURDIR)/..)
|
ENGINE_DIR := $(abspath $(CURDIR)/..)
|
||||||
|
|
||||||
|
# t_phys гоняет НАСТОЯЩУЮ физику, а она ходит по таблицам анимации
|
||||||
|
# абсолютными адресами от KD_DATA_OFF (0x100) — там, куда на устройстве
|
||||||
|
# мапится страница W0. Значит блоб kid_data.bin лежит по 0x100, и код
|
||||||
|
# набора обязан начинаться выше его конца (0x100 + 3720 = 0x0F88).
|
||||||
|
CODE_LOC := 0x1000
|
||||||
|
|
||||||
|
# Заголовки libc/libbgi нужны потому, что pop_kid.c включает gfx/sprite/
|
||||||
|
# sprinter: под тестом эти функции заглушены (stubs.c), но объявления
|
||||||
|
# берутся настоящие — иначе разъедутся сигнатуры.
|
||||||
|
PROJ_ROOT := $(abspath $(TESTKIT)/..)
|
||||||
|
EXTRA_INC := -I$(PROJ_ROOT)/libc/include -I$(PROJ_ROOT)/libbgi/include -I$(CURDIR)
|
||||||
|
|
||||||
OBJS_geom := build/eng_pop_geom.rel
|
OBJS_geom := build/eng_pop_geom.rel
|
||||||
|
|
||||||
|
OBJS_phys := build/eng_pop_geom.rel build/eng_pop_kid.rel \
|
||||||
|
build/eng_pop_map.rel build/eng_pop_ctrl.rel \
|
||||||
|
build/eng_pop_state.rel \
|
||||||
|
build/stubs.rel build/scene.rel build/kid_blob.rel \
|
||||||
|
build/bank_stub.rel
|
||||||
|
|
||||||
|
# Пробный набор (разведка, без эталонов) линкуется тем же составом.
|
||||||
|
OBJS_grab := $(OBJS_phys)
|
||||||
|
|
||||||
include $(TESTKIT)/host-tests.mk
|
include $(TESTKIT)/host-tests.mk
|
||||||
|
|
||||||
|
# --- t_phys: блоб таблиц анимации ------------------------------------
|
||||||
|
KID_BIN := $(abspath $(CURDIR)/../../poc/res/kid/kid_data.bin)
|
||||||
|
|
||||||
|
build/kid_blob.c: gen_kid_blob.py $(KID_BIN) | $(BUILD)
|
||||||
|
@python3 gen_kid_blob.py $(KID_BIN) $@
|
||||||
|
|
||||||
|
build/kid_blob.rel: build/kid_blob.c kidblob.h | $(BUILD)
|
||||||
|
@$(SDCC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
build/stubs.rel: stubs.c stubs.h kidblob.h | $(BUILD)
|
||||||
|
@$(SDCC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
build/scene.rel: scene.c scene.h stubs.h kidblob.h | $(BUILD)
|
||||||
|
@$(SDCC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
build/bank_stub.rel: bank_stub.s | $(BUILD)
|
||||||
|
@$(SDASZ80) -plosgff $@ $<
|
||||||
|
|
||||||
|
# Эталоны трасс: пересобрать набор, если их правили (см. шапку t_phys.c).
|
||||||
|
build/phys.rel: $(wildcard golden/*.h)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ make -C applications/PoP/roomtest/tests-host t_geom
|
|||||||
| набор | модуль | что проверяет |
|
| набор | модуль | что проверяет |
|
||||||
|-------|--------|---------------|
|
|-------|--------|---------------|
|
||||||
| `t_geom` | `pop_geom.c` | геометрия комнаты (`x_bump`, `y_land`, `y_to_row`) и PRNG оригинала |
|
| `t_geom` | `pop_geom.c` | геометрия комнаты (`x_bump`, `y_land`, `y_to_row`) и PRNG оригинала |
|
||||||
|
| `t_phys` | `pop_map.c` + `pop_kid.c` + `pop_ctrl.c` | покадровые трассы физики Кида: стойка, бег, шаг с Shift, разворот, присед, прыжок вверх, падение с кромки, loose-плита, разбег-прыжок через провал |
|
||||||
|
|
||||||
Главный тест в `t_geom` — `geom_lcg_matches_reference`. LCG оригинала
|
Главный тест в `t_geom` — `geom_lcg_matches_reference`. LCG оригинала
|
||||||
(`s = s*214013 + 2531011`) написан в `pop_geom.c` на ассемблере по схеме
|
(`s = s*214013 + 2531011`) написан в `pop_geom.c` на ассемблере по схеме
|
||||||
@@ -22,25 +23,83 @@ make -C applications/PoP/roomtest/tests-host t_geom
|
|||||||
asm с наивной 32-битной формулой на 128 шагах — и по возвращаемому
|
asm с наивной 32-битной формулой на 128 шагах — и по возвращаемому
|
||||||
значению, и по обеим половинам сида.
|
значению, и по обеим половинам сида.
|
||||||
|
|
||||||
|
## t_phys: характеризация, а не проверка правильности
|
||||||
|
|
||||||
|
Набор существует ради **страховки под рефакторинг**. Эталоны сняты с
|
||||||
|
текущего билда, то есть консервируют и текущие баги — правильность
|
||||||
|
по-прежнему проверяется сверкой с `SDLPoP/src/`. Ценность в другом:
|
||||||
|
ближайший крупный шаг (перенос физики с `Kid.` на `Char.`, чтобы её мог
|
||||||
|
переиспользовать страж) обязан быть **поведение-сохраняющим**, и «трасса до
|
||||||
|
== трасса после» ловит ровно тот класс ошибок, который там возможен.
|
||||||
|
|
||||||
|
Поэтому эталон **переснимается только осознанно**, и в коммите должно быть
|
||||||
|
написано, что и почему поменялось. Молча обновлённый эталон обесценивает
|
||||||
|
весь набор.
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 record_golden.py # переснять все трассы
|
||||||
|
python3 record_golden.py 7 9 # только сценарии 7 и 9
|
||||||
|
```
|
||||||
|
|
||||||
|
Трасса — по одной записи на кадр: `frame, x, y, dir, col, row, action` и
|
||||||
|
смещение `curr_seq` от `SEQTBL_BASE`. Кадр гоняется ровно в том порядке,
|
||||||
|
что и в главном цикле `roomtest.c`:
|
||||||
|
|
||||||
|
```
|
||||||
|
pop_ctrl_tick(); // ввод -> control(): смена последовательности
|
||||||
|
kid_tick(); // play_seq: следующий кадр
|
||||||
|
pop_phys_tick(); // падение/приземление/стена
|
||||||
|
pop_loose_tick(); // досчёт тряски и снятие провалившейся плиты
|
||||||
|
```
|
||||||
|
|
||||||
|
Покадровость здесь принципиальна: порт seg005/seg006 — конечный автомат, и
|
||||||
|
ошибка почти всегда проявляется не в одной функции, а в расхождении
|
||||||
|
траектории через несколько кадров. Плюс главный риск переноса на `Char` —
|
||||||
|
не арифметика, а то, **кто владеет окном `Char` внутри кадра**; поймать это
|
||||||
|
можно только прогоном полного кадра.
|
||||||
|
|
||||||
|
Проверка, что харнесс воспроизводит устройство: сценарий `stand` даёт
|
||||||
|
`frame=15, x=114, y=118, curr_seq=0x19A3` — те же значения, что читаются из
|
||||||
|
`_Kid` в живом MAME.
|
||||||
|
|
||||||
|
### Как это заработало под ucsim
|
||||||
|
|
||||||
|
Три шва, каждый закрыт без правок продуктового кода:
|
||||||
|
|
||||||
|
1. **Таблицы анимации.** `pop_kid.c` ходит по ним абсолютными адресами от
|
||||||
|
`KD_DATA_OFF` (0x100) — там, куда на устройстве их приводит маппинг W0.
|
||||||
|
В тесте `kid_data.bin` просто кладётся по 0x100 (`gen_kid_blob.py`), а
|
||||||
|
`gfx_w0_map` заглушен. Следствие: код набора линкуется с
|
||||||
|
`--code-loc 0x1000` (`CODE_LOC` в Makefile), чтобы не налезть на блоб.
|
||||||
|
2. **Окружение.** `stubs.c` — libc/libbgi (графика, память, файлы), соседние
|
||||||
|
модули (`pop_bg`/`pop_trob`/`pop_level`/`pop_redraw`) и состояние стража.
|
||||||
|
Заглушки не пустые там, где это меняет смысл: файловое чтение реально
|
||||||
|
отдаёт `kid_data.bin` (иначе `pop_kid_data_load` тихо сдаётся, `kdat_ok`
|
||||||
|
остаётся нулём и `play_seq` не делает ничего — трасса выходит из одного
|
||||||
|
застывшего кадра), клавиатура отвечает по набору «нажатых» скан-кодов
|
||||||
|
(так под тест попадают `read_input` и `read_user_control`, а не только
|
||||||
|
диспетчер), а обращения к соседям **журналируются** (`tk_log`) — можно
|
||||||
|
проверять «плита отвалилась и попросила перерисовку», а не только
|
||||||
|
координаты.
|
||||||
|
3. **`__banked`.** Модули физики помечены как банковые, SDCC генерирует на
|
||||||
|
них трамплин `___sdcc_bcall_ehl`, которому нужны `set_bank`/`get_bank`.
|
||||||
|
В тесте память плоская — `bank_stub.s` отдаёт нулевой банк и пустое
|
||||||
|
переключение, соблюдая контракт по регистрам.
|
||||||
|
|
||||||
|
Что харнесс НЕ покрывает и остаётся за MAME: отрисовка и окклюзия, реальные
|
||||||
|
переключения банков, тайминги кадра, настоящая клавиатура.
|
||||||
|
|
||||||
## Что нужно, чтобы двинуться дальше
|
## Что нужно, чтобы двинуться дальше
|
||||||
|
|
||||||
Следующие на очереди модули (`pop_trob`, `pop_level`, `pop_map`, логика
|
- **Доступ к странице уровня.** `pop_level.c` ходит по абсолютным адресам
|
||||||
падающих плит) упираются в два шва, и оба затрагивают продукт:
|
(`(uint8_t *)(LVL_DATA_OFF + …)` после `gfx_w0_map`). Для `pop_map` это не
|
||||||
|
потребовалось — карту комнаты в него ИНЪЕКТИРУЮТ через `pop_map_set`, —
|
||||||
1. **Доступ к странице уровня.** `pop_level.c` ходит по абсолютным адресам
|
но набор на сам `pop_level`/`pop_trob` в это упрётся. Решение то же, что
|
||||||
(`(uint8_t *)(LVL_DATA_OFF + …)` после `gfx_w0_map`) — в тестах это
|
для `kid_data`: положить страницу по нужному адресу и поднять `CODE_LOC`.
|
||||||
обращение в никуда. Нужен макрос вида `W0PTR(off)`: на таргете как
|
- **Сценарии из `bug_closed.md`.** У каждой закрытой записи есть симптом и
|
||||||
сейчас, в тестах — смещение в обычном массиве.
|
ожидаемое поведение — готовый список регрессионных кейсов, который стоит
|
||||||
2. **Журналирующий рендерер** вместо `pop_bg`/`pop_gdraw` — не заглушка, а
|
переносить в `t_phys` по мере надобности.
|
||||||
запись вызовов, чтобы проверять «какой тайл помечен к перерисовке, каким
|
- **BUG-LOOSE-2** (гонка «уйти из комнаты раньше, чем долетит плита») —
|
||||||
кодом, с каким счётчиком страниц». `BUG-GATE-ANIM-1` был ровно такой
|
через мост MAME воспроизвести не удалось, а на уровне логики это
|
||||||
формы.
|
несколько строк: заспавнить кусок, сменить комнату, тикать до
|
||||||
|
приземления, проверить щебень.
|
||||||
После этого `bug_closed.md` превращается в готовый список регрессионных
|
|
||||||
кейсов: у каждой записи там есть симптом и ожидаемое поведение.
|
|
||||||
|
|
||||||
Отдельно стоит завести тест на `BUG-LOOSE-2` — он до сих пор помечен в
|
|
||||||
`bug_list.md` как непроверенный именно потому, что гонку «уйти из комнаты
|
|
||||||
раньше, чем долетит плита» через мост MAME воспроизвести не удалось. На
|
|
||||||
уровне логики это несколько строк: заспавнить кусок, сменить комнату,
|
|
||||||
тикать до приземления, проверить щебень в данных уровня.
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
;--------------------------------------------------------------------------
|
||||||
|
; bank_stub.s — set_bank/get_bank для прогона под ucsim.
|
||||||
|
;
|
||||||
|
; Модули движка помечены __banked (в продукте они лежат в страницах W3), и
|
||||||
|
; SDCC генерирует на такие вызовы трамплин ___sdcc_bcall_ehl из своей
|
||||||
|
; библиотеки. Трамплин обязан найти две пользовательские подпрограммы:
|
||||||
|
;
|
||||||
|
; get_bank — вернуть в A номер текущего банка, прочие регистры не трогать
|
||||||
|
; set_bank — переключить банк на A, прочие регистры не трогать
|
||||||
|
;
|
||||||
|
; В тесте банков нет: вся память плоская, все модули слинкованы вместе.
|
||||||
|
; Поэтому «банк» всегда нулевой, а переключение — пустое. Важно только
|
||||||
|
; соблюсти контракт по регистрам, иначе трамплин испортит аргументы вызова.
|
||||||
|
;--------------------------------------------------------------------------
|
||||||
|
.area _CODE
|
||||||
|
|
||||||
|
.globl set_bank
|
||||||
|
.globl get_bank
|
||||||
|
|
||||||
|
get_bank::
|
||||||
|
ld a, #0x00
|
||||||
|
ret
|
||||||
|
|
||||||
|
set_bank::
|
||||||
|
ret
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
gen_kid_blob.py — kid_data.bin как C-массив для тестов под ucsim.
|
||||||
|
|
||||||
|
Зачем. Таблицы кадров и seqtbl живут в EMM-странице, замапленной в W0, и
|
||||||
|
pop_kid ходит по ним АБСОЛЮТНЫМИ адресами от KD_DATA_OFF (0x100). В ucsim
|
||||||
|
никакого W0 нет, зато есть ровная 64-КБ память, поэтому тест просто кладёт
|
||||||
|
файл по 0x100 и глушит gfx_w0_map. Единственное следствие для сборки —
|
||||||
|
код набора линкуется с --code-loc выше конца блоба (см. Makefile).
|
||||||
|
|
||||||
|
Файл берётся ровно тот, что уходит на устройство, — pop_extract_kid_data.py
|
||||||
|
делает его из оригинального PRINCE.EXE, так что тест проверяет порт против
|
||||||
|
НАСТОЯЩИХ данных игры, а не против переписанной от руки таблицы.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
src, dst = Path(sys.argv[1]), Path(sys.argv[2])
|
||||||
|
data = src.read_bytes()
|
||||||
|
|
||||||
|
out = [
|
||||||
|
"/* Сгенерировано gen_kid_blob.py — не править руками. */",
|
||||||
|
'#include "kidblob.h"',
|
||||||
|
"",
|
||||||
|
f"const unsigned char kid_blob[{len(data)}] = {{",
|
||||||
|
]
|
||||||
|
for i in range(0, len(data), 16):
|
||||||
|
out.append(" " + ",".join(str(b) for b in data[i:i + 16]) + ",")
|
||||||
|
out += [
|
||||||
|
"};",
|
||||||
|
"",
|
||||||
|
f"const unsigned int kid_blob_size = {len(data)};",
|
||||||
|
"",
|
||||||
|
"/* Разложить блоб по KD_DATA_OFF. Без memcpy: тестовый бинарь не",
|
||||||
|
" * линкуется с libc, а тянуть ради этого z80-рантайм незачем. */",
|
||||||
|
"void kid_blob_install(void)",
|
||||||
|
"{",
|
||||||
|
" unsigned char *d = (unsigned char *)KID_BLOB_ADDR;",
|
||||||
|
" const unsigned char *s = kid_blob;",
|
||||||
|
f" unsigned int n = {len(data)};",
|
||||||
|
" while (n--) *d++ = *s++;",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
dst.write_text("\n".join(out))
|
||||||
|
print(f"gen_kid_blob: {src.name} -> {dst.name} ({len(data)} Б)")
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/* Эталон трассы: crouch. Снят record_golden.py — не править руками.
|
||||||
|
* Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */
|
||||||
|
0x6B, 0x81, 0x76, 0x00, 0x04, 0x01, 0x01, 0x3E, 0x06,
|
||||||
|
0x6C, 0x83, 0x76, 0x00, 0x04, 0x01, 0x01, 0x41, 0x06,
|
||||||
|
0x6D, 0x83, 0x76, 0x00, 0x04, 0x01, 0x01, 0x42, 0x06,
|
||||||
|
0x6D, 0x83, 0x76, 0x00, 0x04, 0x01, 0x01, 0x42, 0x06,
|
||||||
|
0x6D, 0x83, 0x76, 0x00, 0x04, 0x01, 0x01, 0x42, 0x06,
|
||||||
|
0x6D, 0x83, 0x76, 0x00, 0x04, 0x01, 0x01, 0x42, 0x06,
|
||||||
|
0x6D, 0x83, 0x76, 0x00, 0x04, 0x01, 0x01, 0x42, 0x06,
|
||||||
|
0x6D, 0x83, 0x76, 0x00, 0x04, 0x01, 0x01, 0x42, 0x06,
|
||||||
|
0x6E, 0x84, 0x76, 0x00, 0x04, 0x01, 0x05, 0x4A, 0x06,
|
||||||
|
0x6F, 0x84, 0x76, 0x00, 0x04, 0x01, 0x05, 0x4B, 0x06,
|
||||||
|
0x70, 0x86, 0x76, 0x00, 0x04, 0x01, 0x05, 0x4E, 0x06,
|
||||||
|
0x71, 0x86, 0x76, 0x00, 0x04, 0x01, 0x05, 0x4F, 0x06,
|
||||||
|
0x72, 0x87, 0x76, 0x00, 0x04, 0x01, 0x05, 0x52, 0x06,
|
||||||
|
0x73, 0x87, 0x76, 0x00, 0x04, 0x01, 0x05, 0x53, 0x06,
|
||||||
|
0x74, 0x87, 0x76, 0x00, 0x04, 0x01, 0x05, 0x54, 0x06,
|
||||||
|
0x75, 0x83, 0x76, 0x00, 0x04, 0x01, 0x05, 0x57, 0x06,
|
||||||
|
0x76, 0x83, 0x76, 0x00, 0x04, 0x01, 0x05, 0x58, 0x06,
|
||||||
|
0x77, 0x83, 0x76, 0x00, 0x04, 0x01, 0x05, 0x59, 0x06,
|
||||||
|
0x0F, 0x83, 0x76, 0x00, 0x04, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x83, 0x76, 0x00, 0x04, 0x01, 0x00, 0x35, 0x00,
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/* Эталон трассы: fall_ledge. Снят record_golden.py — не править руками.
|
||||||
|
* Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */
|
||||||
|
0x01, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x08, 0x00,
|
||||||
|
0x02, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x09, 0x00,
|
||||||
|
0x03, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x0A, 0x00,
|
||||||
|
0x04, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x0B, 0x00,
|
||||||
|
0x05, 0x5E, 0x76, 0x00, 0x01, 0x01, 0x01, 0x0E, 0x00,
|
||||||
|
0x06, 0x61, 0x76, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00,
|
||||||
|
0x07, 0x64, 0x76, 0x00, 0x01, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0x69, 0x76, 0x00, 0x02, 0x01, 0x01, 0x17, 0x00,
|
||||||
|
0x09, 0x6A, 0x76, 0x00, 0x02, 0x01, 0x01, 0x1C, 0x00,
|
||||||
|
0x0A, 0x6C, 0x76, 0x00, 0x02, 0x01, 0x01, 0x1F, 0x00,
|
||||||
|
0x0B, 0x70, 0x76, 0x00, 0x02, 0x01, 0x01, 0x22, 0x00,
|
||||||
|
0x0C, 0x75, 0x76, 0x00, 0x03, 0x01, 0x01, 0x25, 0x00,
|
||||||
|
0x0D, 0x77, 0x76, 0x00, 0x03, 0x01, 0x01, 0x2A, 0x00,
|
||||||
|
0x0E, 0x7A, 0x76, 0x00, 0x03, 0x01, 0x01, 0x2D, 0x00,
|
||||||
|
0x07, 0x7E, 0x76, 0x00, 0x03, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0x83, 0x76, 0x00, 0x04, 0x01, 0x01, 0x17, 0x00,
|
||||||
|
0x09, 0x84, 0x76, 0x00, 0x04, 0x01, 0x01, 0x1C, 0x00,
|
||||||
|
0x0A, 0x86, 0x76, 0x00, 0x04, 0x01, 0x01, 0x1F, 0x00,
|
||||||
|
0x0B, 0x8A, 0x76, 0x00, 0x04, 0x01, 0x01, 0x22, 0x00,
|
||||||
|
0x0C, 0x8F, 0x76, 0x00, 0x05, 0x01, 0x01, 0x25, 0x00,
|
||||||
|
0x0D, 0x91, 0x76, 0x00, 0x05, 0x01, 0x01, 0x2A, 0x00,
|
||||||
|
0x0E, 0x94, 0x76, 0x00, 0x05, 0x01, 0x01, 0x2D, 0x00,
|
||||||
|
0x07, 0x98, 0x76, 0x00, 0x05, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x66, 0x9E, 0x79, 0x00, 0x06, 0x02, 0x03, 0x77, 0x02,
|
||||||
|
0x67, 0xA0, 0x7F, 0x00, 0x06, 0x02, 0x03, 0x7C, 0x02,
|
||||||
|
0x68, 0x9F, 0x88, 0x00, 0x06, 0x02, 0x03, 0x81, 0x02,
|
||||||
|
0x69, 0x9F, 0x94, 0x00, 0x06, 0x02, 0x03, 0x84, 0x02,
|
||||||
|
0x6A, 0x9E, 0xA6, 0x00, 0x06, 0x02, 0x04, 0xDE, 0x03,
|
||||||
|
0x6B, 0xA0, 0xB5, 0x00, 0x06, 0x02, 0x05, 0xC3, 0x06,
|
||||||
|
0x6C, 0xA2, 0xB5, 0x00, 0x06, 0x02, 0x05, 0xC6, 0x06,
|
||||||
|
0x6D, 0xA2, 0xB5, 0x00, 0x06, 0x02, 0x01, 0xC9, 0x06,
|
||||||
|
0x6E, 0xA3, 0xB5, 0x00, 0x06, 0x02, 0x05, 0x4A, 0x06,
|
||||||
|
0x6F, 0xA3, 0xB5, 0x00, 0x06, 0x02, 0x05, 0x4B, 0x06,
|
||||||
|
0x70, 0xA5, 0xB5, 0x00, 0x06, 0x02, 0x05, 0x4E, 0x06,
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/* Эталон трассы: jump_up. Снят record_golden.py — не править руками.
|
||||||
|
* Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */
|
||||||
|
0x43, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x12, 0x04,
|
||||||
|
0x44, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x13, 0x04,
|
||||||
|
0x45, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x14, 0x04,
|
||||||
|
0x46, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x15, 0x04,
|
||||||
|
0x47, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x16, 0x04,
|
||||||
|
0x48, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x17, 0x04,
|
||||||
|
0x49, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x18, 0x04,
|
||||||
|
0x4A, 0x80, 0x76, 0xFF, 0x05, 0x01, 0x01, 0x19, 0x04,
|
||||||
|
0x4B, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x1A, 0x04,
|
||||||
|
0x4C, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x1B, 0x04,
|
||||||
|
0x4D, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x1C, 0x04,
|
||||||
|
0x4E, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x1D, 0x04,
|
||||||
|
0x4F, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x1E, 0x04,
|
||||||
|
0x4F, 0x80, 0x72, 0xFF, 0x04, 0x01, 0x01, 0x21, 0x04,
|
||||||
|
0x4F, 0x80, 0x70, 0xFF, 0x04, 0x01, 0x01, 0x24, 0x04,
|
||||||
|
0x4F, 0x80, 0x70, 0xFF, 0x04, 0x01, 0x01, 0x25, 0x04,
|
||||||
|
0x4F, 0x80, 0x72, 0xFF, 0x04, 0x01, 0x01, 0x28, 0x04,
|
||||||
|
0x51, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0xB8, 0x03,
|
||||||
|
0x52, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x01, 0xB9, 0x03,
|
||||||
|
0x53, 0x80, 0x76, 0xFF, 0x04, 0x01, 0x05, 0xBC, 0x03,
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/* Эталон трассы: loose. Снят record_golden.py — не править руками.
|
||||||
|
* Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */
|
||||||
|
0x01, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x08, 0x00,
|
||||||
|
0x02, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x09, 0x00,
|
||||||
|
0x03, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x0A, 0x00,
|
||||||
|
0x04, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x0B, 0x00,
|
||||||
|
0x05, 0x5E, 0x76, 0x00, 0x01, 0x01, 0x01, 0x0E, 0x00,
|
||||||
|
0x06, 0x61, 0x76, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00,
|
||||||
|
0x07, 0x64, 0x76, 0x00, 0x01, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0x69, 0x76, 0x00, 0x02, 0x01, 0x01, 0x17, 0x00,
|
||||||
|
0x09, 0x6A, 0x76, 0x00, 0x02, 0x01, 0x01, 0x1C, 0x00,
|
||||||
|
0x0A, 0x6C, 0x76, 0x00, 0x02, 0x01, 0x01, 0x1F, 0x00,
|
||||||
|
0x0B, 0x70, 0x76, 0x00, 0x02, 0x01, 0x01, 0x22, 0x00,
|
||||||
|
0x0C, 0x75, 0x76, 0x00, 0x03, 0x01, 0x01, 0x25, 0x00,
|
||||||
|
0x0D, 0x77, 0x76, 0x00, 0x03, 0x01, 0x01, 0x2A, 0x00,
|
||||||
|
0x0E, 0x7A, 0x76, 0x00, 0x03, 0x01, 0x01, 0x2D, 0x00,
|
||||||
|
0x07, 0x7E, 0x76, 0x00, 0x03, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0x83, 0x76, 0x00, 0x04, 0x01, 0x01, 0x17, 0x00,
|
||||||
|
0x09, 0x84, 0x76, 0x00, 0x04, 0x01, 0x01, 0x1C, 0x00,
|
||||||
|
0x0A, 0x86, 0x76, 0x00, 0x04, 0x01, 0x01, 0x1F, 0x00,
|
||||||
|
0x0B, 0x8A, 0x76, 0x00, 0x04, 0x01, 0x01, 0x22, 0x00,
|
||||||
|
0x0C, 0x8F, 0x76, 0x00, 0x05, 0x01, 0x01, 0x25, 0x00,
|
||||||
|
0x0D, 0x91, 0x76, 0x00, 0x05, 0x01, 0x01, 0x2A, 0x00,
|
||||||
|
0x0E, 0x94, 0x76, 0x00, 0x05, 0x01, 0x01, 0x2D, 0x00,
|
||||||
|
0x07, 0x98, 0x76, 0x00, 0x05, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0x9D, 0x76, 0x00, 0x06, 0x01, 0x01, 0x17, 0x00,
|
||||||
|
0x09, 0x9E, 0x76, 0x00, 0x06, 0x01, 0x01, 0x1C, 0x00,
|
||||||
|
0x0A, 0xA0, 0x76, 0x00, 0x06, 0x01, 0x01, 0x1F, 0x00,
|
||||||
|
0x0B, 0xA4, 0x76, 0x00, 0x06, 0x01, 0x01, 0x22, 0x00,
|
||||||
|
0x0C, 0xA9, 0x76, 0x00, 0x07, 0x01, 0x01, 0x25, 0x00,
|
||||||
|
0x0D, 0xAB, 0x76, 0x00, 0x07, 0x01, 0x01, 0x2A, 0x00,
|
||||||
|
0x0E, 0xAE, 0x76, 0x00, 0x07, 0x01, 0x01, 0x2D, 0x00,
|
||||||
|
0x07, 0xB2, 0x76, 0x00, 0x07, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0xB7, 0x76, 0x00, 0x08, 0x01, 0x01, 0x17, 0x00,
|
||||||
|
0x09, 0xB8, 0x76, 0x00, 0x08, 0x01, 0x01, 0x1C, 0x00,
|
||||||
|
0x0A, 0xBA, 0x76, 0x00, 0x08, 0x01, 0x01, 0x1F, 0x00,
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/* Эталон трассы: rjump_gap3. Снят record_golden.py — не править руками.
|
||||||
|
* Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */
|
||||||
|
0x01, 0xB8, 0x76, 0xFF, 0x08, 0x01, 0x01, 0x08, 0x00,
|
||||||
|
0x02, 0xB8, 0x76, 0xFF, 0x08, 0x01, 0x01, 0x09, 0x00,
|
||||||
|
0x03, 0xB8, 0x76, 0xFF, 0x08, 0x01, 0x01, 0x0A, 0x00,
|
||||||
|
0x04, 0xB8, 0x76, 0xFF, 0x08, 0x01, 0x01, 0x0B, 0x00,
|
||||||
|
0x05, 0xB0, 0x76, 0xFF, 0x08, 0x01, 0x01, 0x0E, 0x00,
|
||||||
|
0x06, 0xAD, 0x76, 0xFF, 0x08, 0x01, 0x01, 0x11, 0x00,
|
||||||
|
0x07, 0xAA, 0x76, 0xFF, 0x08, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0xA5, 0x76, 0xFF, 0x07, 0x01, 0x01, 0x17, 0x00,
|
||||||
|
0x09, 0xA4, 0x76, 0xFF, 0x07, 0x01, 0x01, 0x1C, 0x00,
|
||||||
|
0x0A, 0xA2, 0x76, 0xFF, 0x07, 0x01, 0x01, 0x1F, 0x00,
|
||||||
|
0x0B, 0x9E, 0x76, 0xFF, 0x07, 0x01, 0x01, 0x22, 0x00,
|
||||||
|
0x0C, 0x99, 0x76, 0xFF, 0x06, 0x01, 0x01, 0x25, 0x00,
|
||||||
|
0x0D, 0x97, 0x76, 0xFF, 0x06, 0x01, 0x01, 0x2A, 0x00,
|
||||||
|
0x22, 0x95, 0x76, 0xFF, 0x06, 0x01, 0x01, 0x64, 0x01,
|
||||||
|
0x23, 0x90, 0x76, 0xFF, 0x06, 0x01, 0x01, 0x67, 0x01,
|
||||||
|
0x24, 0x8A, 0x76, 0xFF, 0x06, 0x01, 0x01, 0x6A, 0x01,
|
||||||
|
0x25, 0x87, 0x76, 0xFF, 0x05, 0x01, 0x01, 0x6D, 0x01,
|
||||||
|
0x26, 0x82, 0x76, 0xFF, 0x05, 0x01, 0x01, 0x72, 0x01,
|
||||||
|
0x27, 0x7B, 0x76, 0xFF, 0x05, 0x01, 0x01, 0x75, 0x01,
|
||||||
|
0x28, 0x6F, 0x73, 0xFF, 0x04, 0x01, 0x01, 0x7A, 0x01,
|
||||||
|
0x29, 0x67, 0x6A, 0xFF, 0x03, 0x01, 0x01, 0x7F, 0x01,
|
||||||
|
0x2A, 0x5F, 0x68, 0xFF, 0x02, 0x01, 0x01, 0x84, 0x01,
|
||||||
|
0x2B, 0x5B, 0x73, 0xFF, 0x01, 0x01, 0x01, 0x89, 0x01,
|
||||||
|
0x2C, 0x57, 0x76, 0xFF, 0x01, 0x01, 0x01, 0x8E, 0x01,
|
||||||
|
0x07, 0x52, 0x76, 0xFF, 0x01, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x32, 0x52, 0x76, 0xFF, 0x01, 0x01, 0x05, 0x93, 0x04,
|
||||||
|
0x33, 0x52, 0x76, 0xFF, 0x01, 0x01, 0x05, 0x94, 0x04,
|
||||||
|
0x34, 0x52, 0x76, 0xFF, 0x01, 0x01, 0x05, 0x95, 0x04,
|
||||||
|
0x0F, 0x52, 0x76, 0xFF, 0x01, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x10, 0x52, 0x76, 0xFF, 0x01, 0x01, 0x01, 0x28, 0x01,
|
||||||
|
0x11, 0x52, 0x76, 0xFF, 0x01, 0x01, 0x01, 0x29, 0x01,
|
||||||
|
0x12, 0x50, 0x76, 0xFF, 0x01, 0x01, 0x01, 0x2C, 0x01,
|
||||||
|
0x13, 0x4E, 0x76, 0xFF, 0x01, 0x01, 0x01, 0x2F, 0x01,
|
||||||
|
0x32, 0x52, 0x76, 0xFF, 0x01, 0x01, 0x05, 0x93, 0x04,
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/* Эталон трассы: run_right. Снят record_golden.py — не править руками.
|
||||||
|
* Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */
|
||||||
|
0x01, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x08, 0x00,
|
||||||
|
0x02, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x09, 0x00,
|
||||||
|
0x03, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x0A, 0x00,
|
||||||
|
0x04, 0x56, 0x76, 0x00, 0x01, 0x01, 0x01, 0x0B, 0x00,
|
||||||
|
0x05, 0x5E, 0x76, 0x00, 0x01, 0x01, 0x01, 0x0E, 0x00,
|
||||||
|
0x06, 0x61, 0x76, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00,
|
||||||
|
0x07, 0x64, 0x76, 0x00, 0x01, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0x69, 0x76, 0x00, 0x02, 0x01, 0x01, 0x17, 0x00,
|
||||||
|
0x09, 0x6A, 0x76, 0x00, 0x02, 0x01, 0x01, 0x1C, 0x00,
|
||||||
|
0x0A, 0x6C, 0x76, 0x00, 0x02, 0x01, 0x01, 0x1F, 0x00,
|
||||||
|
0x0B, 0x70, 0x76, 0x00, 0x02, 0x01, 0x01, 0x22, 0x00,
|
||||||
|
0x0C, 0x75, 0x76, 0x00, 0x03, 0x01, 0x01, 0x25, 0x00,
|
||||||
|
0x0D, 0x77, 0x76, 0x00, 0x03, 0x01, 0x01, 0x2A, 0x00,
|
||||||
|
0x0E, 0x7A, 0x76, 0x00, 0x03, 0x01, 0x01, 0x2D, 0x00,
|
||||||
|
0x07, 0x7E, 0x76, 0x00, 0x03, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0x83, 0x76, 0x00, 0x04, 0x01, 0x01, 0x17, 0x00,
|
||||||
|
0x09, 0x84, 0x76, 0x00, 0x04, 0x01, 0x01, 0x1C, 0x00,
|
||||||
|
0x0A, 0x86, 0x76, 0x00, 0x04, 0x01, 0x01, 0x1F, 0x00,
|
||||||
|
0x0B, 0x8A, 0x76, 0x00, 0x04, 0x01, 0x01, 0x22, 0x00,
|
||||||
|
0x0C, 0x8F, 0x76, 0x00, 0x05, 0x01, 0x01, 0x25, 0x00,
|
||||||
|
0x0D, 0x91, 0x76, 0x00, 0x05, 0x01, 0x01, 0x2A, 0x00,
|
||||||
|
0x0E, 0x94, 0x76, 0x00, 0x05, 0x01, 0x01, 0x2D, 0x00,
|
||||||
|
0x07, 0x98, 0x76, 0x00, 0x05, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0x9D, 0x76, 0x00, 0x06, 0x01, 0x01, 0x17, 0x00,
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/* Эталон трассы: stand. Снят record_golden.py — не править руками.
|
||||||
|
* Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x72, 0x76, 0xFF, 0x03, 0x01, 0x00, 0x35, 0x00,
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/* Эталон трассы: step_shift. Снят record_golden.py — не править руками.
|
||||||
|
* Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */
|
||||||
|
0x79, 0x72, 0x76, 0x00, 0x03, 0x01, 0x01, 0x6F, 0x05,
|
||||||
|
0x7A, 0x73, 0x76, 0x00, 0x03, 0x01, 0x01, 0x72, 0x05,
|
||||||
|
0x7B, 0x74, 0x76, 0x00, 0x03, 0x01, 0x01, 0x75, 0x05,
|
||||||
|
0x7C, 0x77, 0x76, 0x00, 0x03, 0x01, 0x01, 0x78, 0x05,
|
||||||
|
0x7D, 0x7B, 0x76, 0x00, 0x03, 0x01, 0x01, 0x7B, 0x05,
|
||||||
|
0x7E, 0x7E, 0x76, 0x00, 0x03, 0x01, 0x01, 0x7E, 0x05,
|
||||||
|
0x7F, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x01, 0x81, 0x05,
|
||||||
|
0x80, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x01, 0x82, 0x05,
|
||||||
|
0x81, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x01, 0x83, 0x05,
|
||||||
|
0x82, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x01, 0x84, 0x05,
|
||||||
|
0x83, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x01, 0x85, 0x05,
|
||||||
|
0x84, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x01, 0x86, 0x05,
|
||||||
|
0x0F, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x00, 0x35, 0x00,
|
||||||
|
0x0F, 0x7D, 0x76, 0x00, 0x04, 0x01, 0x00, 0x35, 0x00,
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/* Эталон трассы: turn. Снят record_golden.py — не править руками.
|
||||||
|
* Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */
|
||||||
|
0x2D, 0x7A, 0x76, 0xFF, 0x04, 0x01, 0x07, 0xD1, 0x01,
|
||||||
|
0x2E, 0x79, 0x76, 0xFF, 0x04, 0x01, 0x07, 0xD4, 0x01,
|
||||||
|
0x2F, 0x77, 0x76, 0xFF, 0x04, 0x01, 0x07, 0xD7, 0x01,
|
||||||
|
0x30, 0x78, 0x76, 0xFF, 0x04, 0x01, 0x07, 0xDA, 0x01,
|
||||||
|
0x01, 0x79, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x08, 0x00,
|
||||||
|
0x02, 0x79, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x09, 0x00,
|
||||||
|
0x03, 0x79, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x0A, 0x00,
|
||||||
|
0x04, 0x79, 0x76, 0xFF, 0x04, 0x01, 0x01, 0x0B, 0x00,
|
||||||
|
0x05, 0x71, 0x76, 0xFF, 0x03, 0x01, 0x01, 0x0E, 0x00,
|
||||||
|
0x06, 0x6E, 0x76, 0xFF, 0x03, 0x01, 0x01, 0x11, 0x00,
|
||||||
|
0x07, 0x6B, 0x76, 0xFF, 0x03, 0x01, 0x01, 0x14, 0x00,
|
||||||
|
0x08, 0x66, 0x76, 0xFF, 0x03, 0x01, 0x01, 0x17, 0x00,
|
||||||
|
0x09, 0x65, 0x76, 0xFF, 0x02, 0x01, 0x01, 0x1C, 0x00,
|
||||||
|
0x0A, 0x63, 0x76, 0xFF, 0x02, 0x01, 0x01, 0x1F, 0x00,
|
||||||
|
0x0B, 0x5F, 0x76, 0xFF, 0x02, 0x01, 0x01, 0x22, 0x00,
|
||||||
|
0x0C, 0x5A, 0x76, 0xFF, 0x02, 0x01, 0x01, 0x25, 0x00,
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* kidblob.h — таблицы анимации Кида в памяти теста.
|
||||||
|
*
|
||||||
|
* KID_BLOB_ADDR обязан совпадать с KD_DATA_OFF из pop_kid.c: продукт
|
||||||
|
* адресует таблицы именно так, и в этом весь смысл — под тестом работает
|
||||||
|
* тот же код доступа, что на устройстве, просто без маппинга W0.
|
||||||
|
*/
|
||||||
|
#ifndef POP_TESTS_KIDBLOB_H
|
||||||
|
#define POP_TESTS_KIDBLOB_H
|
||||||
|
|
||||||
|
#define KID_BLOB_ADDR 0x0100
|
||||||
|
|
||||||
|
extern const unsigned char kid_blob[];
|
||||||
|
extern const unsigned int kid_blob_size;
|
||||||
|
|
||||||
|
void kid_blob_install(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
record_golden.py — снять эталонные трассы для t_phys.
|
||||||
|
|
||||||
|
Зачем скрипт, а не один прогон. Отчёт tcheck ограничен 768 байтами лога, а
|
||||||
|
трасс на все сценарии сильно больше. Поэтому набор умеет снимать РОВНО
|
||||||
|
ОДИН сценарий за прогон (-DSC_RECORD=<n>), а перебор делает этот скрипт:
|
||||||
|
собирает набор N раз, каждый раз забирает из отчёта имя сценария и его
|
||||||
|
трассу и пишет golden/<имя>.h.
|
||||||
|
|
||||||
|
Эталон фиксирует ТЕКУЩЕЕ поведение. Это осознанно: набор страхует
|
||||||
|
поведение-сохраняющие правки (перенос физики Kid -> Char), а не доказывает
|
||||||
|
правильность. Правильность — сверка с SDLPoP. Поэтому переснимать эталоны
|
||||||
|
можно только вместе с внятным объяснением, что и почему поменялось.
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
python3 record_golden.py # снять все сценарии
|
||||||
|
python3 record_golden.py 7 9 # только эти номера
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
GOLDEN = HERE / "golden"
|
||||||
|
# Число сценариев = число TC_RUN в t_phys.c: список ведётся там, здесь его
|
||||||
|
# дублировать незачем.
|
||||||
|
NSCEN = len(re.findall(r"^\s*TC_RUN\(", (HERE / "t_phys.c").read_text(), re.M))
|
||||||
|
|
||||||
|
|
||||||
|
def record(n: int) -> str:
|
||||||
|
# make не видит смены -DSC_RECORD (в зависимостях только файлы), поэтому
|
||||||
|
# объектник набора сносим руками — иначе следующий прогон молча снимет
|
||||||
|
# трассу предыдущего сценария под новым именем.
|
||||||
|
for f in ("phys.rel", "phys.ihx"):
|
||||||
|
(HERE / "build" / f).unlink(missing_ok=True)
|
||||||
|
subprocess.run(["make", "-s", "build/phys.ihx", f"EXTRA_CFLAGS=-DSC_RECORD={n}"],
|
||||||
|
cwd=HERE, check=True)
|
||||||
|
p = subprocess.run(["make", "-s", "run-phys", f"EXTRA_CFLAGS=-DSC_RECORD={n}"],
|
||||||
|
cwd=HERE, capture_output=True, text=True)
|
||||||
|
out = p.stdout
|
||||||
|
m = re.search(r"^\s*### (\S+)\s*$", out, re.M)
|
||||||
|
if not m:
|
||||||
|
raise SystemExit(f"сценарий {n}: в отчёте нет строки '### имя'\n{out}")
|
||||||
|
name = m.group(1)
|
||||||
|
rows = re.findall(r"^\s*([0-9A-F]{18})\s*$", out, re.M)
|
||||||
|
if not rows:
|
||||||
|
raise SystemExit(f"сценарий {n} ({name}): трасса пустая\n{out}")
|
||||||
|
|
||||||
|
lines = [f"/* Эталон трассы: {name}. Снят record_golden.py — не править руками.",
|
||||||
|
" * Поля кадра: frame, x, y, dir, col, row, action, seq_lo, seq_hi. */"]
|
||||||
|
for r in rows:
|
||||||
|
lines.append(" " + ", ".join(f"0x{r[i:i+2]}" for i in range(0, 18, 2)) + ",")
|
||||||
|
GOLDEN.mkdir(exist_ok=True)
|
||||||
|
(GOLDEN / f"{name}.h").write_text("\n".join(lines) + "\n")
|
||||||
|
return f"{name}: {len(rows)} кадров"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
nums = [int(a) for a in sys.argv[1:]] or list(range(1, NSCEN + 1))
|
||||||
|
for n in nums:
|
||||||
|
print(f"[{n}/{NSCEN}] " + record(n))
|
||||||
|
# Итоговая пересборка в обычном режиме, чтобы каталог не остался с
|
||||||
|
# объектниками, собранными под SC_RECORD.
|
||||||
|
subprocess.run(["make", "-s", "clean"], cwd=HERE, check=True)
|
||||||
|
print("эталоны сняты; проверить: make -C", HERE.name, "t_phys")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* scene.c — реализация покадрового раннера (см. scene.h).
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <kbd_raw.h>
|
||||||
|
|
||||||
|
#include "tcheck.h"
|
||||||
|
#include "pop_kid.h"
|
||||||
|
#include "pop_map.h"
|
||||||
|
#include "pop_ctrl.h"
|
||||||
|
#include "pop_geom.h"
|
||||||
|
#include "pop_guard.h"
|
||||||
|
#include "kid_data.h"
|
||||||
|
#include "scene.h"
|
||||||
|
#include "stubs.h"
|
||||||
|
#include "kidblob.h"
|
||||||
|
|
||||||
|
sc_rec sc_trace[SC_TRACE_MAX];
|
||||||
|
uint8_t sc_len;
|
||||||
|
|
||||||
|
static uint8_t sc_fg[30];
|
||||||
|
/* Кромки шва: соседей у сцены нет, поэтому по краям стена (тайл 20). */
|
||||||
|
static const uint8_t sc_edge[6] = { 20, 20, 20, 20, 20, 20 };
|
||||||
|
|
||||||
|
void sc_init(void)
|
||||||
|
{
|
||||||
|
kid_blob_install(); /* таблицы туда, куда смотрит pop_kid */
|
||||||
|
pop_kid_data_load("KID\\KID_DATA.BIN");
|
||||||
|
pop_kid_hp_newgame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_room(const uint8_t *fg, uint8_t room)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < 30; i++) { sc_fg[i] = fg[i]; tk_modif[i] = 0; }
|
||||||
|
pop_map_set(sc_fg);
|
||||||
|
pop_map_set_room(room);
|
||||||
|
/* Изолированная сцена: соседей нет. Тип TILE_WALL (20) с обеих сторон
|
||||||
|
* и сверху/снизу — тогда решения физики зависят только от sc_fg, и
|
||||||
|
* трасса не поедет, если когда-нибудь поменяется загрузка кромок. */
|
||||||
|
pop_map_set_edges(0, 0, 0, 0, sc_edge, sc_edge);
|
||||||
|
tk_start_room = room;
|
||||||
|
tk_log_clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_kid_at(uint8_t col, uint8_t row, int8_t dir)
|
||||||
|
{
|
||||||
|
kid_init(2 /* seq_2_stand */, (int8_t)col, (int8_t)row, dir);
|
||||||
|
pop_kid_hp_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_kid_at_x(uint8_t col, uint8_t row, uint8_t x, int8_t dir)
|
||||||
|
{
|
||||||
|
kid_init(2 /* seq_2_stand */, (int8_t)col, (int8_t)row, dir);
|
||||||
|
Kid.x = x; /* тайл задаёт ряд, X — фазу внутри тайла */
|
||||||
|
pop_determine_col(); /* колонку пересчитать под новый X */
|
||||||
|
pop_kid_hp_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_trace_clear(void) { sc_len = 0; }
|
||||||
|
|
||||||
|
static void sc_record(void)
|
||||||
|
{
|
||||||
|
sc_rec *r;
|
||||||
|
if (sc_len >= SC_TRACE_MAX) return;
|
||||||
|
r = &sc_trace[sc_len++];
|
||||||
|
r->frame = Kid.frame;
|
||||||
|
r->x = Kid.x;
|
||||||
|
r->y = Kid.y;
|
||||||
|
r->dir = Kid.direction;
|
||||||
|
r->col = Kid.curr_col;
|
||||||
|
r->row = Kid.curr_row;
|
||||||
|
r->action = Kid.action;
|
||||||
|
r->seq = (uint16_t)(Kid.curr_seq - SEQTBL_BASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t sc_run(uint8_t keys, uint8_t n)
|
||||||
|
{
|
||||||
|
uint8_t i, done = 0;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
tk_keys_clear();
|
||||||
|
if (keys & SC_L) tk_key_down(KBD_LEFT);
|
||||||
|
if (keys & SC_R) tk_key_down(KBD_RIGHT);
|
||||||
|
if (keys & SC_U) tk_key_down(KBD_UP);
|
||||||
|
if (keys & SC_D) tk_key_down(KBD_DOWN);
|
||||||
|
if (keys & SC_SHIFT) tk_key_down(KBD_LSHIFT);
|
||||||
|
|
||||||
|
/* Порядок ровно как в главном цикле roomtest.c (kid_heal и
|
||||||
|
* отрисовка выпадают — они ничего не решают). pop_loose_tick
|
||||||
|
* оставлен: он не рисование, а МЕХАНИКА — досчитывает тряску и
|
||||||
|
* убирает провалившуюся плиту из карты, то есть меняет то, по чему
|
||||||
|
* физика в следующем кадре принимает решения. */
|
||||||
|
pop_ctrl_tick();
|
||||||
|
kid_tick();
|
||||||
|
pop_phys_tick();
|
||||||
|
pop_loose_tick();
|
||||||
|
|
||||||
|
sc_record();
|
||||||
|
done++;
|
||||||
|
if (sc_len >= SC_TRACE_MAX) break;
|
||||||
|
}
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- сверка с эталоном ---------------------------------------------- */
|
||||||
|
|
||||||
|
void sc_expect(const uint8_t *golden, uint8_t nrec)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
const uint8_t *g = golden;
|
||||||
|
tc_check((int16_t)sc_len, (int16_t)nrec, "sc_len", __LINE__);
|
||||||
|
for (i = 0; i < nrec && i < sc_len; i++, g += SC_REC_BYTES) {
|
||||||
|
const sc_rec *r = &sc_trace[i];
|
||||||
|
/* Номер кадра идёт первым полем каждой проверки: в отчёте видно,
|
||||||
|
* на каком именно кадре трасса разошлась. */
|
||||||
|
tc_check((int16_t)r->frame, (int16_t)g[0], "frame", i);
|
||||||
|
tc_check((int16_t)r->x, (int16_t)g[1], "x", i);
|
||||||
|
tc_check((int16_t)r->y, (int16_t)g[2], "y", i);
|
||||||
|
tc_check((int16_t)r->dir, (int16_t)(int8_t)g[3], "dir", i);
|
||||||
|
tc_check((int16_t)r->col, (int16_t)(int8_t)g[4], "col", i);
|
||||||
|
tc_check((int16_t)r->row, (int16_t)(int8_t)g[5], "row", i);
|
||||||
|
tc_check((int16_t)r->action, (int16_t)g[6], "action", i);
|
||||||
|
tc_check((int16_t)r->seq,
|
||||||
|
(int16_t)((uint16_t)g[7] | ((uint16_t)g[8] << 8)), "seq", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- снятие эталона -------------------------------------------------- */
|
||||||
|
|
||||||
|
static void put(char c)
|
||||||
|
{
|
||||||
|
if (tc_result.logn < TC_LOGSZ) tc_result.log[tc_result.logn++] = (uint8_t)c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_byte(uint8_t v)
|
||||||
|
{
|
||||||
|
static const char hex[] = "0123456789ABCDEF";
|
||||||
|
put(hex[v >> 4]); put(hex[v & 15]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_dump(const char *name)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
tc_result.magic = TC_MAGIC;
|
||||||
|
tc_result.total = 1;
|
||||||
|
tc_result.failed = 1; /* иначе раннер лог не напечатает */
|
||||||
|
put('#'); put('#'); put('#'); put(' ');
|
||||||
|
while (*name) put(*name++);
|
||||||
|
put('\n');
|
||||||
|
for (i = 0; i < sc_len; i++) {
|
||||||
|
const sc_rec *r = &sc_trace[i];
|
||||||
|
put_byte(r->frame);
|
||||||
|
put_byte(r->x);
|
||||||
|
put_byte(r->y);
|
||||||
|
put_byte((uint8_t)r->dir);
|
||||||
|
put_byte((uint8_t)r->col);
|
||||||
|
put_byte((uint8_t)r->row);
|
||||||
|
put_byte(r->action);
|
||||||
|
put_byte((uint8_t)r->seq);
|
||||||
|
put_byte((uint8_t)(r->seq >> 8));
|
||||||
|
put('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* stubs.h — интерфейс тестового окружения (см. stubs.c).
|
||||||
|
*
|
||||||
|
* Что тест может задавать: «нажатые» клавиши (tk_key_down) и модификаторы
|
||||||
|
* тайлов комнаты (tk_modif). Что может проверять: журнал обращений
|
||||||
|
* физики к соседним модулям (tk_log).
|
||||||
|
*/
|
||||||
|
#ifndef POP_TESTS_STUBS_H
|
||||||
|
#define POP_TESTS_STUBS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* ---- клавиатура ------------------------------------------------------ */
|
||||||
|
#define TK_KEYS_MAX 6
|
||||||
|
extern uint16_t tk_keys[TK_KEYS_MAX];
|
||||||
|
void tk_keys_clear(void);
|
||||||
|
void tk_key_down(uint16_t code);
|
||||||
|
|
||||||
|
/* ---- комната --------------------------------------------------------- */
|
||||||
|
extern uint8_t tk_modif[30]; /* room_modif текущей комнаты */
|
||||||
|
extern uint8_t tk_start_room; /* что вернёт pop_level_start_room */
|
||||||
|
|
||||||
|
/* ---- журнал обращений ------------------------------------------------ */
|
||||||
|
#define TK_LOG_MAX 24
|
||||||
|
|
||||||
|
enum {
|
||||||
|
TK_TROB = 1, /* a=room b=tilepos c=type */
|
||||||
|
TK_BUTTON, /* a=room b=tilepos c=type */
|
||||||
|
TK_REDRAW, /* a=tilepos b=kind c=pages */
|
||||||
|
TK_REDRAW_ABOVE, /* a=col b=kind c=pages */
|
||||||
|
TK_MOB_SPAWN /* a=row b=col */
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct { uint8_t kind, a, b, c; } tk_log_e;
|
||||||
|
typedef struct { uint8_t n, overflow; tk_log_e e[TK_LOG_MAX]; } tk_log_t;
|
||||||
|
|
||||||
|
extern tk_log_t tk_log;
|
||||||
|
void tk_log_clear(void);
|
||||||
|
|
||||||
|
/* Сколько записей данного вида в журнале (0 = не было). */
|
||||||
|
uint8_t tk_log_count(uint8_t kind);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* t_grab.c — прыжок с места через провал в три тайла с зацепом за дальнюю
|
||||||
|
* кромку (уровень 2, комната 9: Кид на краю (1,1) прыгает и должен
|
||||||
|
* повиснуть на (1,5)).
|
||||||
|
*
|
||||||
|
* Зачем отдельный набор. Трюк требует точной фазы и вручную выходит далеко
|
||||||
|
* не всегда — в SDLPoP тоже. Поэтому проверяем не «получилось с первого
|
||||||
|
* раза», а то, что действительно важно: **окно существует и достижимо**.
|
||||||
|
* Разница между «сложно» и «невозможно» — это разница между «так в
|
||||||
|
* оригинале» и «баг у нас», и глазами в MAME её не различить.
|
||||||
|
*
|
||||||
|
* Два утверждения:
|
||||||
|
* 1. есть хотя бы одна стартовая X, из которой зацеп удаётся;
|
||||||
|
* 2. короткий шаг (Shift+вперёд) приводит Кида В ЭТО окно — иначе окно
|
||||||
|
* было бы недостижимо для игрока, что равно его отсутствию.
|
||||||
|
*
|
||||||
|
* При провале в отчёт уходит карта исходов по всей ширине плиты:
|
||||||
|
* G — зацепился, L — приземлился на дальнюю плиту, . — сорвался.
|
||||||
|
*
|
||||||
|
* Механику зацепа задаёт check_grab (seg006:0A28): она работает ТОЛЬКО на
|
||||||
|
* кадрах 102..105 (начало падения) и только при зажатом Shift. mininim,
|
||||||
|
* для сравнения, разрешает повиснуть прямо в дуге прыжка (kid-jump.c,
|
||||||
|
* кадры 7..10) — но это переработка, а не порт, и источник истины у нас
|
||||||
|
* SDLPoP.
|
||||||
|
*/
|
||||||
|
#include "tcheck.h"
|
||||||
|
#include "scene.h"
|
||||||
|
#include "stubs.h"
|
||||||
|
#include "pop_kid.h"
|
||||||
|
#include "pop_map.h"
|
||||||
|
|
||||||
|
#define E 0
|
||||||
|
#define F 1
|
||||||
|
#define P 3
|
||||||
|
#define W 20
|
||||||
|
|
||||||
|
/* Уровень 2, комната 9, ряд 1: факел(0), кнопка(1), провал(2,3,4),
|
||||||
|
* колонна(5). Ряд 2 под провалом пуст — падение уходит из комнаты. */
|
||||||
|
static const uint8_t room9[30] = {
|
||||||
|
E, E, E, E, E, W, W, W, W, W,
|
||||||
|
F, F, E, E, E, P, F, F, F, P,
|
||||||
|
W, W, E, E, E, P, F, W, W, W,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Судить по curr_row нельзя: у улетающего вниз Кида ряд заворачивается
|
||||||
|
* (0,1,2,0,…), и падение выглядит как «остался наверху». Надёжный признак
|
||||||
|
* — ДЕЙСТВИЕ: 2 = вис/подтягивание, 6 = вис у стены. */
|
||||||
|
#define ACT_STAND 0
|
||||||
|
#define ACT_RUN_JUMP 1
|
||||||
|
#define ACT_HANG_CLIMB 2
|
||||||
|
#define ACT_HANG_STRAIGHT 6
|
||||||
|
|
||||||
|
#define X_LO 76
|
||||||
|
#define X_HI 99
|
||||||
|
|
||||||
|
static char probe(uint8_t x, uint8_t k1, uint8_t k2)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
sc_room(room9, 9);
|
||||||
|
sc_kid_at_x(1, 1, x, 0 /* лицом вправо */);
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(k1, 4); /* фаза 1: ↑+вперёд — прыжок с места */
|
||||||
|
sc_run(k2, 26); /* фаза 2: Shift — попытка зацепа */
|
||||||
|
|
||||||
|
for (i = 0; i < sc_len; i++) {
|
||||||
|
uint8_t a = sc_trace[i].action;
|
||||||
|
if (a == ACT_HANG_CLIMB || a == ACT_HANG_STRAIGHT) return 'G';
|
||||||
|
}
|
||||||
|
i = sc_len ? (uint8_t)(sc_len - 1) : 0;
|
||||||
|
if (sc_trace[i].col >= 5 &&
|
||||||
|
(sc_trace[i].action == ACT_STAND || sc_trace[i].action == ACT_RUN_JUMP))
|
||||||
|
return 'L';
|
||||||
|
return '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put(char c)
|
||||||
|
{
|
||||||
|
if (tc_result.logn < TC_LOGSZ) tc_result.log[tc_result.logn++] = (uint8_t)c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void puts_(const char *s) { while (*s) put(*s++); }
|
||||||
|
|
||||||
|
/* Карта исходов — только когда что-то сломалось: по ней сразу видно,
|
||||||
|
* окно съехало или исчезло. */
|
||||||
|
static void dump_map(const char *tag, uint8_t k1, uint8_t k2)
|
||||||
|
{
|
||||||
|
uint8_t x;
|
||||||
|
puts_(tag);
|
||||||
|
for (x = X_LO; x <= X_HI; x++) put(probe(x, k1, k2));
|
||||||
|
put('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Куда встаёт Кид после n-го короткого шага от x0. */
|
||||||
|
static uint8_t step_x(uint8_t x0, uint8_t nsteps)
|
||||||
|
{
|
||||||
|
uint8_t n, fx = x0;
|
||||||
|
sc_room(room9, 9);
|
||||||
|
sc_kid_at_x(1, 1, x0, 0);
|
||||||
|
for (n = 0; n < nsteps; n++) {
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(SC_SHIFT | SC_R, 4); /* шаг = нажатие, не удержание */
|
||||||
|
sc_run(0, 6); /* отпустить, дать доиграть */
|
||||||
|
if (sc_len) fx = sc_trace[sc_len - 1].x;
|
||||||
|
}
|
||||||
|
return fx;
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(grab_window_exists)
|
||||||
|
{
|
||||||
|
uint8_t x, n = 0, lo = 0, hi = 0;
|
||||||
|
for (x = X_LO; x <= X_HI; x++)
|
||||||
|
if (probe(x, SC_U | SC_R, SC_SHIFT) == 'G') {
|
||||||
|
if (!n) lo = x;
|
||||||
|
hi = x;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
/* Окно обязано существовать: без него трюк непроходим в принципе, а
|
||||||
|
* уровень 2 без него не пройти. */
|
||||||
|
TC_TRUE(n > 0);
|
||||||
|
if (!n) {
|
||||||
|
dump_map("no-window: ", SC_U | SC_R, SC_SHIFT);
|
||||||
|
} else {
|
||||||
|
/* Границы фиксируем: если поедут — это осознанное изменение физики,
|
||||||
|
* а не случайность. */
|
||||||
|
TC_EQ(lo, 91);
|
||||||
|
TC_EQ(hi, 95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(grab_window_reachable_by_safe_step)
|
||||||
|
{
|
||||||
|
/* Игрок доходит до кромки короткими шагами. Если они не попадают в
|
||||||
|
* окно, существование окна ничего не стоит. */
|
||||||
|
TC_EQ(step_x(80, 1), 91);
|
||||||
|
TC_EQ(step_x(80, 2), 95);
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(grab_same_for_all_key_patterns)
|
||||||
|
{
|
||||||
|
/* Живой игрок держит «вперёд» и жмёт Shift по-разному. Исход не должен
|
||||||
|
* зависеть от того, отпущена ли стрелка и когда нажат Shift. */
|
||||||
|
uint8_t x;
|
||||||
|
for (x = X_LO; x <= X_HI; x++) {
|
||||||
|
char a = probe(x, SC_U | SC_R, SC_SHIFT);
|
||||||
|
char b = probe(x, SC_U | SC_R, SC_SHIFT | SC_R);
|
||||||
|
char c = probe(x, SC_U | SC_R | SC_SHIFT, SC_SHIFT | SC_R);
|
||||||
|
TC_EQ(b, a);
|
||||||
|
TC_EQ(c, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
sc_init();
|
||||||
|
TC_RUN(grab_window_exists);
|
||||||
|
TC_RUN(grab_window_reachable_by_safe_step);
|
||||||
|
TC_RUN(grab_same_for_all_key_patterns);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* t_phys.c — характеризация физики Кида (pop_map + pop_kid + pop_ctrl).
|
||||||
|
*
|
||||||
|
* Назначение набора — СТРАХОВКА ПОД РЕФАКТОРИНГ, а не проверка
|
||||||
|
* правильности. Эталоны здесь сняты с текущего билда, то есть
|
||||||
|
* консервируют и текущие баги; правильность по-прежнему проверяется
|
||||||
|
* против SDLPoP. Смысл в другом: перенос физики с `Kid.` на `Char.`
|
||||||
|
* обязан быть поведение-сохраняющим, и «трасса до == трасса после» ловит
|
||||||
|
* ровно тот класс ошибок, который там возможен.
|
||||||
|
*
|
||||||
|
* Поэтому при осознанном изменении поведения эталон ПЕРЕСНИМАЕТСЯ (см.
|
||||||
|
* SC_RECORD ниже), а в коммите должно быть написано, почему трасса
|
||||||
|
* поменялась. Молча обновлённый эталон обесценивает весь набор.
|
||||||
|
*
|
||||||
|
* Снятие эталона:
|
||||||
|
* make -C applications/PoP/roomtest/tests-host t_phys SC_RECORD=1
|
||||||
|
* Трассы уходят в отчёт tcheck готовыми C-строками — вставить в golden_*.
|
||||||
|
*/
|
||||||
|
#include "tcheck.h"
|
||||||
|
#include "scene.h"
|
||||||
|
#include "stubs.h"
|
||||||
|
#include "pop_kid.h"
|
||||||
|
#include "pop_map.h"
|
||||||
|
|
||||||
|
/* Коды тайлов (pop_map): 0 пусто, 1 пол, 3 колонна, 11 loose, 20 стена. */
|
||||||
|
#define E 0
|
||||||
|
#define F 1
|
||||||
|
#define P 3
|
||||||
|
#define L 11
|
||||||
|
#define W 20
|
||||||
|
|
||||||
|
/* Ровный пол во всю комнату: базовая сцена для ходьбы/бега/разворота. */
|
||||||
|
static const uint8_t room_flat[30] = {
|
||||||
|
W, E, E, E, E, E, E, E, E, E,
|
||||||
|
W, F, F, F, F, F, F, F, F, F,
|
||||||
|
W, W, W, W, W, W, W, W, W, W,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Пол с обрывом после колонки 5: падение с ряда 1 на ряд 2. */
|
||||||
|
static const uint8_t room_ledge[30] = {
|
||||||
|
W, E, E, E, E, E, E, E, E, E,
|
||||||
|
W, F, F, F, F, F, E, E, E, E,
|
||||||
|
W, F, F, F, F, F, F, F, F, W,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Проваливающаяся плита на (1,4) — сцена под loose-механику. */
|
||||||
|
static const uint8_t room_loose[30] = {
|
||||||
|
W, E, E, E, E, E, E, E, E, E,
|
||||||
|
W, F, F, F, L, F, F, F, F, F,
|
||||||
|
W, F, F, F, F, F, F, F, F, W,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Провал в ТРИ пустых тайла (колонки 2,3,4) — геометрия из BUG-RJUMP-1
|
||||||
|
* (ур. 2, комнаты 1 и 9). Разбег-прыжок влево с колонки 5 обязан
|
||||||
|
* доставать до колонки 1; у нас Кид сваливается в провал. */
|
||||||
|
static const uint8_t room_gap3[30] = {
|
||||||
|
W, E, E, E, E, E, E, E, E, E,
|
||||||
|
W, F, E, E, E, F, F, F, F, F,
|
||||||
|
W, F, F, F, F, F, F, F, F, W,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* SC_RECORD=<n> — снять эталон СЦЕНАРИЯ n (нумерация в порядке вызовов в
|
||||||
|
* main). По одному за прогон: лог tcheck 768 байт, на все трассы сразу
|
||||||
|
* его не хватает. Перебором занимается record_golden.py. */
|
||||||
|
#ifndef SC_RECORD
|
||||||
|
#define SC_RECORD 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Снять или сверить — одной строкой в каждом сценарии, чтобы режимы не
|
||||||
|
* разъезжались. Имя сценария = имя файла эталона. */
|
||||||
|
#if SC_RECORD
|
||||||
|
static uint8_t sc_idx;
|
||||||
|
#define SC_CHECK(g, nm) \
|
||||||
|
do { if (++sc_idx == (SC_RECORD)) sc_dump(nm); } while (0)
|
||||||
|
#else
|
||||||
|
#define SC_CHECK(g, nm) sc_expect((g), (uint8_t)(sizeof(g) / SC_REC_BYTES))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ---- эталоны (сняты SC_RECORD=1) ------------------------------------ *
|
||||||
|
* В режиме снятия эталонов файлов golden/ ещё нет — и не должно быть:
|
||||||
|
* первый прогон их и порождает. */
|
||||||
|
#if !SC_RECORD
|
||||||
|
|
||||||
|
static const uint8_t golden_stand[] = {
|
||||||
|
#include "golden/stand.h"
|
||||||
|
};
|
||||||
|
static const uint8_t golden_run_right[] = {
|
||||||
|
#include "golden/run_right.h"
|
||||||
|
};
|
||||||
|
static const uint8_t golden_step_shift[] = {
|
||||||
|
#include "golden/step_shift.h"
|
||||||
|
};
|
||||||
|
static const uint8_t golden_turn[] = {
|
||||||
|
#include "golden/turn.h"
|
||||||
|
};
|
||||||
|
static const uint8_t golden_crouch[] = {
|
||||||
|
#include "golden/crouch.h"
|
||||||
|
};
|
||||||
|
static const uint8_t golden_jump_up[] = {
|
||||||
|
#include "golden/jump_up.h"
|
||||||
|
};
|
||||||
|
static const uint8_t golden_fall_ledge[] = {
|
||||||
|
#include "golden/fall_ledge.h"
|
||||||
|
};
|
||||||
|
static const uint8_t golden_loose[] = {
|
||||||
|
#include "golden/loose.h"
|
||||||
|
};
|
||||||
|
static const uint8_t golden_rjump_gap3[] = {
|
||||||
|
#include "golden/rjump_gap3.h"
|
||||||
|
};
|
||||||
|
|
||||||
|
#else
|
||||||
|
/* SC_RECORD: имена нужны только чтобы SC_CHECK() раскрылся. */
|
||||||
|
#define golden_stand 0
|
||||||
|
#define golden_run_right 0
|
||||||
|
#define golden_step_shift 0
|
||||||
|
#define golden_turn 0
|
||||||
|
#define golden_crouch 0
|
||||||
|
#define golden_jump_up 0
|
||||||
|
#define golden_fall_ledge 0
|
||||||
|
#define golden_loose 0
|
||||||
|
#define golden_rjump_gap3 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ---- сценарии -------------------------------------------------------- */
|
||||||
|
|
||||||
|
TC_TEST(phys_stand_is_stable)
|
||||||
|
{
|
||||||
|
/* Никакого ввода: Кид на ровном полу обязан стоять неподвижно. Тест
|
||||||
|
* дешёвый, но ловит самое обидное — «сам поехал» после правки
|
||||||
|
* геометрии или окна Char. */
|
||||||
|
sc_room(room_flat, 1);
|
||||||
|
sc_kid_at(3, 1, -1);
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(0, 12);
|
||||||
|
SC_CHECK(golden_stand, "stand");
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(phys_run_right)
|
||||||
|
{
|
||||||
|
sc_room(room_flat, 1);
|
||||||
|
sc_kid_at(1, 1, 0);
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(SC_R, 24);
|
||||||
|
SC_CHECK(golden_run_right, "run_right");
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(phys_step_with_shift)
|
||||||
|
{
|
||||||
|
/* Shift + вперёд — осторожный шаг: одна из самых чувствительных к
|
||||||
|
* distance_to_edge веток. */
|
||||||
|
sc_room(room_flat, 1);
|
||||||
|
sc_kid_at(3, 1, 0);
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(SC_R | SC_SHIFT, 20);
|
||||||
|
SC_CHECK(golden_step_shift, "step_shift");
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(phys_turn_around)
|
||||||
|
{
|
||||||
|
sc_room(room_flat, 1);
|
||||||
|
sc_kid_at(4, 1, 0);
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(SC_L, 16);
|
||||||
|
SC_CHECK(golden_turn, "turn");
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(phys_crouch_and_stand_up)
|
||||||
|
{
|
||||||
|
sc_room(room_flat, 1);
|
||||||
|
sc_kid_at(4, 1, 0);
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(SC_D, 8);
|
||||||
|
sc_run(0, 12);
|
||||||
|
SC_CHECK(golden_crouch, "crouch");
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(phys_jump_up_nothing_above)
|
||||||
|
{
|
||||||
|
/* Над головой пусто — seq_28; проверяем и смещение по x, которое
|
||||||
|
* оригинал делает перед прыжком. */
|
||||||
|
sc_room(room_flat, 1);
|
||||||
|
sc_kid_at(4, 1, -1);
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(SC_U, 20);
|
||||||
|
SC_CHECK(golden_jump_up, "jump_up");
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(phys_run_off_ledge)
|
||||||
|
{
|
||||||
|
/* Бег в обрыв: check_on_floor -> start_fall -> land на ряду 2.
|
||||||
|
* Сценарий, вокруг которого крутился BUG-FALL-SWORD-1. */
|
||||||
|
sc_room(room_ledge, 1);
|
||||||
|
sc_kid_at(1, 1, 0);
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(SC_R, 34);
|
||||||
|
SC_CHECK(golden_fall_ledge, "fall_ledge");
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(phys_loose_floor_breaks)
|
||||||
|
{
|
||||||
|
/* Пробежка по loose-плите: тайл обязан затрястись и провалиться, а
|
||||||
|
* физика — сообщить об этом соседям (trob + перерисовка). */
|
||||||
|
sc_room(room_loose, 1);
|
||||||
|
sc_kid_at(1, 1, 0);
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(SC_R, 34);
|
||||||
|
SC_CHECK(golden_loose, "loose");
|
||||||
|
#if !SC_RECORD
|
||||||
|
/* Журнал заглушек: плита обязана была отвалиться (кусок отрывается —
|
||||||
|
* pop_loose_mob_spawn) и попросить перерисовку. Это уже не
|
||||||
|
* характеризация, а содержательная проверка — заглушка-пустышка её не
|
||||||
|
* дала бы. Через trob loose у нас НЕ идёт (только пики/ворота), у него
|
||||||
|
* свой pop_loose_modif — поэтому спрашиваем именно про кусок. */
|
||||||
|
TC_TRUE(tk_log_count(TK_MOB_SPAWN) > 0);
|
||||||
|
TC_TRUE(tk_log_count(TK_REDRAW) > 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TC_TEST(phys_running_jump_over_3tile_gap)
|
||||||
|
{
|
||||||
|
/* BUG-RJUMP-1. Разбег влево от колонки 8, дальше Up — разбег-прыжок.
|
||||||
|
* Ожидание оригинала: перелёт провала в три тайла и посадка на (1,1).
|
||||||
|
* Сейчас эталон фиксирует НАШЕ поведение (падение в провал) — когда
|
||||||
|
* баг починим, трасса поменяется осознанно и эталон переснимется. */
|
||||||
|
sc_room(room_gap3, 1);
|
||||||
|
sc_kid_at(8, 1, -1);
|
||||||
|
sc_trace_clear();
|
||||||
|
sc_run(SC_L, 8); /* разбег */
|
||||||
|
sc_run(SC_L | SC_U, 26); /* прыжок и посадка */
|
||||||
|
SC_CHECK(golden_rjump_gap3, "rjump_gap3");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- точка входа ----------------------------------------------------- */
|
||||||
|
|
||||||
|
void main(void)
|
||||||
|
{
|
||||||
|
sc_init();
|
||||||
|
TC_RUN(phys_stand_is_stable);
|
||||||
|
TC_RUN(phys_run_right);
|
||||||
|
TC_RUN(phys_step_with_shift);
|
||||||
|
TC_RUN(phys_turn_around);
|
||||||
|
TC_RUN(phys_crouch_and_stand_up);
|
||||||
|
TC_RUN(phys_jump_up_nothing_above);
|
||||||
|
TC_RUN(phys_run_off_ledge);
|
||||||
|
TC_RUN(phys_loose_floor_breaks);
|
||||||
|
TC_RUN(phys_running_jump_over_3tile_gap);
|
||||||
|
}
|
||||||
+10
-2
@@ -29,9 +29,17 @@ BUILD := build
|
|||||||
|
|
||||||
# --code-loc: стартовый код занимает 13 байт по 0x0000, код с 0x0200.
|
# --code-loc: стартовый код занимает 13 байт по 0x0000, код с 0x0200.
|
||||||
# --data-loc 0x8000: данные заведомо выше кода.
|
# --data-loc 0x8000: данные заведомо выше кода.
|
||||||
|
#
|
||||||
|
# CODE_LOC переопределяется набором, если ему нужна нижняя память под
|
||||||
|
# данные: так делает t_phys в roomtest — он кладёт по 0x100 страницу
|
||||||
|
# таблиц анимации, которую на устройстве туда приводит маппинг W0.
|
||||||
|
# EXTRA_INC — заголовки, которых нет ни в testkit, ни в ENGINE_DIR
|
||||||
|
# (например libc/libbgi, когда модуль под тестом их включает).
|
||||||
|
CODE_LOC ?= 0x0200
|
||||||
|
EXTRA_INC ?=
|
||||||
CFLAGS := -mz80 --no-std-crt0 --std-c99 --opt-code-size -I$(TESTKIT) \
|
CFLAGS := -mz80 --no-std-crt0 --std-c99 --opt-code-size -I$(TESTKIT) \
|
||||||
$(if $(ENGINE_DIR),-I$(ENGINE_DIR))
|
$(if $(ENGINE_DIR),-I$(ENGINE_DIR)) $(EXTRA_INC) $(EXTRA_CFLAGS)
|
||||||
LDFLAGS := -mz80 --no-std-crt0 --code-loc 0x0200 --data-loc 0x8000
|
LDFLAGS := -mz80 --no-std-crt0 --code-loc $(CODE_LOC) --data-loc 0x8000
|
||||||
|
|
||||||
SUITES := $(patsubst t_%.c,%,$(notdir $(wildcard t_*.c)))
|
SUITES := $(patsubst t_%.c,%,$(notdir $(wildcard t_*.c)))
|
||||||
COMMON := $(BUILD)/crt0_ucsim.rel $(BUILD)/tcheck.rel
|
COMMON := $(BUILD)/crt0_ucsim.rel $(BUILD)/tcheck.rel
|
||||||
|
|||||||
Reference in New Issue
Block a user