DRAW-CHAR: отрисовка одна на всех Char; разгрузка банка 2 (90.4% -> 72.9%)

DRAW-CHAR.  Отрисовка персонажа сведена к одному набору функций над Char —
как физика после GUARD-PHYS.  В оригинале add_kid_to_objtable (seg008:22F0) и
add_guard_to_objtable (seg008:2324) имеют идентичное тело и различаются
окном (loadkid/loadshad), набором спрайтов и типом объекта, а
redraw_at_char/redraw_at_char2 гейтов по charid не имеют вовсе.

  pop_gdraw.c -> pop_cdraw.c: pop_char_draw/heal/fore(who), слот
  POP_CH_KID / POP_CH_OPP; состояние слотов pop_cd[] в _DATA — читается из
  любого банка без трамплина.  Проход окклюзии тоже один
  (pop_fore_over_char), pop_fore_over_kid больше нет.

Починилось само (расхождения, которые и были ценой дублирования): у
соперника не было clip_char; у Кида не было клипа полем 192 и ветки брызг
«мёртв/падение»; char_width_half СТРАЖА считался по спрайту КИДА.

Замер: _CODE 24 881 -> 20 524 (куча 2023 -> 6333), BANK2 -265, итого -3.2 КБ.
Проверено пользователем в MAME; циан-полоса профиля подросла — оптимизация
заведена отдельной задачей DRAW-COST.

MEM-BANK2, шаг 1: общие «листья» слоя фона в РЕЗИДЕНТ (pop_tile.c/.h).
Ограничение платформы: писучие данные банка лежат в _DATA и видны всем, а
const-таблицы — в странице банка, из другого банка их не прочитать; трамплин
же выбирается объявлением, то есть __banked на листе бьёт и по горячим
вызывающим (654 такта).  W1 замаплено всегда — оттуда обе половины зовут
листья прямым call и читают таблицы напрямую.

MEM-BANK2, шаг 2: дедуп внутри банка.  wall_pattern 808 -> 394 и wall_rnd
786 -> 654: четыре ветки по виду стены отличались только набором кусков и
числами в одной серии prandom — сведены к таблицам WP_PARTS и WR_RULE,
порядок вызовов prandom сохранён дословно.

Заодно: kid_seq_off больше не static const в kid_data.h (230 Б мёртвой копии
в каждом из 9 модулей) — генератор pop_extract_kid_data.py отдаёт
макро-инициализатор, массив определяет один pop_kid.c.

