8175121d25
Первая версия крыла только 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>
182 lines
7.6 KiB
C
182 lines
7.6 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));
|
||
}
|
||
}
|
||
|
||
/* 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;
|
||
}
|