Files
Sprinter-SDCC/applications/PoP/roomtest/tests-host/t_geom.c
T
Александр Петров 61d4255091 testkit: модульные тесты под ucsim_z80 + планы покрытия
Обвязка для быстрых тестов plain-C логики: секунды вместо прогона в MAME,
без образа диска.  ucsim_z80 идёт в комплекте нашего SDCC — новых
зависимостей нет.

ПОЧЕМУ ПОД Z80, А НЕ ХОСТОВЫМ GCC.  У SDCC z80 int 16 бит, у хоста 32, и
расходится это НЕ в объявлениях, а в выражениях: integer promotion
повышает операнды до int независимо от того, объявлены они как uint8_t
или uint16_t.  Перевод кода на фиксированные типы разницу не убирает —
убирает только исполнение с z80-семантикой.  Побочно проверяется
кодогенерация SDCC и модули с inline-asm, которых хостовая сборка не
видит в принципе.

Устройство: crt0_ucsim.s (SP, зануление, main, halt), tcheck.* (итог в
структуру в ОЗУ), run_ucsim.py (гоняет ucsim, дампит tc_result, печатает
отчёт), host-tests.mk (общие правила).  Вывода через printf нет: тестовый
бинарь линкуется без Sprinter-libc.  Через ucsim-simif не идём — номера
его команд плавают между версиями, halt + dump работают везде.

Наборы лежат РЯДОМ с проверяемым кодом, обвязка общая:
  testkit/t_selftest.c                     — самопроверка (sizeof(int)==2)
  applications/PoP/roomtest/tests-host/    — движок PoP

Первый содержательный набор — t_geom: сверяет рукописный asm-LCG из
pop_geom.c с наивной 32-битной формулой на 128 шагах.  Заявка «бит-в-бит
как в SDLPoP» до сих пор держалась на комментарии.  Тест проверен
мутацией: порча эталонной константы даёт красный.

Планы дальнейшего покрытия:
  docs/host-tests-plan.md                    — libc и libbgi (не начато)
  applications/PoP/docs/host_tests_plan.md   — движок PoP

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 22:36:16 +03:00