Итог: BANK2 14 815 -> 11 942 (72.9 %, свободно 4442 Б), _CODE 22 556,
куча 4301 Б.  tests-host зелёные (65/39/53/1723/1); в MAME комната 1
совпала с дорефакторным снимком попиксельно (0 из 227 520), комната 3 —
та же раскладка кладки.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 13:25:53 +03:00
parent 6673279cef
commit bbf91d10ee
28 changed files with 1593 additions and 1471 deletions
+240
View File
@@ -0,0 +1,240 @@
/*
* pop_tile.c — общие ЛИСТЬЯ слоя фона: блит куска атласа, чтение тайла
* комнаты, таблица тайлов. РЕЗИДЕНТ W1 (_CODE).
*
* Мотивация раскладки (почему резидент, а не банк) — в шапке pop_tile.h:
* коротко, эти функции и таблицы нужны ОБЕИМ половинам слоя фона (горячей —
* fore-проход каждый кадр, холодной — полная отрисовка комнаты), а код и
* rodata банка видны только пока банк замаплен. W1 замаплено всегда.
*/
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include "pop_bg.h" /* POP_YOFF */
#include "pop_tile.h"
#include "pop_level.h" /* pop_doorlink2 — подстановка нажатой кнопки */
#include "_pop_draw.h" /* pop_heal_fast */
/* ---- Состояние (писучее, _DATA/W2 — видно из любого банка) ---------- */
atlas_t pop_env[5], pop_wall_at, pop_fore_at, pop_pot_at;
const uint8_t *pop_t_fg, *pop_t_bg, *pop_t_lfg, *pop_t_lbg;
const uint8_t *pop_t_below, *pop_t_above, *pop_t_abovemod;
uint8_t pop_t_room;
pop_rnd_t pop_t_seed;
int pop_t_clip_top;
int pop_t_fclip_x0, pop_t_fclip_y0, pop_t_fclip_x1, pop_t_fclip_y1;
uint8_t pop_t_fclip_on;
uint8_t pop_t_bake_rest;
/* Позиция склянки внутри тайла зелья — поля fore_x/fore_y записи 0x0A той же
* таблицы. Вынесены МАКРОСАМИ (одно место правки, дублирования данных нет):
* пока таблица была `static const`, SDCC сворачивала `tile_table[10].fore_x`
* в константу, но глобальную (её читают оба банка) уже не сворачивает —
* чтение полей в рантайме стоило +150 Б и IX-кадр на 13 локалов. */
#define POT_FORE_X 2
#define POT_FORE_Y (-3)
/* ---- Таблица тайлов (piece, seg008.c:27) --------------------------- */
const piece pop_tile_table[31] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* 00 empty */
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 01 floor */
{127, 1, 0, 133, 1, 2, 145, 0, 43, 0, 0, 0}, /* 02 spike */
{ 92, 1, 0, 93, 1, 2, 0, 94, 43, 95, 1, 0}, /* 03 pillar */
{ 46, 1, 0, 47, 1, 2, 0, 48, 43, 49, 3, 0}, /* 04 gate */
{ 41, 1, 1, 35, 1, 3, 145, 0, 36, 0, 0, 0}, /* 05 stuck floor */
{ 41, 1, 0, 42, 1, 2, 145, 0, 96, 0, 0, 0}, /* 06 close button */
{ 46, 1, 0, 0, 0, 2, 0, 0, 43, 49, 3, 0}, /* 07 door top w/floor */
{ 86, 1, 0, 87, 1, 2, 0, 0, 43, 88, 1, 0}, /* 08 big pillar bottom */
{ 0, 0, 0, 89, 0, 3, 0, 90, 0, 91, 1, 3}, /* 09 big pillar top */
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 12, POT_FORE_X, POT_FORE_Y}, /* 0A potion */
{ 0, 1, 0, 0, 0, 0, 145, 0, 0, 0, 0, 0}, /* 0B loose floor */
{ 0, 0, 0, 0, 0, 2, 0, 0, 85, 49, 3, 0}, /* 0C door top */
{ 75, 1, 0, 42, 1, 2, 0, 0, 43, 77, 0, 0}, /* 0D mirror */
{ 97, 1, 0, 98, 1, 2, 145, 0, 43,100, 0, 0}, /* 0E debris */
{147, 1, 0, 42, 1, 1, 145, 0,149, 0, 0, 0}, /* 0F open button */
{ 41, 1, 0, 37, 0, 0, 0, 38, 43, 0, 0, 0}, /* 10 leveldoor left */
{ 0, 0, 0, 39, 1, 2, 0, 40, 43, 0, 0, 0}, /* 11 leveldoor right */
{ 0, 0, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 12 chomper */
{ 41, 1, 0, 42, 1, 2, 0, 0, 43, 0, 0, 0}, /* 13 torch */
{ 0, 0, 0, 1, 1, 2, 0, 2, 0, 0, 0, 0}, /* 14 wall */
{ 30, 1, 0, 31, 1, 2, 0, 0, 43, 0, 0, 0}, /* 15 skeleton */
{ 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 16 sword */
{ 41, 1, 0, 10, 0, 0, 0, 11, 43, 0, 0, 0}, /* 17 balcony left */
{ 0, 0, 0, 12, 1, 2, 0, 13, 43, 0, 0, 0}, /* 18 balcony right */
{ 92, 1, 0, 42, 1, 2, 145, 0, 43, 95, 1, 0}, /* 19 lattice pillar */
{ 1, 0, 0, 0, 0, 0, 0, 0, 2, 9, 0,-53}, /* 1A lattice down */
{ 3, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1B lattice small */
{ 4, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1C lattice left */
{ 5, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1D lattice right */
{ 97, 1, 0, 98, 1, 2, 0, 0, 43,100, 0, 0}, /* 1E debris w/torch */
};
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};
const uint8_t POP_SPIKES_FRAM_LEFT[10] = {0, 128, 129, 130, 131, 132, 131, 129, 128, 0};
const uint8_t POP_LOOSE_FRAM_LEFT[12] = {41,69,41,70,70,41,41,41,70,70,70,0};
const uint8_t POP_LOOSE_FRAM_BOTTOM[12] = {43,73,43,74,74,43,43,43,74,74,74,0};
/* get_loose_frame (seg008:761): индекс дрожащего кадра по модификатору.
* В режиме ЗАПЕЧКИ фона отдаёт кадр ПОКОЯ — транзиентный кадр нельзя
* консервировать в ОЗУ-копию (см. pop_t_bake_rest в pop_bg.c). */
uint8_t pop_loose_frame(uint8_t m)
{
if (pop_t_bake_rest) return 0;
if (m & 0x80) { m &= 0x7F; if (m > 10) return 1; }
return m;
}
/* ---- Блит куска атласа ---------------------------------------------- */
void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
{
const uint8_t *img;
uint16_t w, h;
int top, dx, dy, dw, dh, sx = 0, sy = 0;
if (idx >= a->count)
return;
/* Грубый отсев ДО маппинга страницы: реальные w/h лежат в заголовке
* ленты, то есть в EMM-странице, и чтобы их прочитать, надо сделать
* gfx_w0_map. Берём заведомо больший габарит (кадры фона не крупнее
* 64×64) и, если он не задевает окно, уходим не заплатив ни за
* atlas_image, ни за map/unmap. */
if (pop_t_fclip_on &&
(x >= pop_t_fclip_x1 || x + 64 <= pop_t_fclip_x0 ||
ybottom + POP_YOFF + 1 <= pop_t_fclip_y0 ||
ybottom + POP_YOFF - 64 >= pop_t_fclip_y1))
return;
img = (const uint8_t *)atlas_image(a, idx);
gfx_w0_map(a->page);
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
dx = x; dy = top; dw = (int)w; dh = (int)h;
if (pop_t_clip_top && dy < pop_t_clip_top) { /* полоса у потолка */
sy = pop_t_clip_top - dy; dy = pop_t_clip_top; dh -= sy;
}
if (pop_t_fclip_on) { /* окно fore-слоя */
if (dx < pop_t_fclip_x0) { int d = pop_t_fclip_x0 - dx; sx += d; dx += d; dw -= d; }
if (dy < pop_t_fclip_y0) { int d = pop_t_fclip_y0 - dy; sy += d; dy += d; dh -= d; }
if (dx + dw > pop_t_fclip_x1) dw = pop_t_fclip_x1 - dx;
if (dy + dh > pop_t_fclip_y1) dh = pop_t_fclip_y1 - dy;
}
if (w && h && dw > 0 && dh > 0) {
/* Целиком на экране — линейное ядро без клипа: ~2.9× быстрее
* (замер: 4617 против 13288 тактов на спрайт 32×3; общее ядро
* gfx_blit платит за клип/16-бит/split, а не за пиксели).
* W3-скобку ставит сама libbgi: из кода, собранного с --w3, её
* вызывать нельзя (окно W3 уходит под видеобанк). */
if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 &&
dx + dw <= 320 && dy + dh <= 256 && w < 256) {
if (sx == 0 && sy == 0 && dw == (int)w && dh == (int)h)
gfx_blit_noclip(dx, dy, img);
else
gfx_blit_part_noclip(dx, dy, img, (uint8_t)sx, (uint8_t)sy,
(uint8_t)dw, (uint8_t)dh);
} else {
gfx_blit_part(dx, dy, img, sx, sy, dw, dh); /* край экрана — с клипом */
}
}
gfx_w0_unmap();
}
void pop_env_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_env[id >> 5], (uint8_t)(id & 31), x, yb); }
void pop_wall_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_wall_at, id, x, yb); }
void pop_fore_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_fore_at, id, x, yb); }
void pop_pot_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_pot_at, id, x, yb); }
void pop_heal_off(int x, int y, int w, int h)
{
pop_heal_fast(x, y + POP_YOFF, w, h);
}
/* ---- Чтение тайла отрисованной комнаты ------------------------------ */
uint8_t pop_tile_code(int row, int col)
{
if (col < 0) {
if (pop_t_lfg && row >= 0 && row < 3) return pop_t_lfg[row];
return 20; /* уровневая кромка = стена */
}
if (row < 0) { /* верхняя полоса: ряд 2 комнаты сверху */
if (row == -1 && pop_t_above && col < 10) return pop_t_above[col];
return 1; /* нет комнаты сверху → пол (кромка) */
}
if (row > 2 || col > 9) return 20;
return pop_t_fg[row * 10 + col];
}
uint8_t pop_tile_mod(int row, int col)
{
if (col < 0) {
if (pop_t_lbg && row >= 0 && row < 3) return pop_t_lbg[row];
return 0;
}
if (row == -1 && pop_t_abovemod && col < 10) return pop_t_abovemod[col];
if (row < 0 || row > 2 || col > 9) return 0;
return pop_t_bg[row * 10 + col];
}
/* Порт подстановки нажатой кнопки из get_tile_to_draw (seg008:240): opener →
* floor, closer → stuck, когда таймер связи > 1. Любой слой, который
* выбирает ветку отрисовки по типу тайла, обязан смотреть на ПОДСТАВЛЕННЫЙ
* тип (иначе кнопка, на которой стоит Kid, ведёт себя как «не пол» — напр.
* climb-оверлей уходил в draw_other_overlay и закрашивал Kid целым тайлом
* вместо узкой кромки floor_left_overlay). */
uint8_t pop_tile_code_drawn(int row, int col)
{
uint8_t code = pop_tile_code(row, col);
if (code == 0x0F) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 1; }
else if (code == 0x06) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 5; }
return code;
}
uint8_t pop_wall_modifier(int row, int col)
{
uint8_t lw = (col > 0) ? (pop_tile_code(row, col - 1) == 20) : 1;
uint8_t rw = (col < 9) ? (pop_tile_code(row, col + 1) == 20) : 1;
if (lw && rw) return 3; /* WWW */
if (lw) return 2; /* WWS */
if (rw) return 1; /* SWW */
return 0; /* SWS */
}
/* ---- Зелье: склянка (tiles_10_potion, seg008 draw_tile_fore) --------- *
* Тип = (modif & 0xF8) >> 3: 0 = пусто (ничего не рисуем), 2..4 — «большая»
* склянка (id 13), остальные — маленькая (id 12). Спрайты из chtab_1 (атлас
* pop_pot), поэтому НЕ из env: раньше рисовали chtab_6 id 12 (res212),
* которого в VDUNGEON нет — оттуда и «мусорный объект». Позиция оригинала:
* xh = fore_x + draw_xh, xl = 6, ybottom = fore_y + draw_main_y.
*
* Здесь, а не в банке: склянку рисует и полная отрисовка тайла (холодная
* половина), и fore-проход поверх персонажа (горячая). */
void pop_potion_flask(int row, int col, uint8_t modif)
{
uint8_t type = (uint8_t)((modif & 0xF8) >> 3);
uint8_t id = 12;
if (type == 0) return;
if (type >= 2 && type < 5) id = 13; /* большая склянка */
pop_pot_b(id, (POT_FORE_X + POP_COL_XH[col]) * 8 + 6,
POT_FORE_Y + 63 * row + 62);
}
/* Окно fore-клипа: ставит тот, кто нарисовал персонажа (pop_cdraw, банк 4),
* читает blit_b. Резидент — чтобы вызов из банка шёл без трамплина. */
void pop_fore_set_clip(int x, int y, int w, int h)
{
pop_t_fclip_x0 = x; pop_t_fclip_y0 = y;
pop_t_fclip_x1 = x + w; pop_t_fclip_y1 = y + h;
}
/* Ряд 0 комнаты СНИЗУ (fg-коды) — нужен draw_tile_topright/floorright, когда
* тайл нижнего ряда стал видимым (empty после падения loose): «стена вниз» =
* топрайт тайла room_B(0, col-1). 0 — нет комнаты снизу (кромка = стена). */
void pop_room_set_below(const uint8_t *below_row0_fg) { pop_t_below = below_row0_fg; }
/* Ряд 2 (fg-коды + модификаторы) комнаты СВЕРХУ — для полосы кладки у
* потолка (seg008 draw_room: доп. верхний ряд из room_A, draw_tile_aboveroom
* при draw_main_y=-1). 0 — нет комнаты сверху (кромка уровня → пол). */
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod)
{ pop_t_above = above_row2_fg; pop_t_abovemod = above_row2_mod; }