fc0ede91e1
Гипотеза «двухбайтная индексация съест выигрыш от сложения» не
подтвердилась. Собраны ОБА варианта, такты посчитаны по сгенерированному
asm (хвост после проверки границ):
int16_t готовое: add hl,hl / add hl,de / ld e,(hl) / inc hl / ld d,(hl)
= 132 такта, 2 304 байта
int8_t x/7: add hl,bc / ld a,(hl) / ld l,a / rlca / sbc a,a /
ld h,a / add hl,de = 131 такт, 1 152 байта
Расширение знака плюс 16-битное сложение стоят ровно столько же, сколько
лишний add hl,hl при двухбайтном индексе, а обращений к памяти на одно
меньше — под wait-state'ами Sprinter (2,4x номинала именно на обращениях
к ОЗУ) байтовый вариант ещё чуть выгоднее номинала.
Банк 4: 9 394 -> 8 243 из 16 384 (свободно 8 141 вместо 6 990) — запас под
рост pop_cdraw, о котором и был вопрос.
Тест переименован в geom_mul8div7_table_rules и проверяет ОБА правила
генерации таблицы: тождество 8x/7 == x + x/7 и усечение к нулю.
tests-host: [geom] 1992 -> 3144, все 5 наборов прошли.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
185 lines
7.8 KiB
C
185 lines
7.8 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 таблицей 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;
|
||
}
|