diff --git a/applications/PoP/roomtest/TASKS_OPEN.md b/applications/PoP/roomtest/TASKS_OPEN.md index 463c219..5b1966b 100644 --- a/applications/PoP/roomtest/TASKS_OPEN.md +++ b/applications/PoP/roomtest/TASKS_OPEN.md @@ -454,13 +454,36 @@ awk '/^;[a-z_0-9]+\.[ch]:[0-9]+:/{s=$0} /^\tcall\t__(div|mod|mul)/{printf "%-22s | `pop_guard.c` `guard_col_from_x` | `/14` + `%14` **безусловно**, мимо `POP_TILE_DIV` | единственное 16-битное деление, у которого таблица вообще не была подключена | | `pop_trob.c` `animate_chomper` | `tp / 10` = `__divuchar` на чомпера каждый кадр | таблицы `TP_ROW`/`TP_COL` уже лежали в ЭТОМ ЖЕ файле, но ниже по тексту — чомперы их не видели | -Приём для `×8/7`: тождество **`8x/7 == x + x/7`** (7x делится нацело, поэтому -усечение обеих частей совпадает — и для отрицательных с трункейтом к нулю -тоже). Дальше `x/7` — таблица `DIV7[256]` в банке 4 (резидент не тронут). -Диапазон покрыт не «частый», а весь обычный: `x_bump[5..14] = 58..184`, то -есть `obj_x = 2*fwd − 116` у персонажа в отрисованной комнате лежит в 0..252; -хвост нужен только на шве (`render_dx = ∓140`). Тождество проверено тестом -`geom_mul8div7_identity`, сама таблица — сверкой с `x//7` питоном. +Приём для `×8/7`: **таблица готовых значений** `SCRX[1152]` (int16_t) в +банке 4, индекс `x + 448`. 2 304 байта, резидент не тронут. + +Два решения по дороге, оба проверены, а не угаданы: + +1. **Диапазон — весь, включая отрицательные.** Первая версия крыла 0..255 по + тождеству `8x/7 == x + x/7` с байтовой таблицей `x/7`. Ошибка: `obj_x = + 2*fwd − 116` уходит в минус, как только `fwd < 58` (левее `x_bump[5]`) — + то есть у ЛЕВОЙ КРОМКИ комнаты, а это не экзотика, и там мы продолжали + делить. Границы взяты из фактических данных: `kid_data.bin` даёт `dx` + кадров Кида −5..+10, стража −2..+10; при `Char.x` типа uint8_t и + `render_dx ∈ {−140, 0, +140}` полный диапазон `obj_x` = **−416..695**. + Таблица кроет −448..703, деление стало недостижимым (оставлено + страховкой на третьего персонажа / другой `render_dx`). +2. **Хранить готовое значение, а не `x/7`.** Байтовая таблица вдвое меньше, + но со знаковыми значениями к ней добавляются расширение знака и 16-битное + сложение — ровно те же такты, что лишний `add hl,hl` при 2-байтном + индексе. Раз выигрыша нет, берём вариант без арифметики. + +Кодоген проверен глазами (`bank4_pop_cdraw.asm`): `ld hl,#0x01C0; add hl,de`, +16-битное беззнаковое сравнение, `add hl,hl; add hl,de; ld e,(hl); inc hl; +ld d,(hl)` — индекс полный, старший байт не теряется (грабли +`sdcc_z80_const_ptr_index_bug` обойдены отдельной `uint16_t`-переменной). +≈130 тактов номинала против ~1 000 у `__divsint`. + +Проверка таблицы: значения сверены питоном обратно из `.c` со всеми 1 152 +записями, а ПРАВИЛО генерации («усечение к нулю») — тестом +`geom_mul8div7_trunc_to_zero` на целевом компиляторе: округляй SDCC к минус +бесконечности, вся отрицательная половина уехала бы на пиксель, и поймалось +бы это только глазами на левой кромке. Оставлено сознательно (НЕ трогать, это не забытые места): diff --git a/applications/PoP/roomtest/pop_cdraw.c b/applications/PoP/roomtest/pop_cdraw.c index 55b7773..f9ad0bc 100644 --- a/applications/PoP/roomtest/pop_cdraw.c +++ b/applications/PoP/roomtest/pop_cdraw.c @@ -117,37 +117,136 @@ void pop_guard_free(void) __banked * `__divsint` — 2 400 тактов на вызов, и зовётся оно на КАЖДОГО персонажа * КАЖДЫЙ кадр (последнее деление, остававшееся в горячем пути). * - * Тождество: 8x/7 == x + x/7. Оно ТОЧНОЕ в целых, потому что 7x делится - * нацело: floor((7x+x)/7) = x + floor(x/7); для отрицательных с усечением - * к нулю — тоже (проверено перебором в tests-host). + * Таблица хранит ГОТОВЫЙ результат (int16_t), а не x/7 с последующим + * сложением по тождеству 8x/7 == x + x/7. Байтовая таблица вдвое меньше, + * но она обязана быть знаковой (x/7 при x<0 отрицателен), а значит к её + * значению добавляется расширение знака плюс 16-битное сложение — по + * тактам это ровно то же самое, что лишний `add hl,hl` при 2-байтном + * индексе. Раз выигрыша нет, берём вариант без арифметики вообще. * - * Таблица кроет 0..255 — это ВЕСЬ обычный случай, а не «частый»: - * x_bump[5..14] = 58..184, то есть у персонажа в отрисованной комнате - * obj_x = 2*fwd − 116 лежит в 0..252. Медленный хвост остаётся для шва, - * где render_dx уводит X на ∓140. */ -static const uint8_t DIV7[256] = { - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, - 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, - 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, - 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 9, - 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,10,11,11,11, - 11,11,11,11,12,12,12,12,12,12,12,13,13,13,13,13, - 13,13,14,14,14,14,14,14,14,15,15,15,15,15,15,15, - 16,16,16,16,16,16,16,17,17,17,17,17,17,17,18,18, - 18,18,18,18,18,19,19,19,19,19,19,19,20,20,20,20, - 20,20,20,21,21,21,21,21,21,21,22,22,22,22,22,22, - 22,23,23,23,23,23,23,23,24,24,24,24,24,24,24,25, - 25,25,25,25,25,25,26,26,26,26,26,26,26,27,27,27, - 27,27,27,27,28,28,28,28,28,28,28,29,29,29,29,29, - 29,29,30,30,30,30,30,30,30,31,31,31,31,31,31,31, - 32,32,32,32,32,32,32,33,33,33,33,33,33,33,34,34, - 34,34,34,34,34,35,35,35,35,35,35,35,36,36,36,36, + * ДИАПАЗОН — весь, а не «обычный случай». Отрицательные X реальны: + * obj_x = 2*fwd − 116 уходит в минус, как только fwd < 58 (левее + * x_bump[5]), то есть у левой кромки комнаты — это не экзотика. По + * фактическим данным кадров (kid_data.bin: dx Кида −5..+10, стража + * −2..+10) при x типа uint8_t и render_dx ∈ {−140, 0, +140} полный + * диапазон obj_x = −416..695; таблица кроет −448..703 с запасом. + * Деление ниже поэтому недостижимо и оставлено страховкой на случай + * третьего персонажа или другого render_dx. + * + * Цена: 2 304 байта в банке 4 (там было 9 048 свободно). + * + * Экранная X по логической: SCRX[x + SCRX_BIAS] == x*8/7 (усечение к + * нулю, как в C и как у 8086 idiv в оригинале). */ +#define SCRX_BIAS 448 +#define SCRX_N 1152 +static const int16_t SCRX[SCRX_N] = { + -512, -510, -509, -508, -507, -506, -505, -504, -502, -501, -500, -499, + -498, -497, -496, -494, -493, -492, -491, -490, -489, -488, -486, -485, + -484, -483, -482, -481, -480, -478, -477, -476, -475, -474, -473, -472, + -470, -469, -468, -467, -466, -465, -464, -462, -461, -460, -459, -458, + -457, -456, -454, -453, -452, -451, -450, -449, -448, -446, -445, -444, + -443, -442, -441, -440, -438, -437, -436, -435, -434, -433, -432, -430, + -429, -428, -427, -426, -425, -424, -422, -421, -420, -419, -418, -417, + -416, -414, -413, -412, -411, -410, -409, -408, -406, -405, -404, -403, + -402, -401, -400, -398, -397, -396, -395, -394, -393, -392, -390, -389, + -388, -387, -386, -385, -384, -382, -381, -380, -379, -378, -377, -376, + -374, -373, -372, -371, -370, -369, -368, -366, -365, -364, -363, -362, + -361, -360, -358, -357, -356, -355, -354, -353, -352, -350, -349, -348, + -347, -346, -345, -344, -342, -341, -340, -339, -338, -337, -336, -334, + -333, -332, -331, -330, -329, -328, -326, -325, -324, -323, -322, -321, + -320, -318, -317, -316, -315, -314, -313, -312, -310, -309, -308, -307, + -306, -305, -304, -302, -301, -300, -299, -298, -297, -296, -294, -293, + -292, -291, -290, -289, -288, -286, -285, -284, -283, -282, -281, -280, + -278, -277, -276, -275, -274, -273, -272, -270, -269, -268, -267, -266, + -265, -264, -262, -261, -260, -259, -258, -257, -256, -254, -253, -252, + -251, -250, -249, -248, -246, -245, -244, -243, -242, -241, -240, -238, + -237, -236, -235, -234, -233, -232, -230, -229, -228, -227, -226, -225, + -224, -222, -221, -220, -219, -218, -217, -216, -214, -213, -212, -211, + -210, -209, -208, -206, -205, -204, -203, -202, -201, -200, -198, -197, + -196, -195, -194, -193, -192, -190, -189, -188, -187, -186, -185, -184, + -182, -181, -180, -179, -178, -177, -176, -174, -173, -172, -171, -170, + -169, -168, -166, -165, -164, -163, -162, -161, -160, -158, -157, -156, + -155, -154, -153, -152, -150, -149, -148, -147, -146, -145, -144, -142, + -141, -140, -139, -138, -137, -136, -134, -133, -132, -131, -130, -129, + -128, -126, -125, -124, -123, -122, -121, -120, -118, -117, -116, -115, + -114, -113, -112, -110, -109, -108, -107, -106, -105, -104, -102, -101, + -100, -99, -98, -97, -96, -94, -93, -92, -91, -90, -89, -88, + -86, -85, -84, -83, -82, -81, -80, -78, -77, -76, -75, -74, + -73, -72, -70, -69, -68, -67, -66, -65, -64, -62, -61, -60, + -59, -58, -57, -56, -54, -53, -52, -51, -50, -49, -48, -46, + -45, -44, -43, -42, -41, -40, -38, -37, -36, -35, -34, -33, + -32, -30, -29, -28, -27, -26, -25, -24, -22, -21, -20, -19, + -18, -17, -16, -14, -13, -12, -11, -10, -9, -8, -6, -5, + -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 8, + 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, + 22, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, + 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 48, 49, + 50, 51, 52, 53, 54, 56, 57, 58, 59, 60, 61, 62, + 64, 65, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, + 77, 78, 80, 81, 82, 83, 84, 85, 86, 88, 89, 90, + 91, 92, 93, 94, 96, 97, 98, 99, 100, 101, 102, 104, + 105, 106, 107, 108, 109, 110, 112, 113, 114, 115, 116, 117, + 118, 120, 121, 122, 123, 124, 125, 126, 128, 129, 130, 131, + 132, 133, 134, 136, 137, 138, 139, 140, 141, 142, 144, 145, + 146, 147, 148, 149, 150, 152, 153, 154, 155, 156, 157, 158, + 160, 161, 162, 163, 164, 165, 166, 168, 169, 170, 171, 172, + 173, 174, 176, 177, 178, 179, 180, 181, 182, 184, 185, 186, + 187, 188, 189, 190, 192, 193, 194, 195, 196, 197, 198, 200, + 201, 202, 203, 204, 205, 206, 208, 209, 210, 211, 212, 213, + 214, 216, 217, 218, 219, 220, 221, 222, 224, 225, 226, 227, + 228, 229, 230, 232, 233, 234, 235, 236, 237, 238, 240, 241, + 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 254, + 256, 257, 258, 259, 260, 261, 262, 264, 265, 266, 267, 268, + 269, 270, 272, 273, 274, 275, 276, 277, 278, 280, 281, 282, + 283, 284, 285, 286, 288, 289, 290, 291, 292, 293, 294, 296, + 297, 298, 299, 300, 301, 302, 304, 305, 306, 307, 308, 309, + 310, 312, 313, 314, 315, 316, 317, 318, 320, 321, 322, 323, + 324, 325, 326, 328, 329, 330, 331, 332, 333, 334, 336, 337, + 338, 339, 340, 341, 342, 344, 345, 346, 347, 348, 349, 350, + 352, 353, 354, 355, 356, 357, 358, 360, 361, 362, 363, 364, + 365, 366, 368, 369, 370, 371, 372, 373, 374, 376, 377, 378, + 379, 380, 381, 382, 384, 385, 386, 387, 388, 389, 390, 392, + 393, 394, 395, 396, 397, 398, 400, 401, 402, 403, 404, 405, + 406, 408, 409, 410, 411, 412, 413, 414, 416, 417, 418, 419, + 420, 421, 422, 424, 425, 426, 427, 428, 429, 430, 432, 433, + 434, 435, 436, 437, 438, 440, 441, 442, 443, 444, 445, 446, + 448, 449, 450, 451, 452, 453, 454, 456, 457, 458, 459, 460, + 461, 462, 464, 465, 466, 467, 468, 469, 470, 472, 473, 474, + 475, 476, 477, 478, 480, 481, 482, 483, 484, 485, 486, 488, + 489, 490, 491, 492, 493, 494, 496, 497, 498, 499, 500, 501, + 502, 504, 505, 506, 507, 508, 509, 510, 512, 513, 514, 515, + 516, 517, 518, 520, 521, 522, 523, 524, 525, 526, 528, 529, + 530, 531, 532, 533, 534, 536, 537, 538, 539, 540, 541, 542, + 544, 545, 546, 547, 548, 549, 550, 552, 553, 554, 555, 556, + 557, 558, 560, 561, 562, 563, 564, 565, 566, 568, 569, 570, + 571, 572, 573, 574, 576, 577, 578, 579, 580, 581, 582, 584, + 585, 586, 587, 588, 589, 590, 592, 593, 594, 595, 596, 597, + 598, 600, 601, 602, 603, 604, 605, 606, 608, 609, 610, 611, + 612, 613, 614, 616, 617, 618, 619, 620, 621, 622, 624, 625, + 626, 627, 628, 629, 630, 632, 633, 634, 635, 636, 637, 638, + 640, 641, 642, 643, 644, 645, 646, 648, 649, 650, 651, 652, + 653, 654, 656, 657, 658, 659, 660, 661, 662, 664, 665, 666, + 667, 668, 669, 670, 672, 673, 674, 675, 676, 677, 678, 680, + 681, 682, 683, 684, 685, 686, 688, 689, 690, 691, 692, 693, + 694, 696, 697, 698, 699, 700, 701, 702, 704, 705, 706, 707, + 708, 709, 710, 712, 713, 714, 715, 716, 717, 718, 720, 721, + 722, 723, 724, 725, 726, 728, 729, 730, 731, 732, 733, 734, + 736, 737, 738, 739, 740, 741, 742, 744, 745, 746, 747, 748, + 749, 750, 752, 753, 754, 755, 756, 757, 758, 760, 761, 762, + 763, 764, 765, 766, 768, 769, 770, 771, 772, 773, 774, 776, + 777, 778, 779, 780, 781, 782, 784, 785, 786, 787, 788, 789, + 790, 792, 793, 794, 795, 796, 797, 798, 800, 801, 802, 803, }; static int scr_x(int x) { - if ((unsigned int)x < 256u) return x + DIV7[x]; - return x * 8 / 7; + /* Индекс считаем в uint16_t ОТДЕЛЬНОЙ переменной, а не выражением внутри + * скобок: на `(const T*)КОНСТ + var*K` SDCC z80 умеет терять старший байт + * индекса (memory sdcc_z80_const_ptr_index_bug), а тут индекс как раз + * больше 255. */ + uint16_t i = (uint16_t)(x + SCRX_BIAS); + if (i < SCRX_N) return SCRX[i]; + return x * 8 / 7; /* по данным кадров недостижимо */ } /* Добавить накладной спрайт (клинок / брызги) в overlay-прямоугольник diff --git a/applications/PoP/roomtest/tests-host/t_geom.c b/applications/PoP/roomtest/tests-host/t_geom.c index 5276c43..6c5e40d 100644 --- a/applications/PoP/roomtest/tests-host/t_geom.c +++ b/applications/PoP/roomtest/tests-host/t_geom.c @@ -72,16 +72,21 @@ TC_TEST(geom_y_to_row_matches_formula) } } -/* calc_screen_x_coord (seg008:2602) считается в pop_cdraw.c таблицей DIV7 по - * тождеству 8x/7 == x + x/7 (7x делится нацело, поэтому усечение обеих частей - * совпадает). Само тождество и проверяем — оно и есть неочевидная часть; - * таблица DIV7 сгенерирована как x/7 и живёт в банке 4, сюда не линкуется. */ -TC_TEST(geom_mul8div7_identity) +/* 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 = 2*fwd − 116; со сдвигом шва (∓140) уходит в −406..684. */ - for (x = -420; x <= 700; x++) - TC_EQ((int16_t)(x * 8 / 7), (int16_t)(x + x / 7)); + /* Полный диапазон 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 с эталонной формулой ---------------------- */ @@ -166,7 +171,7 @@ int main(void) TC_RUN(geom_yland_rows); TC_RUN(geom_y_to_row); TC_RUN(geom_y_to_row_matches_formula); - TC_RUN(geom_mul8div7_identity); + TC_RUN(geom_mul8div7_trunc_to_zero); #if POP_PRANDOM_EXACT TC_RUN(geom_lcg_matches_reference); TC_RUN(geom_lcg_seed_zero);