Files
Sprinter-SDCC/applications/PoP/roomtest/pop_tile_cold.c
T
snark13 b6699b3aef Разрез pop_tile: холодная половина в банк 5 (−1788 Б резидента)
pop_tile.c был крупнейшим жильцом резидента (5972 Б кода).  Целиком он не
уедет — его const-таблицы читают банки 2, 3, 7 и 8, а таблица в чужом банке
не видна.  Поэтому разрез, а не перенос.

Отбор ЗАМЕРОМ, а не по смыслу: каждая функция посчитана брейкпоинтом-
счётчиком в MAME — сколько вызовов в кадре покоя и сколько в кадре полной
перерисовки комнаты (форсируется читом +/-).  Порог — пик не больше 3.
Проверено на ДВУХ тайлсетах, подземелье и дворец: pop_mem_b рисует
композитный кусок и мог оказаться дворцовым, но и там 0 вызовов.

Уехало: pop_mem_b, pop_cd_hit (+hit_rect), pop_cd_hit_slot, pop_cd_init,
pop_cd_clear, pop_t_win_set/clear, pop_bar_black, pop_heal_off,
pop_potion_flask, pop_room_set_above/below.
Осталось: pop_blit_b со статиками (184 вызова на редрав), pop_cd_touch
(198), pop_tile_code (296), pop_wall_modifier (101), pop_env_b (73),
pop_tile_mod (70), все таблицы.  pop_fore_set_clip оставлен намеренно —
88 Б не стоят отказа от прямого вызова из банка 4.

Цена трамплина замерена: 252 такта пролог + 84 эпилог + ~50 у вызывающего
= ~410.  Это ~1000 тактов на кадр покоя (0,2 % работы) и ~3700 на редрав.

blit_b_clip перестал быть static и объявлен в _pop_tile.h: вызов
банк -> резидент прямой, трамплин не нужен, поэтому статик горячей половины
переносить следом не пришлось.

Итог: _CODE 23716 -> 21928, свободно 747 -> 2535 Б (с 129 Б до всех работ).
Проверено в MAME на уровнях 1 и 4, с переходами комнат.

Метод замера, таблица частот и ловушка с данными банка — docs/resident_budget.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:26:05 +03:00

