8cac51d592
mob_tick_one (927) и mob_render (976/977) считали `(y+60)/63 % 4 - 1` вручную, хотя pop_y_to_row — точный эквивалент этой формулы на всём int16_t (включая усечение деления к нулю для отрицательных). В asm это были три пары __divsint+__modsint, ~16 200 тактов (3,8 % кадра) — только пока кусок плиты в полёте, то есть в самом тяжёлом кадре. В банке 7 теперь ноль __divsint. Эквивалентность закреплена тестом geom_y_to_row_matches_formula: перебор −400..400 против исходной формулы (вызовы разбросаны по трём банкам, соблазн написать деление «по месту» возвращается). tests-host: [geom] 39 -> 840, все 5 наборов прошли. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
164 lines
6.5 KiB
C
164 lines
6.5 KiB
C
/*
|
||
* 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);
|
||
}
|
||
|
||
/* Цепочка сравнений в pop_y_to_row обязана совпадать с ИСХОДНОЙ формулой
|
||
* `(y + 60) / 63 % 4 - 1` во всём рабочем диапазоне — включая усечение
|
||
* деления К НУЛЮ для отрицательных. Тест держит инвариант: вызовы
|
||
* разбросаны по банкам (pop_map, pop_bg, pop_room), и соблазн написать
|
||
* деление «по месту» возвращается. */
|
||
TC_TEST(geom_y_to_row_matches_formula)
|
||
{
|
||
int16_t y;
|
||
/* −400..400 с запасом накрывает всё: кусок loose стартует у потолка
|
||
* (ряд −1) и улетает ниже поля в комнату снизу. */
|
||
for (y = -400; y <= 400; y++) {
|
||
int16_t t = (int16_t)(y + 60);
|
||
TC_EQ(pop_y_to_row(y), (int8_t)(t / 63 % 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);
|
||
TC_RUN(geom_y_to_row_matches_formula);
|
||
#if POP_PRANDOM_EXACT
|
||
TC_RUN(geom_lcg_matches_reference);
|
||
TC_RUN(geom_lcg_seed_zero);
|
||
#endif
|
||
TC_RUN(geom_prandom_range);
|
||
return 0;
|
||
}
|