/* * 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 таблицей SCRX7 * (банк 4, сюда не линкуется): хранится x/7 байтом, результат = x + SCRX7[..]. * Таблица сгенерирована питоном, поэтому проверяем два правила, на которых * держится генерация — ИМЕННО на целевом компиляторе: * 1) тождество 8x/7 == x + x/7 (7x делится нацело); * 2) усечение К НУЛЮ, а не к минус бесконечности. * Округляй SDCC иначе — вся отрицательная половина уехала бы на пиксель, и * поймалось бы это только глазами на левой кромке комнаты. */ TC_TEST(geom_mul8div7_table_rules) { 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); /* усечение к нулю */ TC_EQ((int16_t)(x * 8 / 7), (int16_t)(x + x / 7)); /* тождество */ } } /* ---- 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_table_rules); #if POP_PRANDOM_EXACT TC_RUN(geom_lcg_matches_reference); TC_RUN(geom_lcg_seed_zero); #endif TC_RUN(geom_prandom_range); return 0; }