tests/w0page + docs §9в: атласы в страницах W0 — probe-тест (все PASS)
Схема: атлас в EMM-странице, на время sprite_update страница
подключается в W0. Все прерывания исполняют 0x38 текущей страницы W0
(IM1 напрямую, IM2-трамплин чейнит jp 0x0038) — защита: стаб в первых
0x100 байтах страницы (0x38: JP на W2-хелпер: свап на страницу DSS →
честный 0x38 → restore спрайт-страницы; 0x66: RETN).
Проверено в dev-MAME (tests/w0page, все PASS):
P1 ESTEX READ в W3-замапленную страницу;
P3 IM1: ~5 c busy-цикла с EI при странице в W0 — жив, сентинел цел;
P4 IM2: 100 кадровых прерываний посчитаны при подключенной странице
(тракт трамплин→чейн→стаб→DSS→restore);
P5 accel-блит src=0x0100 (W0) — пиксели верны по ОЗУ-копии.
Формат .atl в §3.1 дополнен: атлас ≤ 16К−0x100 (одна страница, один
файл), вариант II — 0x100-заголовок, файл-офсет == офсет страницы ==
W0-адрес, загрузка = read() целиком + патч стаба. На железе
перепроверить.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,4 +51,5 @@ stest2 3644
|
|||||||
strtest 1340
|
strtest 1340
|
||||||
text_palette 5024
|
text_palette 5024
|
||||||
timedir 5454
|
timedir 5454
|
||||||
|
w0page 8831
|
||||||
winrest 4459
|
winrest 4459
|
||||||
|
|||||||
|
@@ -195,10 +195,27 @@ sy=j*fh. (w/h ленты при этом задублированы: вывод
|
|||||||
построению: ленты разных размеров просто конкатенируются. Пример
|
построению: ленты разных размеров просто конкатенируются. Пример
|
||||||
(два спрайта 16×16 на 8 и 12 вариаций + один 24×24 на 4): шапка 8 +
|
(два спрайта 16×16 на 8 и 12 вариаций + один 24×24 на 4): шапка 8 +
|
||||||
каталог 24 + (4+2048) + (4+3072) + (4+2304) = 7468 байт, паддинг 0.
|
каталог 24 + (4+2048) + (4+3072) + (4+2304) = 7468 байт, паддинг 0.
|
||||||
Ограничение u16-offset: файл ≤ 64 КБ (для больших — версия 'SPA2' с
|
Привязка к W0-страницам (решение 2026-07-13, см. §9в): один атлас =
|
||||||
u24/u32, пока не нужна). Загрузчик — тонкий хелпер (fread целиком +
|
одна EMM-страница = один файл. Размер данных атласа ограничен
|
||||||
арифметика каталога) + Python-упаковщик (PNG → .atl) в toolchain;
|
16К − 0x100 (страница минус резерв ISR-стаба); если данных больше —
|
||||||
делать по потребности первого реального приложения.
|
следующий атлас в ОТДЕЛЬНОМ файле на своей странице (sprite_t.page их
|
||||||
|
различает). Два варианта офсетов в каталоге:
|
||||||
|
|
||||||
|
I. offset = адрес в W0 (0x0100-based): каталог сразу содержит
|
||||||
|
готовые указатели, данные в файле идут после каталога впритык.
|
||||||
|
II. файл несёт 0x100-байтовый заголовок, so that файл-офсет ==
|
||||||
|
офсет в странице == W0-адрес: загрузчик читает файл ЦЕЛИКОМ в
|
||||||
|
offset 0 страницы и лишь патчит стаб (3 байта JP в 0x38, RETN в
|
||||||
|
0x66). Раскладка заголовка: 0x00-0x07 магия+count,
|
||||||
|
0x08-0x37 резерв, 0x38-0x3A и 0x66-0x67 — место под патч стаба,
|
||||||
|
0x68-0xFF каталог (19 записей × 8 Б). Файл ≤ 16384 Б ровно.
|
||||||
|
|
||||||
|
Рекомендуется II: загрузка = один read() страницы, замапленной в W3
|
||||||
|
(приём проверен в mdview2) + патч 5 байт; офсеты совпадают в файле,
|
||||||
|
странице и W0 — нечего пересчитывать. Вариант I оставляет атласам
|
||||||
|
возможность жить в обычной памяти (malloc-буфер) без страницы.
|
||||||
|
Python-упаковщик (PNG → .atl) в toolchain и загрузчик — делать по
|
||||||
|
потребности первого реального приложения.
|
||||||
|
|
||||||
## 4. Внутренности
|
## 4. Внутренности
|
||||||
|
|
||||||
@@ -648,6 +665,41 @@ noclip доехал до приложения. С vsync-капом оба ва
|
|||||||
ВНИМАНИЕ: noclip требует гарантии приложения, что спрайты целиком на
|
ВНИМАНИЕ: noclip требует гарантии приложения, что спрайты целиком на
|
||||||
экране — выход за край портит соседнюю память (tests/spriteclip).
|
экране — выход за край портит соседнюю память (tests/spriteclip).
|
||||||
|
|
||||||
|
## 9в. Атласы в страницах W0 (дизайн 2026-07-13; probe-тест PASS)
|
||||||
|
|
||||||
|
Атласы грузятся в EMM-страницы (mem_alloc_pages) и на время
|
||||||
|
sprite_update подключаются в W0 (OUT 0x82) — W2-heap свободен от
|
||||||
|
спрайтов, объём = EMM (~3.4 МБ). Загрузка — read() в страницу,
|
||||||
|
замапленную в W3 (приём mdview2; вне рендера W3 свободна).
|
||||||
|
|
||||||
|
ОПАСНОСТЬ: все прерывания исполняют 0x38 текущей страницы W0 — IM1
|
||||||
|
напрямую, наш IM2-трамплин чейнит jp 0x0038 (libc/irq/_irq_tramp.c);
|
||||||
|
не ходит в W0 только личный CBL-путь. ЗАЩИТА — стаб на каждой
|
||||||
|
странице (первые 0x100 байт зарезервированы):
|
||||||
|
0x38: JP <w0_isr_stub в W2>; 0x66: RETN (NMI).
|
||||||
|
Стаб: свап W0 на страницу ядра DSS (снята со старта IN A,(0x82)) →
|
||||||
|
подложить возврат на стек → jp 0x0038 (честный обработчик, EI/RETI) →
|
||||||
|
хвост: ВОССТАНОВИТЬ спрайт-страницу (_w0_cur) → ret. Restore
|
||||||
|
обязателен (закрывает EI-щель между out(0x82) движка и DI leaf'а);
|
||||||
|
один стаб покрывает IM1 и IM2-чейн; вложенное прерывание в хвосте
|
||||||
|
безопасно (W0 в этот момент = DSS, стаб не реентерится).
|
||||||
|
|
||||||
|
ПРАВИЛО: пока спрайт-страница в W0 — НИКАКИХ ESTEX/BIOS (RST-диспетчер
|
||||||
|
живёт на странице DSS). Движок: пролог/эпилог sprite_update
|
||||||
|
сохраняет/возвращает страницу DSS; sprite_t получит поле page.
|
||||||
|
|
||||||
|
Probe-тест tests/w0page (MAME dev, 2026-07-13, ВСЕ PASS):
|
||||||
|
P1 ESTEX READ в W3-замапленную страницу;
|
||||||
|
P3 IM1: busy-цикл ~5 c с EI при странице в W0 — жив, сентинел цел;
|
||||||
|
P4 IM2 (irq_install): 100 кадровых прерываний посчитаны при
|
||||||
|
подключенной странице (полный тракт трамплин→чейн→стаб→DSS→
|
||||||
|
restore), сентинел цел;
|
||||||
|
P5 accel-блит src = 0x0100 (W0) банком 0x50 — пиксели верны по
|
||||||
|
ОЗУ-копии (getpixel).
|
||||||
|
На железе перепроверить вместе с остальным. Альтернатива без стабов —
|
||||||
|
W1, но только tiny (в small/big/huge в W1 код) — отвергнута в пользу
|
||||||
|
универсального W0.
|
||||||
|
|
||||||
## 10. Риски / что проверить артефактом (Фаза 0)
|
## 10. Риски / что проверить артефактом (Фаза 0)
|
||||||
|
|
||||||
Статус 2026-07-11: пп. 1–3 закрыты для MAME 0.283 (tests/gfxbanks,
|
Статус 2026-07-11: пп. 1–3 закрыты для MAME 0.283 (tests/gfxbanks,
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||||
|
EXAMPLE := w0page
|
||||||
|
EXTRA_FLAGS ?= --gfx 256 --memory tiny
|
||||||
|
include $(PROJ_ROOT)/app.mk
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* w0page — проверки схемы «спрайт-данные в странице W0»
|
||||||
|
* (docs/sprite-api-design.md §3.1/§9в, обсуждение 2026-07-13).
|
||||||
|
*
|
||||||
|
* Идея: атлас грузится в EMM-страницу; на время sprite_update страница
|
||||||
|
* подключается в W0 (0x0000-0x3FFF). Опасность: ВСЕ прерывания приходят
|
||||||
|
* на 0x38 текущей страницы W0 (IM1 напрямую; наш IM2-трамплин чейнит
|
||||||
|
* jp 0x0038). Защита: в первых 0x100 байтах страницы — стаб:
|
||||||
|
* 0x38: JP w0_isr_stub (W2), 0x66: RETN. Стаб свапит W0 на страницу
|
||||||
|
* ядра DSS, отдаёт прерывание настоящему 0x38 (подложив возврат) и
|
||||||
|
* ВОССТАНАВЛИВАЕТ спрайт-страницу.
|
||||||
|
*
|
||||||
|
* Стадии (Enter между; PASS/FAIL печатается ПОСЛЕ восстановления W0):
|
||||||
|
* P1 ESTEX READ в страницу, замапленную в W3 (приём mdview2).
|
||||||
|
* P2 подготовка страницы: стаб + картинка 16×16 в 0x100 + сентинел.
|
||||||
|
* P3 IM1: страница в W0, busy-цикл ~2-3 c с EI — выжить, сентинел
|
||||||
|
* через W0 верен на каждой итерации (restore работает).
|
||||||
|
* P4 IM2 (irq_install): страница в W0, дождаться 100 кадровых
|
||||||
|
* прерываний — счётчик растёт => полный тракт tramp→chain→стаб→
|
||||||
|
* DSS→restore живёт; сентинел верен.
|
||||||
|
* P5 графика: gfx_blit картинки ИЗ АДРЕСА 0x0100 (accel-burst
|
||||||
|
* src в W0) банком 0x50; self-check getpixel по ОЗУ-копии.
|
||||||
|
*
|
||||||
|
* Сборка: tiny (irq_install требует код/данные в W2) + --gfx 256.
|
||||||
|
* Запуск через MAME-мост: press_key посимвольно + Enter'ы.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <graphics.h>
|
||||||
|
#include <gfx.h>
|
||||||
|
#include <irq.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
#include <sprinter_mem.h>
|
||||||
|
|
||||||
|
#define PAT_LEN 1024 /* P1: объём проверки чтения */
|
||||||
|
#define SENT_OFF 0x3F00u /* сентинел в конце страницы */
|
||||||
|
#define SENT_VAL 0xA5
|
||||||
|
#define IMG_OFF 0x0100u /* данные атласа — после резерва стаба */
|
||||||
|
#define IMG_W 16
|
||||||
|
#define IMG_H 16
|
||||||
|
|
||||||
|
static uint8_t w2buf[PAT_LEN]; /* эталон в W2 */
|
||||||
|
static uint8_t blk, phys; /* EMM-блок и физ. страница */
|
||||||
|
static uint8_t dss_page; /* страница ядра DSS (снята со старта) */
|
||||||
|
|
||||||
|
/* Для стаба (читается из прерывания — только W2-данные). */
|
||||||
|
static uint8_t w0_cur;
|
||||||
|
|
||||||
|
/* ISR-стаб (живёт в W2 = _CODE tiny): вызывается JP'ом с 0x38
|
||||||
|
* спрайт-страницы. Свапит W0 на ядро DSS, отдаёт прерывание честному
|
||||||
|
* 0x38 (адрес возврата подложен на стек — DSS завершает EI/RETI и
|
||||||
|
* возвращается в хвост), затем возвращает спрайт-страницу. Вложенное
|
||||||
|
* прерывание в хвосте (EI уже сделан DSS) безопасно: W0 в этот момент
|
||||||
|
* = DSS, штатный путь, стаб не реентерится. */
|
||||||
|
static void w0_isr_stub(void) __naked
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
push af
|
||||||
|
ld a, (_dss_page)
|
||||||
|
out (#0x82), a
|
||||||
|
pop af
|
||||||
|
push hl
|
||||||
|
ld hl, #w0_isr_ret
|
||||||
|
ex (sp), hl ; вершина стека = возврат, HL цел
|
||||||
|
jp 0x0038 ; честный обработчик DSS (EI/RETI)
|
||||||
|
w0_isr_ret:
|
||||||
|
push af
|
||||||
|
ld a, (_w0_cur)
|
||||||
|
out (#0x82), a
|
||||||
|
pop af
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* W0 := A под DI (щель между out и прерыванием закрывается стабом,
|
||||||
|
* но сам свап делаем атомарно). */
|
||||||
|
static void w0_set(uint8_t page) __naked
|
||||||
|
{
|
||||||
|
(void)page;
|
||||||
|
__asm
|
||||||
|
di
|
||||||
|
out (#0x82), a
|
||||||
|
ei
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void w0_map(void) { w0_cur = phys; w0_set(phys); }
|
||||||
|
static void w0_unmap(void) { w0_set(dss_page); }
|
||||||
|
|
||||||
|
/* ---- P4: счётчик кадровых прерываний -------------------------------- */
|
||||||
|
static volatile uint8_t irq_ticks;
|
||||||
|
static void tick_isr(void) { irq_ticks++; }
|
||||||
|
|
||||||
|
/* ---- P5: тестовая картинка ------------------------------------------ *
|
||||||
|
* Рамка RED, тело LIGHTCYAN, центр 2×2 YELLOW. */
|
||||||
|
static void build_image_via_w3(void)
|
||||||
|
{
|
||||||
|
uint8_t *p = (uint8_t *)0xC000 + IMG_OFF;
|
||||||
|
int x, y;
|
||||||
|
|
||||||
|
p[0] = IMG_W; p[1] = 0; p[2] = IMG_H; p[3] = 0;
|
||||||
|
p += 4;
|
||||||
|
for (y = 0; y < IMG_H; y++)
|
||||||
|
for (x = 0; x < IMG_W; x++) {
|
||||||
|
uint8_t c = LIGHTCYAN;
|
||||||
|
if (x == 0 || y == 0 || x == IMG_W - 1 || y == IMG_H - 1)
|
||||||
|
c = RED;
|
||||||
|
if ((x == 7 || x == 8) && (y == 7 || y == 8))
|
||||||
|
c = YELLOW;
|
||||||
|
*p++ = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
uint8_t saved_w3;
|
||||||
|
int fd, i, n;
|
||||||
|
uint8_t ok;
|
||||||
|
|
||||||
|
puts("W0PAGE: sprite data in W0 window probes");
|
||||||
|
dss_page = _io_page_w0; /* IN A,(0x82): страница ядра */
|
||||||
|
printf("dss page = %u\n", dss_page);
|
||||||
|
|
||||||
|
blk = mem_alloc_pages(1);
|
||||||
|
if (!blk) { puts("alloc FAIL"); return 1; }
|
||||||
|
phys = mem_get_page(blk, 0);
|
||||||
|
printf("emm page = %u\n", phys);
|
||||||
|
|
||||||
|
/* ---- P1: ESTEX READ в W3-замапленную страницу ------------------- */
|
||||||
|
for (i = 0; i < PAT_LEN; i++) w2buf[i] = (uint8_t)(i * 7 + 3);
|
||||||
|
fd = creat("w0page.dat", 0);
|
||||||
|
if (fd < 0) { puts("creat FAIL"); return 1; }
|
||||||
|
write(fd, w2buf, PAT_LEN);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
fd = open("w0page.dat", O_RDONLY);
|
||||||
|
saved_w3 = _io_page_w3;
|
||||||
|
sprinter_page_w3(phys);
|
||||||
|
n = read(fd, (void *)0xC000, PAT_LEN); /* буфер — в W3! */
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
ok = (n == PAT_LEN);
|
||||||
|
if (ok)
|
||||||
|
for (i = 0; i < PAT_LEN; i++)
|
||||||
|
if (*((uint8_t *)0xC000 + i) != w2buf[i]) { ok = 0; break; }
|
||||||
|
|
||||||
|
/* ---- P2: стаб + картинка + сентинел (страница ещё в W3) --------- */
|
||||||
|
{
|
||||||
|
uint8_t *pg = (uint8_t *)0xC000;
|
||||||
|
uint16_t stub = (uint16_t)&w0_isr_stub;
|
||||||
|
|
||||||
|
pg[0x38] = 0xC3; /* JP w0_isr_stub */
|
||||||
|
pg[0x39] = (uint8_t)(stub & 0xFF);
|
||||||
|
pg[0x3A] = (uint8_t)(stub >> 8);
|
||||||
|
pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN (NMI) */
|
||||||
|
pg[SENT_OFF] = SENT_VAL;
|
||||||
|
build_image_via_w3();
|
||||||
|
}
|
||||||
|
sprinter_page_w3(saved_w3);
|
||||||
|
printf("P1 read->W3: %s\n", ok ? "PASS" : "FAIL");
|
||||||
|
puts("P2 page prepared (stub+img)");
|
||||||
|
(void)getchar();
|
||||||
|
|
||||||
|
/* ---- P3: IM1, страница в W0, busy-цикл с EI --------------------- *
|
||||||
|
* Внутри окна НИКАКИХ ESTEX/BIOS. ~50-100 прерываний IM1 пройдут
|
||||||
|
* через стаб; сентинел через W0 обязан оставаться верным. */
|
||||||
|
ok = 1;
|
||||||
|
w0_map();
|
||||||
|
{
|
||||||
|
uint8_t outer;
|
||||||
|
uint16_t inner;
|
||||||
|
for (outer = 0; outer < 60; outer++)
|
||||||
|
for (inner = 0; inner < 40000u; inner++)
|
||||||
|
if (*(volatile uint8_t *)SENT_OFF != SENT_VAL) ok = 0;
|
||||||
|
}
|
||||||
|
w0_unmap();
|
||||||
|
printf("P3 IM1 in W0: %s\n", ok ? "PASS" : "FAIL");
|
||||||
|
(void)getchar();
|
||||||
|
|
||||||
|
/* ---- P4: IM2, страница в W0, счёт кадровых прерываний ----------- *
|
||||||
|
* Бюджет ожидания: 100 кадров = 2 с; guard-циклов с запасом ~10 с
|
||||||
|
* (65000×150Т ≈ 0.5 с — прошлый одиночный цикл истекал раньше
|
||||||
|
* срока и давал ложный FAIL). */
|
||||||
|
ok = 0;
|
||||||
|
{
|
||||||
|
uint8_t got = 0, sent_ok = 1, outer;
|
||||||
|
|
||||||
|
if (irq_install(tick_isr) == 0) {
|
||||||
|
uint8_t start;
|
||||||
|
uint16_t guard;
|
||||||
|
|
||||||
|
w0_map();
|
||||||
|
start = irq_ticks;
|
||||||
|
for (outer = 0; outer < 20 && !ok && sent_ok; outer++)
|
||||||
|
for (guard = 0; guard < 60000u; guard++) {
|
||||||
|
got = (uint8_t)(irq_ticks - start);
|
||||||
|
if (got >= 100) { ok = 1; break; }
|
||||||
|
if (*(volatile uint8_t *)SENT_OFF != SENT_VAL) {
|
||||||
|
sent_ok = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w0_unmap();
|
||||||
|
irq_remove();
|
||||||
|
} else
|
||||||
|
puts("irq_install failed");
|
||||||
|
printf("P4 IM2 in W0: %s (ticks=%u sent=%s)\n",
|
||||||
|
ok ? "PASS" : "FAIL", got, sent_ok ? "ok" : "BAD");
|
||||||
|
}
|
||||||
|
puts("Enter -> gfx");
|
||||||
|
(void)getchar();
|
||||||
|
|
||||||
|
/* ---- P5: accel-блит из W0-адреса -------------------------------- */
|
||||||
|
initgraph();
|
||||||
|
cleardevice();
|
||||||
|
setfillstyle(SOLID_FILL, GREEN);
|
||||||
|
bar(140, 90, 175, 125); /* фон вокруг картинки */
|
||||||
|
|
||||||
|
w0_map();
|
||||||
|
gfx_blit(150, 100, (const void *)IMG_OFF); /* src = 0x0100 (W0)! */
|
||||||
|
w0_unmap();
|
||||||
|
|
||||||
|
ok = (getpixel(150, 100) == RED) /* рамка */
|
||||||
|
&& (getpixel(157, 107) == YELLOW) /* центр */
|
||||||
|
&& (getpixel(152, 103) == LIGHTCYAN) /* тело */
|
||||||
|
&& (getpixel(148, 100) == GREEN); /* фон цел */
|
||||||
|
|
||||||
|
setcolor(WHITE);
|
||||||
|
outtextxy(4, 4, "W0PAGE gfx: blit from 0x0100 (W0)");
|
||||||
|
outtextxy(4, 200, ok ? "P5 blit from W0: PASS"
|
||||||
|
: "P5 blit from W0: FAIL");
|
||||||
|
outtextxy(4, 210, "Enter to exit");
|
||||||
|
(void)getchar();
|
||||||
|
|
||||||
|
closegraph();
|
||||||
|
printf("P5 gfx blit: %s\n", ok ? "PASS" : "FAIL");
|
||||||
|
puts("done");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user