Files
Sprinter-SDCC/applications/PoP/roomtest/tests-host/t_geom.c
T
snark13 8175121d25 scr_x: таблица готовых значений на весь диапазон, включая отрицательные
Первая версия крыла только 0..255 байтовой таблицей x/7 по тождеству
8x/7 == x + x/7.  Это было мимо: obj_x = 2*fwd - 116 уходит в минус, как
только fwd < 58 (левее x_bump[5]) — то есть у ЛЕВОЙ КРОМКИ комнаты, и там
мы продолжали звать __divsint.

Границы взяты из данных, а не на глаз: kid_data.bin даёт dx кадров Кида
-5..+10, стража -2..+10; при Char.x типа uint8_t и render_dx из
{-140,0,+140} полный диапазон obj_x = -416..695.  SCRX[1152] кроет
-448..703 — деление стало недостижимым, оставлено страховкой.

Хранится ГОТОВОЕ значение (int16_t), а не x/7: байтовая таблица вдвое
меньше, но со знаковыми значениями требует расширения знака плюс
16-битного сложения — те же такты, что лишний add hl,hl при 2-байтном
индексе.  Кодоген проверен: индекс полный 16-битный (грабли
sdcc_z80_const_ptr_index_bug обойдены отдельной uint16_t-переменной),
~130 тактов номинала против ~1 000 у __divsint.

Банк 4: 7 336 -> 9 394 из 16 384 (свободно 6 990).  Таблица сверена
питоном обратно из .c (1 152 записи), правило генерации «усечение к нулю»
закреплено тестом geom_mul8div7_trunc_to_zero на целевом компиляторе.
tests-host: [geom] 1961 -> 1992, все 5 наборов прошли.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 20:35:01 +03:00

182 lines
7.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.
/*
* 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));
}
}
/* calc_screen_x_coord (seg008:2602) считается в pop_cdraw.c ГОТОВОЙ таблицей
* SCRX (банк 4, сюда не линкуется). Таблица сгенерирована питоном по правилу
* «усечение К НУЛЮ»: значение = знак(x) * (|x|*8 / 7). Проверяем ИМЕННО это
* правило на целевом компиляторе: если бы __divsint у SDCC округлял иначе
* (к минус бесконечности), вся отрицательная половина таблицы уехала бы на
* пиксель, а поймалось бы это только глазами на левой кромке комнаты. */
TC_TEST(geom_mul8div7_trunc_to_zero)
{
int16_t x;
/* Полный диапазон obj_x по данным кадров: −416..695 (см. шапку scr_x). */
for (x = -448; x <= 703; x++) {
int16_t want = (int16_t)(((x < 0) ? -x : x) * 8 / 7);
if (x < 0) want = (int16_t)-want;
TC_EQ((int16_t)(x * 8 / 7), want);
}
}
/* ---- 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);
TC_RUN(geom_mul8div7_trunc_to_zero);
#if POP_PRANDOM_EXACT
TC_RUN(geom_lcg_matches_reference);
TC_RUN(geom_lcg_seed_zero);
#endif
TC_RUN(geom_prandom_range);
return 0;
}