31b82661eb
Порт PoP переехал в applications/SprPoP — приложение, которое собирается само: код, оригинальные данные, конверторы ресурсов и сборка внутри одной папки. Наружу знает единственный путь — корень тулчейна (SPRINTER_ROOT, по умолчанию ../..). applications/PoP/roomtest ЗАМОРОЖЕНА и остаётся архивом закрытых задач, багов и исполненных планов. Скопировано из applications/PoP/roomtest@4b74478. Перенос проверен побайтово: собранный sprpop.exe совпал с roomtest.exe того же коммита, все 39 дисковых ресурсов и все 16 генерируемых заголовков — тоже, host- тесты зелёные (15/15). Раскладка: src/ рукописный C (roomtest.c -> sprpop.c) gen/ генерируемые заголовки, в репозитории assets/orig/ оригинальные данные игры, вне репозитория (копирайт) assets/packed/ то, что ложится на диск, в раскладке диска tools/ конверторы; все пути — в одном tools/paths.py build/ выход: exe, каталоги ресурсов, hdd/, промежуточные atl/ Сборка ресурсов: assets/packed и gen — версионируемые ВХОДЫ, а не то, что пересчитывается каждым make. Автоматика построена на ОТСУТСТВИИ файла, а не на таймстемпах: git не хранит времена, и в свежем клоне сравнение по времени превращалось бы в лотерею. Недостающий ресурс или заголовок чинится сам, рекурсивным вызовом в ветку генерации. Музыка собирается из любого из четырёх наборов записей (make music-mp3, music-mt32, ...); набор входит в имя stamp'а, поэтому смена набора сама делает музыку устаревшей. Длины реплик больше не захардкожены: упаковщик печатает их в gen/pop_music_ticks.h, и шкала сцены выражена через них — иначе mt32 (реплики на 6% длиннее) молча ломал катсцену. Тулчейн: в app.mk два обратносовместимых крючка (SRC_DIR/BUILD_DIR), HDD_IMG стал ?=; команда сборки roomtest не изменилась. Корневой make host-tests переключён на SprPoP. Подгонка тайминга катсцены с принцессой (PV_MAGIC_LEAD): сцена render-bound и идёт ~49 тиков/с вместо 60, из-за чего кода реплики приходила раньше молнии. Это обход, а не лечение; разбор с замерами — docs/BUGS_OPEN.md, записи SND-PACE-DEAD, PV-RENDER-BOUND, MUS-LEFT-TEAR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
241 lines
13 KiB
C
241 lines
13 KiB
C
/*
|
||
* 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; }
|