From 8cac51d592c5dd2d4f65f09ff77e52d617f3bf75 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Mon, 10 Aug 2026 20:10:52 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BD=D1=8B=20=D1=82?= =?UTF-8?q?=D1=80=D0=B8=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=D0=B4=D0=BD=D0=B8?= =?UTF-8?q?=D1=85=20/63=20%4=20=D0=B2=20pop=5Froom.c=20=E2=80=94=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B7=D0=BE=D0=B2=20pop=5Fy=5Fto=5Frow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- applications/PoP/roomtest/TASKS_OPEN.md | 10 ++++++++++ applications/PoP/roomtest/pop_room.c | 6 +++--- applications/PoP/roomtest/tests-host/t_geom.c | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/applications/PoP/roomtest/TASKS_OPEN.md b/applications/PoP/roomtest/TASKS_OPEN.md index 46ef7d6..e43ab18 100644 --- a/applications/PoP/roomtest/TASKS_OPEN.md +++ b/applications/PoP/roomtest/TASKS_OPEN.md @@ -425,6 +425,16 @@ wait-state'ы, и замеренная стоимость выходит **≈ 2 `__divuchar` с печатью адреса возврата (`printf "%04X", w@(sp)`) — сразу видно, кто и сколько раз делит за кадр. +**Хвост подобран 2026-08-10.** После переписи `pop_y_to_row` в pop_room.c +осталось ТРИ места, считавших `(y+60)/63 % 4 - 1` вручную (927 в +`mob_tick_one`, 976/977 в `mob_render`) — сгенерированный asm подтвердил +пару `__divsint`+`__modsint` в каждом. Это ~16 200 тактов (3,8 % кадра), но +только пока кусок плиты в полёте — то есть ровно в самом тяжёлом кадре. +Заменены вызовом `pop_y_to_row`; в банке 7 теперь НОЛЬ `__divsint`. +Эквивалентность закреплена тестом `geom_y_to_row_matches_formula` +(перебор −400..400 против исходной формулы) — вызовы разбросаны по трём +банкам, и соблазн написать деление «по месту» возвращается. + **Профиль работы за логический кадр СЕЙЧАС (409 956 тактов + ~15 600 ввод):** | блок | тактов | % растрового кадра | diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index b54e622..215225f 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -924,7 +924,7 @@ static void mob_tick_one(mob_t *m, uint8_t pg) * поверх Kid, если его тайл идёт в обходе ПОЗЖЕ тайла Kid, а при * совпадении тайлов — если он ниже по y. */ { - int8_t mrow = (int8_t)(((m->y + 60) / 63) % 4 - 1); /* y_to_row_mod4 */ + int8_t mrow = pop_y_to_row((int16_t)m->y); /* y_to_row_mod4 */ uint8_t over; if (mrow != pop_bg_obj_row) over = (mrow < pop_bg_obj_row); /* ряды идут 2,1,0 */ else if (m->col != pop_bg_obj_col) over = (m->col > pop_bg_obj_col); @@ -973,8 +973,8 @@ static void mob_render(mob_t *m, uint8_t pg) * через пол + перекрыта передняя грань пола. * Плюс ВТОРОЙ тайл — под ВЕРХОМ куска (ypos-18), пока кусок висит на * границе рядов. Порт draw_mob (seg007:13E5). */ - int8_t r = (int8_t)(((m->y + 60) / 63) % 4 - 1); - int8_t rt = (int8_t)(((m->y - 18 + 60) / 63) % 4 - 1); + int8_t r = pop_y_to_row((int16_t)m->y); + int8_t rt = pop_y_to_row((int16_t)(m->y - 18)); if (r < 0) pop_t_clip_top = POP_YOFF; /* полоса у потолка — только срез */ draw_tile(r, m->col + 1); pop_t_clip_top = 0; diff --git a/applications/PoP/roomtest/tests-host/t_geom.c b/applications/PoP/roomtest/tests-host/t_geom.c index 00b247b..fc0f6df 100644 --- a/applications/PoP/roomtest/tests-host/t_geom.c +++ b/applications/PoP/roomtest/tests-host/t_geom.c @@ -56,6 +56,22 @@ TC_TEST(geom_y_to_row) 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 @@ -137,6 +153,7 @@ 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);