From b0524b9ad06c2e688f5a9e17ee4fd874de65ac7d Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Sun, 9 Aug 2026 15:49:46 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F:=20=D0=BB=D0=BE=D0=B3=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B8=D0=B9=20=D0=BA=D0=B0=D0=B4=D1=80=20?= =?UTF-8?q?=D1=83=D0=BB=D0=BE=D0=B6=D0=B8=D0=BB=D1=81=D1=8F=20=D0=B2=20?= =?UTF-8?q?=D0=B1=D1=8E=D0=B4=D0=B6=D0=B5=D1=82,=20=D1=86=D0=B8=D0=BA?= =?UTF-8?q?=D0=BB=204=20=D1=80=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BA=D0=B0=D0=B4=D1=80=D0=B0=20->=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Главный цикл спейсится тремя gfx_wait_vsync, поэтому работа сверх 430 000 тактов стоит сразу целый лишний растровый кадр. Было 470 964, стало ~425 600 — игра быстрее на треть (16,7 логических кадров/с против 12,5). - pop_y_to_row: цепочка сравнений вместо (y+60)/63%4-1. ВАЖНО: медленный хвост вынесен в ОТДЕЛЬНУЮ функцию — SDCC видит одинаковое выражение в двух ветках и поднимает деление в вершину, быстрые возвраты не спасают. - col_from_x (pop_bg) и get_tile_div_mod (pop_map) — общие резидентные таблицы POP_TILE_DIV/POP_TILE_MOD в pop_tile.c (const банка из чужого банка не читается). - pop_fore_over_char: расширение окна считается арифметикой, а не перебором 10 колонок и 3 рядов (условие монотонно -> границы). Формулы сверены с прежним перебором перебором значений, расхождений нет. - pop_cd_touch: цикл по страницам развёрнут, x+w/y+h считаются один раз. Зовётся с каждого блита фона, стоил 6 846 тактов. - process_trobs: tp/10 и tp%10 у факелов — таблицей. - Пустой слот соперника (стража на сцене нет, на странице ничего не нарисовано) считается «тихим»: ни heal, ни вход в pop_char_draw, ни fore-проход. Приём для поиска делений: брейкпоинт на __divsint/__divuint/__divuchar с печатью адреса возврата (printf "%04X", w@(sp)). Профиль остатка — TASKS_OPEN.md#draw-cost. tests-host: 5/5. Co-Authored-By: Claude Opus 5 --- applications/PoP/roomtest/TASKS_OPEN.md | 77 +++++++++++------- applications/PoP/roomtest/pop_bg.c | 55 +++++++++---- applications/PoP/roomtest/pop_cdraw.c | 12 ++- applications/PoP/roomtest/pop_geom.c | 28 ++++++- applications/PoP/roomtest/pop_map.c | 58 ++------------ applications/PoP/roomtest/pop_tile.c | 83 +++++++++++++++++--- applications/PoP/roomtest/pop_tile.h | 6 ++ applications/PoP/roomtest/pop_trob.c | 13 ++- applications/PoP/roomtest/tests-host/stubs.c | 40 ++++++++++ 9 files changed, 261 insertions(+), 111 deletions(-) diff --git a/applications/PoP/roomtest/TASKS_OPEN.md b/applications/PoP/roomtest/TASKS_OPEN.md index c7ba4ba..033ebb3 100644 --- a/applications/PoP/roomtest/TASKS_OPEN.md +++ b/applications/PoP/roomtest/TASKS_OPEN.md @@ -55,7 +55,7 @@ | 2 | [L3-CHOMP](#l3-chomp) | **СЛЕДУЮЩАЯ**: чомперы (5 шт) | прохождение ур. 3 | | — | [L3-SKEL](#l3-skel) | скелет ур. 3 — **сделан 2026-08-07**, ждёт финальной приёмки | — | | 3 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели | -| — | [DRAW-COST](#draw-cost) | шаг 1 (пропуск персонажа) и логика сделаны: синяя полоса 60 % -> 30 %. ОСТАЛОСЬ: отрисовка одного Кида = 47 % кадра, `process_trobs` = 21 % | плавность на ВСЕХ уровнях | +| — | [DRAW-COST](#draw-cost) | **кадр уложился в бюджет 2026-08-09**: 470 964 -> 425 600 тактов, период цикла 4 растровых кадра -> **3** (игра быстрее на треть). Дальнейшее — запас, не срочность | плавность на ВСЕХ уровнях | | — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент | | — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности | @@ -396,40 +396,61 @@ wait-state'ы, и замеренная стоимость выходит **≈ 2 два полноценных 16-битных деления на каждый вопрос «в какой колонке точка». Оригинал делит таблицей (seg006:702) — мы просто не портировали это место. -**Профиль работы за один логический кадр ПОСЛЕ правок (466 560 тактов):** +**ГЛАВНОЕ: логический кадр уложился в бюджет.** Главный цикл спейсится +тремя `gfx_wait_vsync`, поэтому работа сверх 430 000 тактов стоит СРАЗУ +целый лишний растровый кадр. Было 470 964 (период цикла 4 кадра), стало +**409 956 + ~15 600 на ввод = 425 600** — период цикла **3 растровых кадра**. +Игра стала быстрее на треть (16,7 логических кадров/с против 12,5). + +Что дало последние тысячи (по убыванию): + +| правка | экономия | +|---|---| +| `pop_y_to_row` — цепочка сравнений вместо `/63 % 4` | ~12 000 | +| расширение окна fore-прохода арифметикой вместо перебора 10 колонок и 3 рядов | ~8 500 | +| `col_from_x` — таблицей (те же `POP_TILE_DIV`, вынесены в резидент) | ~11 000 | +| `pop_cd_touch` — развёрнутый цикл по страницам, `x+w`/`y+h` один раз | ~8 600 (зовётся с каждого блита фона) | +| `tp / 10`, `tp % 10` у факелов — таблицей | ~4 000 | +| пустой слот соперника считается «тихим» | ~8 200 | + +**Ловушка SDCC, на которой я потерял один прогон:** в `pop_y_to_row` одно и +то же выражение `t / 63 % 4 - 1` стояло в двух ветках, и компилятор поднял +деление В ВЕРШИНУ функции — быстрые возвраты не спасали, `__divsint` звался +всё равно. Лечится выносом медленного хвоста в ОТДЕЛЬНУЮ функцию. Тот же +эффект уже был описан в `pop_loose_tick`; теперь ясно, что это правило, а не +частный случай: **любое деление, встречающееся дважды, SDCC поднимает выше +всех проверок.** + +Приём, которым это ловится: брейкпоинт на `__divsint`/`__divuint`/ +`__divuchar` с печатью адреса возврата (`printf "%04X", w@(sp)`) — сразу +видно, кто и сколько раз делит за кадр. + +**Профиль работы за логический кадр СЕЙЧАС (409 956 тактов + ~15 600 ввод):** | блок | тактов | % растрового кадра | |---|---|---| -| see_kid + ctrl_tick + heal | 46 920 | 11 | -| kid_tick (play_seq) | 6 468 | 1,5 | -| физика Кида (`pop_phys_tick`) | 63 102 | 15 | -| страж + боёвка | 10 350 | 2,4 | +| see_kid + ctrl_tick + skip + heal + kid_tick | 52 536 | 12 | +| физика Кида + страж + боёвка | 73 452 | 17 | | `pop_loose_tick` | 27 438 | 6,4 | -| **`pop_process_trobs`** | **92 346** | **21,5** | -| `pop_redraw_needed` + шов | 6 474 | 1,5 | -| skip + отрисовка соперника (пусто) | 11 178 | 2,6 | -| **`pop_char_draw` + `pop_char_fore` Кида** | **201 336** | **47** | -| борта | 732 | 0,2 | +| **`pop_process_trobs`** (два факела) | **75 720** | **17,6** | +| `pop_redraw_needed` + шов + skip_mask | 10 932 | 2,5 | +| **`pop_char_draw` Кида** | **56 250** | **13** | +| **`pop_char_fore` Кида + борта** | **113 628** | **26** | -Синяя полоса (ввод+heal+логика) была ~60 % → стала ~30 %. Внутри физики -`check_collisions` — по-прежнему больше половины (38 334 из 63 102): три ряда -× окно 4–5 колонок, как в оригинале. Дальше там режется плохо — осталась -арифметика граней, а её удешевление требует перевода сверок в 8 бит и -доказательства, что `coll_xl`/`coll_xr` не выходят из 0..255. +Синяя полоса (ввод + логика) была ~60 % → стала ~29 %. -**Что осталось — по убыванию (это и есть остаток шага 2):** +**Что осталось (запас на будущее, срочности больше нет):** -1. **Отрисовка одного Кида 201 336 тактов = 47 % кадра.** Ровно тот «циан», - который пользователь назвал следующей целью. План — ниже. -2. **`pop_process_trobs` 92 346 на ДВА факела** (46 000 на факел). У - оригинала `process_trobs` только двигает состояние, рисование — - в `redraw_needed`; у нас `redraw_needed` стоит 6 474, значит блиты факелов - сидят внутри process_trobs. Разобрать, где именно. -3. **`pop_loose_tick` 27 438** при полном отсутствии падающих плит в комнате — - похоже, безусловный проход по всем 30 тайлам. -4. Работа за цикл 466 560 против 430 000 бюджета: **не хватает ~37 000**, - чтобы уложиться в три растровых кадра вместо четырёх. Любая из правок - выше даёт игре сразу +25 % скорости — граница проходит рядом. +1. **`pop_char_fore` 113 628.** Внутри: `char_footprint` + расширение окна + ~21 000, дальше шесть `fore_tile`, из которых два реально рисуют (по + ~32 000). Дальше резать — кэш «в этом тайле переднего слоя нет вовсе». +2. **`pop_process_trobs` 75 720 на два факела** (~31 000 на факел). Внутри + одного факела: `gfx_blit_noclip` 8 700, чтение w/h и клип 6 400, + `pop_cd_touch` (теперь дешевле), маппинг окна 0 и возвраты. Пламя + перерисовывается каждый кадр обязательно (`TORCH_ANIM_DIV = 1`, кадр + меняется), так что пропуск тут не поможет — только удешевление блита. +3. `pop_loose_tick` 27 438 при полном отсутствии падающих плит. +4. Одно `__divsint` осталось в `pop_char_draw` (`obj_x * 8 / 7`) — ~2 400. ### Шаг 2 (СЛЕДУЮЩИЙ, назначен пользователем): ДВИЖУЩИЙСЯ Кид diff --git a/applications/PoP/roomtest/pop_bg.c b/applications/PoP/roomtest/pop_bg.c index 27458b6..234c1ca 100644 --- a/applications/PoP/roomtest/pop_bg.c +++ b/applications/PoP/roomtest/pop_bg.c @@ -304,10 +304,16 @@ void pop_gate_back_b(uint8_t modl, int xh, int dby, int dmy) __banked * X кадра Kid (до ×8/7), obj_y — низ спрайта, w/h — размер, dir — направление. */ static int8_t col_from_x(int xpos) /* get_tile_div_mod: колонка (58/14) */ { - int x = xpos - 58; - int xh = x / 14; - if (x % 14 < 0) xh--; /* округление вниз для отрицательных */ - return (int8_t)xh; + /* Таблицей (резидентная POP_TILE_DIV, см. pop_tile.h) — как в оригинале. + * Было `/14` и `%14`: два полноценных 16-битных деления SDCC на вызов, + * а зовётся это дважды на каждый футпринт персонажа. */ + if ((unsigned int)xpos < 256u) return POP_TILE_DIV[xpos]; + { + int x = xpos - 58; + int xh = x / 14; + if (x % 14 < 0) xh--; /* округление вниз для отрицательных */ + return (int8_t)xh; + } } #define y_to_row(y) pop_y_to_row((int16_t)(y)) /* общий (pop_geom) */ @@ -626,20 +632,37 @@ void pop_fore_over_char(const pop_char_t *ch, int obj_x, int obj_y, * ради скорости (см. pop_fore_layer_cost). Поэтому перебор расширяется * САМИМ ОКНОМ fore-клипа — оно и есть объединение «персонаж + клинок + * брызги», считать его второй раз не надо. */ + /* Границы считаются АРИФМЕТИКОЙ, а не перебором всех колонок и рядов. + * Условие монотонно по номеру (x0 растёт), значит принятые номера идут + * подряд — хватает первого и последнего. Перебор стоил 38 970 тактов на + * кадр (замер MAME): тринадцать итераций, в каждой пара 16-битных + * ЗНАКОВЫХ сверок, а SDCC пишет их через `jp PO / xor 0x80 / jp P`. */ if (pop_t_fclip_x1 > pop_t_fclip_x0 && pop_t_fclip_y1 > pop_t_fclip_y0) { - int8_t c2, r2; - for (c2 = 0; c2 <= 9; c2++) { - int x0 = POP_COL_XH[c2] * 8; - if (x0 + 40 > pop_t_fclip_x0 && x0 < pop_t_fclip_x1) { - if (c2 < fp_cL) fp_cL = c2; - if (c2 > fp_cR) fp_cR = c2; - } + int8_t lo, hi; + /* Колонки: x0 = POP_COL_XH[c]*8 = 32*c (таблица линейна, шаг 4). + * 32c + 40 > x0 -> c >= floor((x0−40)/32) + 1 + * 32c < x1 -> c <= ceil(x1/32) − 1 + * Сдвиг вправо у SDCC z80 для int — арифметический, то есть floor и + * для отрицательных; на это здесь и рассчитано. */ + lo = (int8_t)(((pop_t_fclip_x0 - 40) >> 5) + 1); + hi = (int8_t)((((pop_t_fclip_x1 + 31) >> 5)) - 1); + if (lo < 0) lo = 0; + if (hi > 9) hi = 9; + if (lo <= hi) { + if (lo < fp_cL) fp_cL = lo; + if (hi > fp_cR) fp_cR = hi; } - for (r2 = 0; r2 <= 2; r2++) { - int y0 = POP_YOFF + 63 * r2; - if (y0 + 70 > pop_t_fclip_y0 && y0 - 8 < pop_t_fclip_y1) { - if (r2 < fp_rT) fp_rT = r2; - if (r2 > fp_rB) fp_rB = r2; + /* Ряды: y0 = POP_YOFF + 63*r, r только 0..2 — деления на 63 не надо, + * достаточно сравнить с границами полос. + * 63r > y0 − POP_YOFF − 70 и 63r < y1 − POP_YOFF + 8 */ + { + int a = pop_t_fclip_y0 - POP_YOFF - 70; + int b = pop_t_fclip_y1 - POP_YOFF + 8; + lo = a < 0 ? 0 : (a < 63 ? 1 : 2); + hi = b > 126 ? 2 : (b > 63 ? 1 : 0); + if (a < 126 && b > 0 && lo <= hi) { + if (lo < fp_rT) fp_rT = lo; + if (hi > fp_rB) fp_rB = hi; } } } diff --git a/applications/PoP/roomtest/pop_cdraw.c b/applications/PoP/roomtest/pop_cdraw.c index e9632f3..ba54fcd 100644 --- a/applications/PoP/roomtest/pop_cdraw.c +++ b/applications/PoP/roomtest/pop_cdraw.c @@ -223,6 +223,13 @@ static uint8_t cd_quiet(uint8_t who, uint8_t p) cd_sig_t g; const uint8_t *a, *b; uint8_t i; + /* Соперника на сцене нет и на ЭТОЙ странице от него ничего не осталось — + * стирать и рисовать нечего, слот «тихий». Без этого пустой слот каждый + * кадр честно проходил heal + вход в pop_char_draw + fore-проход и стоил + * ~8 200 тактов из ничего (замер MAME, комната без стража). */ + if (who == POP_CH_OPP && Guard.charid == 0 && + !pop_cd[who].valid[p] && !pop_cd[who].ovalid[p]) + return 1; if (!pop_cd[who].valid[p]) return 0; if (pop_cd_dirty & (1 << p)) { /* фон правили — но задели ли нас? */ int x0, y0, x1, y1; @@ -254,7 +261,10 @@ uint8_t pop_char_skip_mask(void) __banked * рядом — перерисовываем ОБОИХ. */ uint8_t st = (uint8_t)(q0 ? POP_CH_KID : POP_CH_OPP); uint8_t mv = (uint8_t)(st ^ 1); - if (pop_cd[mv].valid[p]) { + /* valid[st] тоже: «тихий» слот, в котором на этой странице НИЧЕГО не + * нарисовано (соперника на сцене нет), выесть невозможно, а bbox у + * него мусорный — правило соседства к нему неприменимо. */ + if (pop_cd[st].valid[p] && pop_cd[mv].valid[p]) { int ax0, ay0, ax1, ay1, bx0, by0, bx1, by1; cd_bbox(st, p, &ax0, &ay0, &ax1, &ay1); cd_bbox(mv, p, &bx0, &by0, &bx1, &by1); diff --git a/applications/PoP/roomtest/pop_geom.c b/applications/PoP/roomtest/pop_geom.c index 55ab792..318e24a 100644 --- a/applications/PoP/roomtest/pop_geom.c +++ b/applications/PoP/roomtest/pop_geom.c @@ -10,9 +10,35 @@ const int16_t pop_x_bump[20] = { }; const int16_t pop_y_land[5] = {-8, 55, 118, 181, 244}; +#define TILE_SIZEY_OFF 60 /* сдвиг «ряд 0 начинается выше нуля» */ + +/* Медленный хвост — ОТДЕЛЬНОЙ функцией. Иначе SDCC видит одно и то же + * выражение в двух ветках и поднимает деление в вершину функции: быстрые + * возвраты уже не спасают, __divsint зовётся всё равно (проверено — + * 5 вызовов за кадр остались после первой переписи). */ +static int8_t y_row_div(int16_t t) +{ + return (int8_t)(t / TILE_SIZEY % 4 - 1); +} + int8_t pop_y_to_row(int16_t y) { - return (int8_t)((y + 60) / TILE_SIZEY % 4 - 1); + /* Было `(y + 60) / 63 % 4 - 1` — SDCC разворачивает это в __divsint плюс + * __modsint (а тот внутри снова зовёт __divsint): ~5 400 тактов на вызов + * по замеру в MAME, при том что полос всего четыре. Цепочка сравнений + * повторяет ИМЕННО прежнюю арифметику, включая усечение деления К НУЛЮ + * для отрицательных (t в −62..−1 даёт 0, а не −1). */ + int16_t t = (int16_t)(y + TILE_SIZEY_OFF); + if (t >= 0) { + if (t < TILE_SIZEY) return -1; /* 0 % 4 - 1 */ + if (t < 2*TILE_SIZEY) return 0; + if (t < 3*TILE_SIZEY) return 1; + if (t < 4*TILE_SIZEY) return 2; + if (t < 5*TILE_SIZEY) return -1; /* 4 % 4 = 0 */ + } else if (t > -TILE_SIZEY) { + return -1; /* t/63 == 0 -> 0 % 4 - 1 */ + } + return y_row_div(t); } /* Уложить значение в диапазон 0..maxv. Вызовы оригинала — prandom(1), diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index 97b7e49..b74a13e 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -21,6 +21,7 @@ #include "pop_map.h" #include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */ #include "pop_geom.h" /* общая геометрия (x_bump/y_land/y_to_row) */ +#include "pop_tile.h" /* POP_TILE_DIV/MOD — деление на ширину тайла таблицей */ #include "pop_redraw.h" /* пометки перерисовки (порт set_redraw_*) */ /* pop_bg нужен ТОЛЬКО падающему куску (mob): spawn/tick/pos — это движущийся * ОБЪЕКТ, а не перерисовка тайла (в оригинале он и живёт отдельно: mobs + @@ -387,64 +388,15 @@ static int char_dx_forward(int8_t dx) return (int)Char.x + d; } -/* tile_div_tbl (data:22A6). Оригинал делит xpos на 14 ТАБЛИЦЕЙ, а не - * арифметикой — и это оказалось не мелочью: `/14` и `%` 14 SDCC разворачивает - * в __divsint + __modsint, причём __modsint внутри зовёт __divsint ещё раз. - * Замер в MAME (стоящий Кид, комната 1.1): один вызов get_tile_div_mod — - * ~5 400 тактов, 13 вызовов за кадр = ~70 000 = 16 % кадрового периода - * на одно только «в какой колонке точка». - * - * Таблица сверена с SDLPoP seg006:702 побайтно. Индекс — xpos как есть, - * смещение -58 уже внутри значений. Вне 0..255 (у нас xpos - int, у +/* Деление на ширину тайла — таблицами POP_TILE_DIV/POP_TILE_MOD (резидент, + * pop_tile.c: их же читает слой фона). Вне 0..255 (у нас xpos — int, у * DOS-оригинала байт) остаётся честная арифметика: путь редкий. */ -static const int8_t tile_div_tbl[256] = { - -5, -5, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, - -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -2, - -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, - 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, - 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, -}; - -/* tile_mod_tbl (data:23A6) — остаток от того же деления. Сначала я считал - * его из xh тремя сдвигами, чтобы сэкономить 256 байт; SDCC развернул это в - * десяток команд со спиллами через IY. Вторая таблица и дешевле, и это - * ровно то, что делает оригинал. */ -static const uint8_t tile_mod_tbl[256] = { - 12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, - 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, - 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, - 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11, - 12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, - 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, - 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, - 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11, - 12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, -}; - /* col = (xpos-58)/14 округл. вниз; g_obj_xl = позиция в тайле (0..13). */ static int8_t get_tile_div_mod(int xpos) { if ((unsigned int)xpos < 256u) { - g_obj_xl = tile_mod_tbl[xpos]; - return tile_div_tbl[xpos]; + g_obj_xl = POP_TILE_MOD[xpos]; + return POP_TILE_DIV[xpos]; } else { int x = xpos - SCREENSPACE_X; int xh = x / TILE_SIZEX; diff --git a/applications/PoP/roomtest/pop_tile.c b/applications/PoP/roomtest/pop_tile.c index 61fcdda..2eb9da3 100644 --- a/applications/PoP/roomtest/pop_tile.c +++ b/applications/PoP/roomtest/pop_tile.c @@ -72,6 +72,54 @@ const piece pop_tile_table[31] = { { 97, 1, 0, 98, 1, 2, 0, 0, 43,100, 0, 0}, /* 1E debris w/torch */ }; +/* Деление координаты на ширину тайла ТАБЛИЦЕЙ (tile_div_tbl/tile_mod_tbl, + * seg006:702/23A6) — как в оригинале. Живут в резиденте, потому что нужны + * и карте (банк 3, колонка персонажа), и слою фона (банк 2, футпринт), а + * const-таблицу чужого банка не прочитать. + * + * Почему не деление: SDCC разворачивает / и % в __divsint + __modsint, + * причём __modsint внутри снова зовёт __divsint — замер в MAME дал 5 400 + * тактов на один вызов при 13+ вызовах за кадр. Индекс — координата как + * есть (0..255), смещение -58 уже внутри значений: POP_TILE_DIV[x] = + * floor((x - 58) / 14), POP_TILE_MOD[x] — остаток. Сверено с SDLPoP + * побайтно. */ +const int8_t POP_TILE_DIV[256] = { + -5, -5, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, +}; +const uint8_t POP_TILE_MOD[256] = { + 12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11, + 12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11, + 12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, +}; + const uint8_t POP_COL_XH[10] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}; const uint8_t POP_WALL_FRAM_BOTTOM[4] = {7, 9, 5, 3}; const uint8_t POP_WALL_FRAM_MAIN[4] = {8, 10, 6, 4}; @@ -107,19 +155,32 @@ uint8_t pop_loose_frame(uint8_t m) uint8_t pop_cd_dirty; int pop_cd_dx0[2], pop_cd_dy0[2], pop_cd_dx1[2], pop_cd_dy1[2]; +/* Цикл по страницам развёрнут, а x+w / y+h посчитаны один раз. Не + * вкусовщина: индекс-переменная заставляла SDCC считать адрес каждой из + * восьми ячеек сложением, а `x + w` в условии и в присваивании считались + * дважды — замер в MAME дал 6 846 тактов на вызов, а зовётся он с КАЖДОГО + * блита фона (два факела в комнате — уже 13 700 за кадр). */ void pop_cd_touch(int x, int y, int w, int h) { - uint8_t p; - for (p = 0; p < 2; p++) { - if (!(pop_cd_dirty & (1 << p))) { - pop_cd_dx0[p] = x; pop_cd_dy0[p] = y; - pop_cd_dx1[p] = x + w; pop_cd_dy1[p] = y + h; - } else { - if (x < pop_cd_dx0[p]) pop_cd_dx0[p] = x; - if (y < pop_cd_dy0[p]) pop_cd_dy0[p] = y; - if (x + w > pop_cd_dx1[p]) pop_cd_dx1[p] = x + w; - if (y + h > pop_cd_dy1[p]) pop_cd_dy1[p] = y + h; - } + int x1 = x + w, y1 = y + h; + uint8_t d = pop_cd_dirty; + if (!(d & 1)) { + pop_cd_dx0[0] = x; pop_cd_dy0[0] = y; + pop_cd_dx1[0] = x1; pop_cd_dy1[0] = y1; + } else { + if (x < pop_cd_dx0[0]) pop_cd_dx0[0] = x; + if (y < pop_cd_dy0[0]) pop_cd_dy0[0] = y; + if (x1 > pop_cd_dx1[0]) pop_cd_dx1[0] = x1; + if (y1 > pop_cd_dy1[0]) pop_cd_dy1[0] = y1; + } + if (!(d & 2)) { + pop_cd_dx0[1] = x; pop_cd_dy0[1] = y; + pop_cd_dx1[1] = x1; pop_cd_dy1[1] = y1; + } else { + if (x < pop_cd_dx0[1]) pop_cd_dx0[1] = x; + if (y < pop_cd_dy0[1]) pop_cd_dy0[1] = y; + if (x1 > pop_cd_dx1[1]) pop_cd_dx1[1] = x1; + if (y1 > pop_cd_dy1[1]) pop_cd_dy1[1] = y1; } pop_cd_dirty = 3; } diff --git a/applications/PoP/roomtest/pop_tile.h b/applications/PoP/roomtest/pop_tile.h index 64fb517..ca2304b 100644 --- a/applications/PoP/roomtest/pop_tile.h +++ b/applications/PoP/roomtest/pop_tile.h @@ -74,6 +74,12 @@ extern const piece pop_tile_table[31]; /* Таблицы, нужные ОБЕИМ половинам слоя фона (односторонние живут по месту). */ extern const uint8_t POP_COL_XH[10]; /* x тайла = COL_XH[col]*8 */ +/* Деление координаты на ширину тайла таблицей (оригинал: tile_div_tbl / + * tile_mod_tbl, seg006:702). Индекс — координата 0..255 как есть, смещение + * -58 уже внутри: POP_TILE_DIV[x] = floor((x-58)/14), POP_TILE_MOD[x] — + * остаток. Резидент: нужны и карте (банк 3), и слою фона (банк 2). */ +extern const int8_t POP_TILE_DIV[256]; +extern const uint8_t POP_TILE_MOD[256]; extern const uint8_t POP_WALL_FRAM_BOTTOM[4]; /* нижняя грань стены по bg */ extern const uint8_t POP_WALL_FRAM_MAIN[4]; /* основная грань стены */ extern const uint8_t POP_SPIKES_FRAM_LEFT[10]; /* кадр пик в своей ячейке */ diff --git a/applications/PoP/roomtest/pop_trob.c b/applications/PoP/roomtest/pop_trob.c index fcd0254..f3f3880 100644 --- a/applications/PoP/roomtest/pop_trob.c +++ b/applications/PoP/roomtest/pop_trob.c @@ -467,6 +467,14 @@ static uint8_t torch_tick; #define TORCH_ANIM_NOW 1 #endif +/* Разбор номера тайла 0..29 на ряд/колонку без деления. */ +static const uint8_t TP_ROW[30] = { + 0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2 +}; +static const uint8_t TP_COL[30] = { + 0,1,2,3,4,5,6,7,8,9, 0,1,2,3,4,5,6,7,8,9, 0,1,2,3,4,5,6,7,8,9 +}; + void pop_process_trobs(uint8_t cur_room) __banked { uint8_t i, w = 0; @@ -549,7 +557,10 @@ void pop_process_trobs(uint8_t cur_room) __banked (code == TILE_TORCH || code == TILE_TORCH_DEBRIS || code == TILE_POTION)) { /* Пламя/пузырёк анимируются КАЖДЫЙ кадр и в фон не запечены — * рисуем поверх (сами делают heal своей области). */ - uint8_t trow = tp / 10, tcol = tp % 10; + /* Таблицей, а не tp/10 и tp%10: у Z80 деления нет, SDCC зовёт + * __divuchar + __moduchar — на два факела это четыре вызова за + * кадр (замер MAME). Тайл всегда 0..29. */ + uint8_t trow = TP_ROW[tp], tcol = TP_COL[tp]; if (code == TILE_POTION) pop_potion_draw(trow, tcol, mod[tp]); else pop_torch_draw(trow, tcol, mod[tp]); } diff --git a/applications/PoP/roomtest/tests-host/stubs.c b/applications/PoP/roomtest/tests-host/stubs.c index 1c6d5ff..7305a9c 100644 --- a/applications/PoP/roomtest/tests-host/stubs.c +++ b/applications/PoP/roomtest/tests-host/stubs.c @@ -273,3 +273,43 @@ void pop_guard_state_save(uint8_t room, uint8_t tile, int8_t dir, uint8_t x, uint16_t pop_guard_state_seq(uint8_t room) { return (room >= 1 && room <= TK_ROOMS) ? tk_gs_seq[room - 1] : 0; } uint8_t pop_guard_state_x(uint8_t room) { (void)room; return 0; } + +/* Таблицы деления на ширину тайла — настоящие (резидент pop_tile.c в тест + * не линкуется). Не заглушка: физика считает по ним колонку, подменять + * нельзя. Значения сгенерированы той же формулой, что в pop_tile.c. */ +const int8_t POP_TILE_DIV[256] = { + -5, -5, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, + -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, +}; +const uint8_t POP_TILE_MOD[256] = { + 12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11, + 12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11, + 12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, +};