Files
Sprinter-SDCC/libbgi/common/sprite_moveto.c
T
snark13 b9ddce8d34 libbgi: спрайты — кэш адреса кадра, DDA+asm тик, Y-сортировка со слоями
Оптимизации A+B (профиль rpgwalk-15: активная часть кадра 410К → 307К
тактов из 430080; лимит спрайтов 16×16 на стабильные 48.8 fps: 14 → ~21):

- (B) sprite_t.src/stride — готовый адрес кадра: считают только
  sprite_frame (теперь функция, одно умножение на СМЕНУ кадра) и тикер
  (±an_step БАЙТ инкрементально); блит-ядра принимают src+stride,
  img/sx/sy из сигнатуры ушли.  Блит 177К → 146К на кадр.
- (A) тик 100К → 37.7К: tween переформулирован Брезенхэм → беззнаковый
  DDA (mv_rem/mv_acc, «приехали» = rem==0 — без знаковых 16-бит
  сравнений), tick_move и tick_anim — ручной asm (SDCC спиллит такие
  функции в IX-фрейм ~100 обращений; C-реструктуризации не помогали —
  проверено кодогеном).  Биты an_flags переименованы по категориям
  (_SPR_STRIP_HORZ, _SPR_PP_BACK).

Y-сортировка (gfx_sprite_ysort, идеи пользователя — 8-бит ключ,
персистентность):

- painter's algorithm по ключу {layer:8, clamp_y:8}; поле
  sprite_t.layer (в КОНЦЕ структуры — asm-офсеты не сдвигает): слои
  сцены в одном массиве/одном sprite_update;
- ПЕРСИСТЕНТНАЯ asm-таблица {key16, ptr16}: resort порядка прошлого
  кадра (почти линейно), rebuild при смене arr/count; массив
  приложения не трогается; ~28К/15 спрайтов (с нуля было 44К);
- компоненты YSORT_Y/YSORT_LAYER отключаемы независимо масками ключа
  (без ветвлений в сортировщике); ВНИМАНИЕ: mode=1 значит Y-only,
  полный порядок = YSORT_Y|YSORT_LAYER;
- funcptr-DCE: выключено = код и таблица не линкуются (rpgwalk −190 Б);
- ПРАВИЛО в sprite.h: два sprite_update на страницу запрещены (heal
  второй группы стирает спрайты первой — ОЗУ-копия чистая).

Попутные фиксы:

- libbgi/Makefile: .rel зависят от заголовков (HDRS) — stale .rel со
  старой раскладкой sprite_t молча ломал рантайм;
- rpgprof: --memory small (перерос tiny: BSS вылезал за W2 → мгновенный
  «Unexpected application termination»; mkexe это пока не ловит);
- tests/spranim: проверки переведены на кэш src, добавлены T6 (reframe
  после тикера) и T7 (Y-сортировка: порядок, слои, персистентный
  resort, LAYER-only) — 7/7 PASS в MAME.

Доки: §9д — новый бюджет (19.5К/спрайт), §9е — ПЛАН FPS-делителя
(frame pacing, gfx_set_fps_div); TODO — дизайн цепочки irq-обработчиков.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:19:24 +03:00

68 lines
3.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* sprite_moveto — запустить tween-перемещение к (tx,ty): раз в interval
* кадров тикер делает до max_step шагов вдоль ГЛАВНОЙ оси (большая
* |дельта|); минорная ось подтягивается DDA-аккумулятором — нелинейные
* 2/3/2/3 сами собой, деления нет. Пример: (0,0)→(100,50), max_step 5,
* interval 4 → 20 порций × 4 кадра = 80 кадров (1.6 с).
*
* Всё дорогое (выбор главной оси, |дельты|, знаки) считается здесь один
* раз; тикеру остаётся БЕЗЗНАКОВАЯ арифметика (см. sprite.h: acc/rem —
* на Z80 беззнаковое 16-бит сравнение в разы дешевле знаковых сравнений
* классического Брезенхэма, ради этого и переформулировано 2026-07-14).
* За mv_dmaj шагов главной оси минорная получает ровно mv_dmin шагов
* (аккумулятор со стартом dmaj/2 — симметричное округление), так что
* обе координаты приходят в цель одновременно; «приехали» = mv_rem == 0.
*
* Если цель == текущая позиция — сразу SPR_MOVE_DONE (тикер не
* включается). Кадровые статусы (SPR_ANIM_*) не трогаются.
*/
#include "../_bgi.h"
#include <sprite.h>
void sprite_moveto(sprite_t *s, int tx, int ty,
uint8_t max_step, uint8_t interval)
{
int dx = tx - s->x;
int dy = ty - s->y;
int8_t sgnx = 1, sgny = 1;
if (dx < 0) { dx = -dx; sgnx = -1; }
if (dy < 0) { dy = -dy; sgny = -1; }
s->an_flags &= (uint8_t)~SPR_MOVE_DONE;
if (dx == 0 && dy == 0) {
s->an_flags = (s->an_flags & (uint8_t)~SPR_MOVE_ON) | SPR_MOVE_DONE;
return;
}
s->mv_tx = tx;
s->mv_ty = ty;
/* Главная ось — с большей |дельтой| (при равенстве — X, диагональ
* ровная в обе стороны). */
if ((uint16_t)dy > (uint16_t)dx) {
s->mv_ymaj = 1;
s->mv_dmaj = (uint16_t)dy;
s->mv_dmin = (uint16_t)dx;
s->mv_sgnmaj = sgny;
s->mv_sgnmin = sgnx;
} else {
s->mv_ymaj = 0;
s->mv_dmaj = (uint16_t)dx;
s->mv_dmin = (uint16_t)dy;
s->mv_sgnmaj = sgnx;
s->mv_sgnmin = sgny;
}
s->mv_rem = s->mv_dmaj; /* шагов до цели */
s->mv_acc = s->mv_dmaj >> 1; /* симметричное округление minor */
if (!max_step) max_step = 1;
if (!interval) interval = 1;
s->mv_step = max_step;
s->mv_speed = interval;
s->mv_timer = interval;
s->an_flags |= SPR_MOVE_ON;
_spr_tick_fn = _sprite_tick;
}