241 lines
13 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.
/*
* pop_tile_cold.c — ХОЛОДНАЯ половина общих листьев слоя фона (банк 5).
*
* ЗАЧЕМ РАЗРЕЗ. pop_tile.c был самым крупным жильцом резидента (5 972 Б
* кода), а резидент huge-режима — это всё окно 0x4100..0xBB00, и место в нём
* нужно под музыку, заставки и UI. Но целиком pop_tile не уедет: его
* const-таблицы (POP_TILE_DIV/MOD, pop_tile_table, таблицы кадров) читают
* банки 2, 3, 7 и 8, а таблица в чужом банке не видна.
*
* КРИТЕРИЙ ОТБОРА — ЗАМЕР, а не догадка. Каждая функция pop_tile была
* посчитана брейкпоинтом-счётчиком в MAME: сколько вызовов в кадре покоя и
* сколько в кадре ПОЛНОЙ перерисовки комнаты (её форсирует чит +/-). Сюда
* переехало только то, у чего пик <= 3 вызовов на кадр — и проверено это
* на ДВУХ тайлсетах: подземелье (уровень 1) и дворец (уровень 4), потому
* что pop_mem_b рисует композитный кусок и мог оказаться дворцовым.
* Замер: pop_mem_b 0, pop_cd_hit 0, pop_t_win_set 0..1, pop_heal_off 0..1,
* pop_room_set_above 1, pop_bar_black 3, pop_potion_flask 0..1,
* pop_cd_hit_slot 2..3 — при канарейке pop_blit_b 184..214 в том же кадре.
*
* ЦЕНА. Трамплин замерен: 252 такта пролог + 84 эпилог + ~50 на стороне
* вызывающего = ~410 тактов на вызов. На кадр покоя выходит ~1 000 тактов
* (0,2 % работы кадра), на кадр редрава ~3 700 (0,009 растра).
*
* ЧТО ОСТАЛОСЬ В РЕЗИДЕНТЕ и почему: pop_blit_b (184 вызова на редрав) со
* своими статиками blit_b_clip/blit_b_oversize, pop_cd_touch (198),
* pop_tile_code (296), pop_wall_modifier (101), pop_env_b (73),
* pop_tile_mod (70), pop_cd_batch_end (40) — и все const-таблицы.
* pop_fore_set_clip оставлен намеренно: 88 Б не стоят отказа от прямого
* вызова из банка 4, ради которого он и заводился.
*
* ВЫЗОВЫ ОТСЮДА В РЕЗИДЕНТ — прямые, без трамплина: W1/W2 замаплены всегда.
* Поэтому blit_b_clip (статик горячей половины) просто перестал быть static
* и объявлен в _pop_tile.h, а не переехал следом.
*/
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include "pop_bg.h" /* POP_YOFF, POP_PLAYFIELD_H */
#include "pop_map.h" /* pop_upside — переворот (зелье инверсии) */
#include "pop_cdraw.h" /* pop_cd, pop_cd_dirty, pop_cd_ymin/ymax */
#include "pop_tile.h"
#include "_pop_draw.h" /* pop_heal_fast */
#include "_pop_tile.h" /* blit_b_clip — из горячей половины */
#define FLIP_TOP(top, h) POP_FLIP_TOP(top, h) /* формула — в pop_bg.h */
/* Смещение склянки внутри тайла (seg008 draw_tile_fore): те же числа, что в
* tile_table горячей половины — дублируем, потому что там они локальные
* define'ы, а тащить их в публичный заголовок ради одной функции незачем. */
#define POT_FORE_X 2
#define POT_FORE_Y (-3)
/* Прямоугольник слота против метки — БЕЗ передачи пяти аргументов.
*
* pop_cd_hit принимает (p, x0, y0, x1, y1): три последних идут стеком, и
* функция целиком уезжает в IX-фрейм — 45 % её тактов уходит на `-n(ix)`
* (замер asm 2026-08-19). А зовут её из cd_quiet дважды на слот, то есть
* до восьми раз за кадр. Здесь координаты берутся прямо из pop_cd, и
* аргументов остаётся два.
*
* Спрайт и накладной проверяются РАЗДЕЛЬНО — объединённый bbox ловит
* касания углом, которых нет (разбор в cd_quiet, pop_cdraw.c). */
static int hit_x, hit_y; /* аргументы hit_rect — file-scope, не стек */
static uint16_t hit_w, hit_h;
static uint8_t hit_p;
static uint8_t hit_rect(void)
{
int8_t c0, c1, c;
uint8_t ya, yb;
int x1 = hit_x + (int)hit_w - 1;
int y1 = hit_y + (int)hit_h - 1;
if (!hit_w || !hit_h) return 0;
if (x1 < 0 || hit_x > 319) return 0;
if (y1 < 0 || hit_y > 255) return 0;
ya = (uint8_t)(hit_y < 0 ? 0 : hit_y);
yb = (uint8_t)(y1 > 255 ? 255 : y1);
c0 = (int8_t)((hit_x < 0 ? 0 : hit_x) >> 5);
c1 = (int8_t)((x1 > 319 ? 319 : x1) >> 5);
for (c = c0; c <= c1; c++)
if (ya <= pop_cd_ymax[hit_p][c] && yb >= pop_cd_ymin[hit_p][c]) return 1;
return 0;
}
uint8_t pop_cd_hit_slot(uint8_t who, uint8_t p) __banked
{
const pop_cdraw_t *s = &pop_cd[who];
if (!(pop_cd_dirty & (1 << p))) return 0;
hit_p = p;
hit_x = s->x[p]; hit_y = s->y[p] + POP_YOFF;
hit_w = s->w[p]; hit_h = s->h[p];
if (hit_rect()) return 1;
if (!s->ovalid[p]) return 0;
hit_x = s->ox[p]; hit_y = s->oy[p] + POP_YOFF;
hit_w = s->ow[p]; hit_h = s->oh[p];
return hit_rect();
}
uint8_t pop_cd_hit(uint8_t p, int x0, int y0, int x1, int y1) __banked
{
int8_t c0, c1, c;
uint8_t ya, yb;
if (!(pop_cd_dirty & (1 << p))) return 0;
if (x1 < 0 || x0 > 319) return 0;
if (x0 < 0) x0 = 0;
if (x1 > 319) x1 = 319;
if (y1 < 0 || y0 > 255) return 0;
ya = (uint8_t)(y0 < 0 ? 0 : y0);
yb = (uint8_t)(y1 > 255 ? 255 : y1);
c0 = (int8_t)(x0 >> 5);
c1 = (int8_t)(x1 >> 5);
/* Пересечение отрезков [ya,yb] и [ymin,ymax] хотя бы в одной колонке.
* Пустая колонка держит ymin = 255, ymax = 0 — условие ниже её отсеет
* само, отдельной проверки «пусто» не нужно. */
for (c = c0; c <= c1; c++)
if (ya <= pop_cd_ymax[p][c] && yb >= pop_cd_ymin[p][c]) return 1;
return 0;
}
/* Страница приведена в порядок — снять с неё метку целиком. */
/* Начальное состояние обеих страниц — «ничего не трогали». ЯВНО, а не
* расчётом на обнуление _DATA: пустая колонка обозначается ymin = 255,
* ymax = 0, а нули от crt0 читались бы как «затронута строка 0». */
void pop_cd_init(void) __banked
{
pop_cd_clear(0);
pop_cd_clear(1);
}
void pop_cd_clear(uint8_t p) __banked
{
uint8_t c;
for (c = 0; c < 10; c++) { pop_cd_ymin[p][c] = 255; pop_cd_ymax[p][c] = 0; }
pop_cd_dirty = (uint8_t)(pop_cd_dirty & ~(1 << p));
}
/* Окно под точечную перерисовку/запечку — контракт в шапке pop_tile.h.
* Логическая пара нужна для отсева тайлов (он считается из ряда комнаты),
* экранная — для клипа самого блита; при перевороте они разные. */
void pop_t_win_set(int x, int ytop, int w, int h) __banked
{
pop_t_fclip_x0 = x;
pop_t_fclip_x1 = x + w;
pop_t_fclip_ly0 = ytop;
pop_t_fclip_ly1 = ytop + h;
if (pop_upside) {
int t = FLIP_TOP(ytop, h);
pop_t_fclip_y0 = t;
pop_t_fclip_y1 = t + h;
} else {
pop_t_fclip_y0 = ytop;
pop_t_fclip_y1 = ytop + h;
}
pop_t_fclip_on = 1;
}
void pop_t_win_clear(void) __banked
{ pop_t_fclip_on = 0; }
/* Блит getimage-блоба из ОБЫЧНОЙ памяти (W2), а не из EMM-страницы атласа:
* те же клип полосы у потолка и окно перерисовки, что у pop_blit_b, но без
* atlas_image и без gfx_w0_map/unmap (это ещё ~1 350 такта). Заведено под
* композитный спрайт падающего куска (pop_room.c: три части сложены в один
* блоб, один вызов блита вместо трёх). */
void pop_mem_b(const uint8_t *img, int x, int ybottom) __banked
{
uint8_t w = img[0], h = img[2];
int top;
if (pop_t_fclip_on &&
(x >= pop_t_fclip_x1 || x + 64 <= pop_t_fclip_x0 ||
ybottom + POP_YOFF + 1 <= pop_t_fclip_ly0 ||
ybottom + POP_YOFF - 64 >= pop_t_fclip_ly1))
return;
if (!w || !h) return;
top = ybottom - (int)h + 1 + POP_YOFF;
if (pop_upside) top = FLIP_TOP(top, h);
if (!pop_t_fclip_on && !pop_t_clip_top &&
!(pop_upside && (top < POP_YOFF || top + (int)h > POP_CLIP_BOTTOM)) &&
x >= 0 && top >= 0 && x + (int)w <= 320 && top + (int)h <= 256) {
if (pop_upside) gfx_blit_noclip_vflip(x, top, img);
else gfx_blit_noclip(x, top, img);
pop_cd_touch(x, top, (int)w, (int)h);
} else {
blit_b_clip(img, x, top, w, h);
}
}
/* Heal по КОМНАТНОЙ координате (её и знают точечные перерисовки: чомпер,
* пики, loose, ворота, полоса потолка). При перевороте прямоугольник
* обязан отразиться ровно так же, как сам тайл, — иначе heal чистит одно
* место, а рисует в другое: именно так «ломалась анимация чомперов»
* (найдено пользователем на первом же прогоне зеркального фона). */
void pop_bar_black(int x, int top, int w, int h) __banked
{
if (pop_upside) top = FLIP_TOP(top, h);
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(x, top, x + w - 1, top + h - 1);
pop_cd_touch(x, top, w, h); /* bar идёт мимо pop_blit_b (DRAW-COST) */
}
void pop_heal_off(int x, int y, int w, int h) __banked
{
int top = y + POP_YOFF;
if (pop_upside) top = FLIP_TOP(top, h);
pop_heal_fast(x, top, w, h);
}
/* ---- Зелье: склянка (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) __banked
{
uint8_t type = (uint8_t)((modif & 0xF8) >> 3);
uint8_t id = 12;
if (type == 0) return;
if (type >= 2 && type < 5) id = 13; /* большая склянка */
if (pop_palace) id += 2; /* seg008:747: во дворце склянки другие */
pop_pot_b(id, (POT_FORE_X + POP_COL_XH[col]) * 8 + 6,
POT_FORE_Y + 63 * row + 62);
}
/* Ряд 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) __banked
{ 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) __banked
{ pop_t_above = above_row2_fg; pop_t_abovemod = above_row2_mod; }