147 lines
5.5 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.
/*
* t_geom.c — тесты pop_geom.c (геометрия комнаты + PRNG оригинала).
*
* Модуль выбран первым, потому что не тянет за собой ничего: ни графики,
* ни данных уровня, ни клавиатуры — только свой заголовок.
*
* Главный тест здесь — geom_lcg_matches_reference. В pop_geom.c LCG
* оригинала (s = s*214013 + 2531011) написан на ассемблере по схеме
* Горнера ради обхода __mullong, и заявка «бит-в-бит как в SDLPoP» до сих
* пор держалась только на комментарии. Тест сверяет рукописный asm с
* наивной 32-битной формулой на каждом шаге — и по возвращаемому значению,
* и по обеим половинам сида.
*/
#include "tcheck.h"
#include "pop_geom.h"
/* ---- геометрия ------------------------------------------------------ */
TC_TEST(geom_xbump_layout)
{
/* Шаг колонок равномерный и равен TILE_SIZEX — на этом стоит вся
* арифметика колонок (x_bump[col + FIRST_ONSCREEN_COLUMN]). */
uint8_t i;
for (i = 1; i < 20; i++)
TC_EQ(pop_x_bump[i] - pop_x_bump[i - 1], TILE_SIZEX);
/* Центр тайла колонки 0 — та самая величина, которую кладут в Char.x
* do_startpos и pos_guards (seg003). */
TC_EQ(pop_x_bump[0 + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX, 72);
TC_EQ(pop_x_bump[9 + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX, 198);
}
TC_TEST(geom_yland_rows)
{
/* y_land[row + 1]; [0] — ряд «над комнатой». */
TC_EQ(pop_y_land[0], -8);
TC_EQ(pop_y_land[1], 55);
TC_EQ(pop_y_land[2], 118);
TC_EQ(pop_y_land[3], 181);
TC_EQ(pop_y_land[4], 244);
/* Шаг между рядами — TILE_SIZEY. */
TC_EQ(pop_y_land[2] - pop_y_land[1], TILE_SIZEY);
TC_EQ(pop_y_land[3] - pop_y_land[2], TILE_SIZEY);
}
TC_TEST(geom_y_to_row)
{
/* Пол каждого ряда должен давать номер этого ряда. */
TC_EQ(pop_y_to_row(pop_y_land[1]), 0);
TC_EQ(pop_y_to_row(pop_y_land[2]), 1);
TC_EQ(pop_y_to_row(pop_y_land[3]), 2);
/* Ряд над комнатой. */
TC_EQ(pop_y_to_row(pop_y_land[0]), -1);
/* Оборот mod 4: ряд «под комнатой» сворачивается обратно в -1 —
* ровно на это опирается спавн падающего куска у нижней кромки. */
TC_EQ(pop_y_to_row(pop_y_land[4]), -1);
}
/* ---- PRNG: сверка asm-LCG с эталонной формулой ---------------------- */
#if POP_PRANDOM_EXACT
#define LCG_ITERS 128
TC_TEST(geom_lcg_matches_reference)
{
pop_rnd_t s;
unsigned long ref = 12345UL; /* произвольный ненулевой старт */
uint16_t i, got, want;
pop_prandom_set(s, 12345u);
for (i = 0; i < LCG_ITERS; i++) {
got = pop_prandom(&s, 255u);
ref = ref * 214013UL + 2531011UL;
want = (uint16_t)((uint16_t)(ref >> 16) % 256u);
/* Обрываемся на первом расхождении: иначе одна ошибка в умножении
* забила бы весь буфер отчёта однотипными строками. */
if (got != want ||
s.lo != (uint16_t)ref ||
s.hi != (uint16_t)(ref >> 16)) {
TC_EQ(got, want);
TC_EQ(s.lo, (uint16_t)ref);
TC_EQ(s.hi, (uint16_t)(ref >> 16));
break;
}
}
TC_EQ(i, LCG_ITERS); /* прошли всю дистанцию без расхождений */
}
TC_TEST(geom_lcg_seed_zero)
{
/* Ноль — законный сид (кладку сеют номером комнаты + ряд + колонка,
* что вполне даёт 0). У LCG ноль не является неподвижной точкой —
* убеждаемся, что asm это воспроизводит. */
pop_rnd_t s;
unsigned long ref = 0UL;
pop_prandom_set(s, 0u);
pop_prandom(&s, 255u);
ref = ref * 214013UL + 2531011UL;
TC_EQ(s.lo, (uint16_t)ref);
TC_EQ(s.hi, (uint16_t)(ref >> 16));
}
#endif /* POP_PRANDOM_EXACT */
TC_TEST(geom_prandom_range)
{
/* Оба генератора обязаны укладываться в 0..maxv, в том числе когда
* maxv+1 НЕ степень двойки (там другая ветка pop_rnd_fit). */
pop_rnd_t s;
uint16_t i, v, seen_hi = 0;
uint16_t out_of_range = 0;
pop_prandom_set(s, 1u);
for (i = 0; i < 200; i++) {
v = pop_prandom(&s, 4u); /* n = 5, не степень двойки */
if (v > 4) out_of_range++;
if (v == 4) seen_hi = 1;
}
TC_EQ(out_of_range, 0);
TC_TRUE(seen_hi); /* верхняя граница достижима */
pop_prandom_set(s, 7u);
out_of_range = 0;
for (i = 0; i < 64; i++) {
v = pop_prandom(&s, 1u); /* n = 2, маска */
if (v > 1) out_of_range++;
}
TC_EQ(out_of_range, 0);
}
int main(void)
{
TC_RUN(geom_xbump_layout);
TC_RUN(geom_yland_rows);
TC_RUN(geom_y_to_row);
#if POP_PRANDOM_EXACT
TC_RUN(geom_lcg_matches_reference);
TC_RUN(geom_lcg_seed_zero);
#endif
TC_RUN(geom_prandom_range);
return 0;
}