scr_x: таблица готовых значений на весь диапазон, включая отрицательные
Первая версия крыла только 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>
This commit is contained in:
@@ -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 к минус
|
||||
бесконечности, вся отрицательная половина уехала бы на пиксель, и поймалось
|
||||
бы это только глазами на левой кромке.
|
||||
|
||||
Оставлено сознательно (НЕ трогать, это не забытые места):
|
||||
|
||||
|
||||
@@ -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-прямоугольник
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user