Ресурсы — в архивы PBA1; тайминги сцены PV — по часам насоса CBL

ТАЙМИНГИ.  Пользователь заметил, что заклинание Джафара не совпадает с
музыкой. Замер в MAME (breakpoint на вспышке + курсор трека): реплика m53
успевала ДОИГРАТЬ до молнии, хотя по шкале оригинала между входом Джафара
и заклинанием ровно 822 тика = 13,7 с.

Причина: пейсинг считал НАШИ ожидания vsync, а не прошедшее время.
Полноэкранная копия страницы стоит больше кадра луча, и разница копилась.
Кадровые прерывания для счёта не годятся (теряются в di-окнах
акселератора), поэтому часами стал насос CBL: он идёт от расхода буфера
железом, 85,4 Гц, и ему безразлично, чем занят главный цикл. Один тик
оригинала = 57/40 тика насоса (0,07 % ошибки, без деления в кадре).
Когда звук выключен, работает прежний путь по кадрам луча.

После фикса замер даёт 229 блоков остатка m53 против расчётных 233 —
расхождение 47 мс.

АРХИВЫ.  Группы ресурсов сложены в PBA1: kid (58 атласов), pv (78),
shadow (32), bg (25), title (20), guard (10), vizier (5), skel (4). Было
232 файла на образе, стало 8 архивов плюс шесть палитр и таблицы уровней.
При цене `open` 51,4 мс против 32,6 мс за чтение 16 КБ это возвращает
секунды на каждой загрузке.

- pop_pack_arc.py печатает индексы элементов константами (ARC_<GRP>_<FILE>),
  порядок задаёт Makefile, а серии код проверяет статически;
- atlas_load разделён: atlas_attach принимает уже прочитанную страницу,
  поэтому чтение из архива не дублирует проверку магии и подготовку W0;
- имена архивов живут в pop_arc.c и адресуются номером. Строковый литерал
  лежит в rodata своего банка, и указатель на него из другого банка после
  переключения W3 показывает на чужие данные — ровно так падала загрузка
  фона (поймано брейкпоинтом на puts).

Проверено в MAME: заставка, интро и уровень 1 собираются из архивов,
Тень грузит все 32 страницы. Host-тесты: 15 наборов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 21:29:14 +03:00
parent d86adc54c4
commit 65797af44c
40 changed files with 997 additions and 244 deletions
+43 -35
View File
@@ -18,6 +18,10 @@
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include "pop_arc.h"
#include "guard_arc.h"
#include "skel_arc.h"
#include "vizier_arc.h"
#include "pop_guard.h"
#include "pop_kid.h"
#include "_pop_kdraw.h"
@@ -107,31 +111,30 @@ pop_cdraw_t pop_cd[POP_CH_N];
* грузит. Уровень БЕЗ соперников (type < 0) не грузит ничего. */
int pop_guard_load(void) __banked
{
static const char *const guard_names[GUARD_PAGES] = {
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
"GUARD\\g3.atl", "GUARD\\g4.atl"
};
static const char *const skel_names[GUARD_PAGES] = {
"SKEL\\g0.atl", "SKEL\\g1.atl", "SKEL\\g2.atl",
"SKEL\\g3.atl", 0
};
/* Джафар (уровень 13, tbl_guard_type == 3) — VIZIER.DAT: кадров столько
* же, сколько у стража, страниц пять. */
static const char *const vizier_names[GUARD_PAGES] = {
"VIZIER\\g0.atl", "VIZIER\\g1.atl", "VIZIER\\g2.atl",
"VIZIER\\g3.atl", "VIZIER\\g4.atl"
};
const char *const *names;
uint8_t tbl[ARC_GUARD_COUNT * POP_ARC_REC]; /* самый крупный из трёх */
pop_arc_t arc;
int8_t type = pop_level_guard_type(pop_current_level);
uint8_t i;
uint8_t i, pages, count, id;
if (type < 0) return 0; /* соперников на уровне нет */
names = (type == 2) ? skel_names : (type == 3) ? vizier_names : guard_names;
for (i = 0; i < GUARD_PAGES; i++) {
if (names[i] == 0) break;
if (atlas_load(&gp[i], names[i]) != 0) return -1;
}
/* Наборы разложены по своим архивам, а не в один: имена внутри у них
* совпадают (g0.atl у всех троих), да и грузится всегда ровно один. */
if (type == 2) { id = POP_ARC_SKEL; count = ARC_SKEL_COUNT;
pages = ARC_SKEL_COUNT; }
else if (type == 3) { id = POP_ARC_VIZIER; count = ARC_VIZIER_COUNT;
pages = ARC_VIZIER_COUNT; }
else { id = POP_ARC_GUARD; count = ARC_GUARD_COUNT;
pages = GUARD_PAGES; }
if (pages > GUARD_PAGES) pages = GUARD_PAGES;
if (pop_arc_open_id(&arc, id, tbl, count) < 0) return -1;
for (i = 0; i < pages; i++)
if (pop_arc_atlas(&arc, tbl, i, &gp[i]) != 0) {
pop_arc_close(&arc);
return -1;
}
pop_arc_close(&arc);
g_ok = 1;
/* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется
/* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется
* из res10.bin по guards_color — это делает pop_guard_set_palette при
* входе в комнату (seg002:186: у не-guard curr_guard_color = 0). */
pop_guard_pal_apply();
@@ -143,24 +146,29 @@ int pop_guard_load(void) __banked
* потому что gp[] и выбор GUARD/SKEL живут именно тут. */
int pop_guard_vflip_load(void) __banked
{
static const char *const guard_v[GUARD_PAGES] = {
"GUARD\\g0_v.atl", "GUARD\\g1_v.atl", "GUARD\\g2_v.atl",
"GUARD\\g3_v.atl", "GUARD\\g4_v.atl"
};
static const char *const skel_v[GUARD_PAGES] = {
"SKEL\\g0_v.atl", "SKEL\\g1_v.atl", "SKEL\\g2_v.atl",
"SKEL\\g3_v.atl", 0
};
const char *const *names;
uint8_t tbl[ARC_GUARD_COUNT * POP_ARC_REC];
pop_arc_t arc;
int8_t type = pop_level_guard_type(pop_current_level);
uint8_t i;
int res = 0;
if (type < 0 || !g_ok) return 0; /* соперников на уровне нет */
names = (type == 2) ? skel_v : guard_v;
/* Зеркала есть ТОЛЬКО у обычного стража: зелье инверсии — уровень 9, а
* там tbl_guard_type = 0 (у скелета и Джафара своих *_v нет вовсе). */
if (type != 0 && type != 1) return 0;
if (pop_arc_open_id(&arc, POP_ARC_GUARD, tbl, ARC_GUARD_COUNT) < 0)
return -1;
for (i = 0; i < GUARD_PAGES; i++) {
if (names[i] == 0) break;
if (pop_vflip_add(&gp[i], names[i]) != 0) return -1;
atlas_t m;
if (pop_arc_atlas(&arc, tbl, (uint8_t)(ARC_GUARD_G0_V + i), &m) != 0 ||
pop_vflip_take(&gp[i], &m) != 0) {
res = -1;
break;
}
}
return 0;
pop_arc_close(&arc);
return res;
}
void pop_guard_free(void) __banked