feat(termtris): 80x24 cell-grid X11 renderer (6x13 cells)

This commit is contained in:
opencode.energia.arti.ee committed 2026-11-03 19:00:42 +00:00
1 parent a8041818bb
commit 264b53a6a0
8 files changed
+735

No files matched your search

Binary file not shown.
+85
View File
@@ -0,0 +1,85 @@
*****************************************************
TMS340 COFF Linker Version 5.01
*****************************************************
Tue Nov 03 19:00:04 2026
OUTPUT FILE NAME: <RENDER.out>
ENTRY POINT SYMBOL: "_start" address: 018a0000
MEMORY CONFIGURATION
name origin length attributes
-------- -------- --------- ----------
prog 018a0000 000020000 RWIX
ram 01a00000 000040000 RWIX
SECTION ALLOCATION MAP
output attributes/
section page origin length input sections
-------- ---- ---------- ---------- ----------------
.text 0 018a0000 00003af0
018a0000 00000150 ENTRY.obj (.text)
018a0150 00001280 RENDER.obj (.text)
018a13d0 00002510 X11.obj (.text)
018a38e0 00000210 XGFIX.obj (.text)
.cinit 0 018a3af0 000000a0
018a3af0 00000050 RENDER.obj (.cinit)
018a3b40 00000050 X11.obj (.cinit)
.bss 0 01a00000 0001d080 UNINITIALIZED
01a00000 00008000 ENTRY.obj (.bss)
01a08000 00000260 RENDER.obj (.bss)
01a08260 00012e20 X11.obj (.bss)
01a1b080 00002000 XGFIX.obj (.bss)
.data 0 018a0000 00000000 UNINITIALIZED
GLOBAL SYMBOLS
address name address name
-------- ---- -------- ----
01a00000 .bss 018a0000 .text
018a0000 .data 018a0000 .data
018a0000 .text 018a0000 edata
018a0370 _main 018a0000 _start
01a08000 _mark 018a0370 _main
018a0000 _start 018a13d0 _vt_root_window
01a1afa0 _term_clearscr 018a14b0 _vt_x11_init
01a1afe0 _term_cursor 018a1b60 _x11_clearscr
01a08220 _term_height 018a1d50 _x11_reset
01a1b020 _term_ibmchar 018a1d90 _x11_cursor
018a3870 _term_init 018a1df0 _x11_setcolor
018a2600 _term_putstr 018a1f10 _x11_setcursor
01a1af80 _term_reset 018a2160 _x11_ibmchar
01a1b000 _term_setcolor 018a2600 _term_putstr
01a1afc0 _term_setcursor 018a27a0 _vt_x11_flush
01a1b060 _term_type 018a3550 _vt_x11_term_init
01a08200 _term_width 018a3870 _term_init
01a1b040 _termenv 018a38e0 _vt_install_xg
01a1aee0 _vt_dpy 018a3af0 etext
01a1af20 _vt_gc 018a3af0 cinit
018a38e0 _vt_install_xg 01a00000 .bss
018a13d0 _vt_root_window 01a08000 _mark
01a1af00 _vt_win 01a08200 _term_width
01a1af60 _vt_x11_drain_events 01a08220 _term_height
018a27a0 _vt_x11_flush 01a1aee0 _vt_dpy
018a14b0 _vt_x11_init 01a1af00 _vt_win
01a1af40 _vt_x11_progress 01a1af20 _vt_gc
018a3550 _vt_x11_term_init 01a1af40 _vt_x11_progress
018a1b60 _x11_clearscr 01a1af60 _vt_x11_drain_events
018a1d90 _x11_cursor 01a1af80 _term_reset
018a2160 _x11_ibmchar 01a1afa0 _term_clearscr
018a1d50 _x11_reset 01a1afc0 _term_setcursor
018a1df0 _x11_setcolor 01a1afe0 _term_cursor
018a1f10 _x11_setcursor 01a1b000 _term_setcolor
018a3af0 cinit 01a1b020 _term_ibmchar
018a0000 edata 01a1b040 _termenv
01a1d080 end 01a1b060 _term_type
018a3af0 etext 01a1d080 end
[38 symbols]
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+190
View File
@@ -0,0 +1,190 @@
/*
* x11.c -- a small character-cell terminal that draws through the VT1200 ROM
* Xlib. The game's term_* callbacks feed an 80x24 grid; vt_x11_flush() paints
* only the dirty cells with XFillRectangle (background) + XDrawString (glyph).
*
* Geometry is the default GC's font, measured on the real server: 6x13 cells,
* baseline +10. Do not change the cell metrics without re-measuring.
*/
#include "xlib.h"
#include "x11.h"
#include "term.h"
#include "game.h"
#define COLS 80
#define ROWS 24
#define CELL_W 6
#define CELL_H 13
#define BASE_OFF 10
struct vt_cell {
unsigned short ch;
unsigned char fg, bg, dirty;
};
static struct vt_cell cells[ROWS * COLS];
static int cur_row, cur_col;
static int cur_fg, cur_bg;
void *vt_dpy;
vt_u32 vt_win;
vt_u32 vt_gc;
volatile vt_u32 vt_x11_progress;
int vt_x11_drain_events;
/* term.h's callback objects (term.c is not compiled for the VT1200). */
void (*term_reset)(void);
void (*term_clearscr)(void);
void (*term_setcursor)(int row, int col);
void (*term_cursor)(int show);
void (*term_setcolor)(int fg, int bg);
void (*term_ibmchar)(unsigned char c, unsigned char attr);
char *termenv;
int term_type = TERM_ANSI;
vt_u32 vt_root_window(void *dpy)
{
vt_u32 screen;
screen = *(vt_u32 *)((char *)dpy + 0x90);
return *(vt_u32 *)((char *)screen + 8);
}
int vt_x11_init(void)
{
if(!vt_install_xg())
return -1;
vt_dpy = VT_XOPEN("0::0");
if(!vt_dpy)
return -1;
vt_win = VT_XCREATE(vt_dpy, vt_root_window(vt_dpy), 80, 80, 480, 312, 2, 1, 0);
if(!vt_win)
return -1;
VT_XSELECT(vt_dpy, vt_win, 0x22000FUL);
vt_gc = VT_XGC(vt_dpy, vt_win, 0, 0);
if(!vt_gc)
return -1;
VT_XMAP(vt_dpy, vt_win);
VT_XSYNC(vt_dpy, 0);
return 0;
}
void x11_clearscr(void)
{
int i;
for(i = 0; i < ROWS * COLS; i++) {
cells[i].ch = ' ';
cells[i].fg = 0;
cells[i].bg = 0;
cells[i].dirty = 1;
}
cur_row = cur_col = 0;
}
void x11_reset(void)
{
x11_clearscr();
}
void x11_cursor(int show)
{
(void)show;
}
void x11_setcolor(int fg, int bg)
{
cur_fg = fg;
cur_bg = bg;
}
void x11_setcursor(int row, int col)
{
if(row < 0) row = 0;
if(row >= ROWS) row = ROWS - 1;
if(col < 0) col = 0;
if(col >= COLS) col = COLS - 1;
cur_row = row;
cur_col = col;
}
void x11_ibmchar(unsigned char c, unsigned char attr)
{
struct vt_cell *p;
if(cur_row < 0 || cur_row >= ROWS) return;
if(cur_col < 0 || cur_col >= COLS) return;
p = &cells[cur_row * COLS + cur_col];
p->ch = c;
p->fg = (attr >> 4) & 0xf;
p->bg = attr & 0xf;
p->dirty = 1;
cur_col++;
if(cur_col >= COLS) {
cur_col = 0;
cur_row++;
}
}
void term_putstr(const char *s, unsigned char attr)
{
while(*s) {
x11_ibmchar((unsigned char)*s++, attr);
}
}
void vt_x11_flush(void)
{
int i, row_dirty = 0, dirty_any = 0;
char one[2];
vt_x11_progress = 1;
for(i = 0; i < ROWS * COLS; i++) {
struct vt_cell *p = &cells[i];
vt_x11_progress = 0x100 + (vt_u32)i;
if(p->dirty) {
int x, y, bgpx, fgpx;
x = (i % COLS) * CELL_W;
y = (i / COLS) * CELL_H;
bgpx = p->bg == 0 ? 0 : 1;
fgpx = p->fg == 0 ? 0 : 1;
VT_XSETFG(vt_dpy, vt_gc, (vt_u32)bgpx);
VT_XFILL(vt_dpy, vt_win, vt_gc, x, y, CELL_W, CELL_H);
VT_XSETFG(vt_dpy, vt_gc, (vt_u32)fgpx);
one[0] = (char)p->ch;
one[1] = 0;
VT_XDRAW(vt_dpy, vt_win, vt_gc, x, y + BASE_OFF, one, 1);
p->dirty = 0;
row_dirty = 1;
dirty_any = 1;
}
if((i % COLS) == COLS - 1 && row_dirty) {
vt_u32 ev[24];
vt_x11_progress = 0x10000 + (vt_u32)(i / COLS);
VT_XSYNC(vt_dpy, 0);
if(vt_x11_drain_events) {
while(*(vt_u32 *)((char *)vt_dpy + 0x54) != 0) {
VT_XNEXT(vt_dpy, (void *)ev);
}
}
row_dirty = 0;
}
}
if(dirty_any) {
VT_XSYNC(vt_dpy, 0);
}
vt_x11_progress = 0xFFFFFFUL;
}
void vt_x11_term_init(void)
{
term_width = COLS;
term_height = ROWS;
term_reset = x11_reset;
term_clearscr = x11_clearscr;
term_setcursor = x11_setcursor;
term_cursor = x11_cursor;
term_setcolor = x11_setcolor;
term_ibmchar = x11_ibmchar;
}
void term_init(void)
{
vt_x11_term_init();
}
+25
View File
@@ -0,0 +1,25 @@
/*
* x11.h -- the VT1200 cell-grid X11 backend.
*/
#ifndef VT_X11_H_
#define VT_X11_H_
#include "xlib.h"
extern void *vt_dpy;
extern vt_u32 vt_win;
extern vt_u32 vt_gc;
extern int vt_x11_drain_events;
int vt_x11_init(void);
void vt_x11_flush(void);
void vt_x11_term_init(void);
void x11_reset(void);
void x11_clearscr(void);
void x11_setcursor(int row, int col);
void x11_cursor(int show);
void x11_setcolor(int fg, int bg);
void x11_ibmchar(unsigned char c, unsigned char attr);
#endif
+59
View File
@@ -0,0 +1,59 @@
/*
* render.c -- Task 3 acceptance: draw a border, a playfield outline and
* TERMTRIS through the cell-grid backend, then keep the window alive so the
* loader can snapshot it.
*/
#include "xlib.h"
#include "x11.h"
extern int vt_install_xg(void);
volatile vt_u32 mark[16];
int term_width, term_height;
static const char *word = "TERMTRIS";
static void put(int row, int col, const char *s, unsigned char attr)
{
x11_setcursor(row, col);
while(*s) x11_ibmchar((unsigned char)*s++, attr);
}
int main(void)
{
int r, c;
mark[0] = 0x524E4452UL; /* RNDR */
if(vt_x11_init() != 0) return 1;
mark[1] = (vt_u32)vt_dpy;
mark[2] = vt_win;
mark[3] = vt_gc;
mark[5] = (vt_u32)VT_XPENDING(vt_dpy);
x11_clearscr();
for(c = 0; c < 80; c++) {
x11_setcursor(0, c); x11_ibmchar('#', 0x70);
x11_setcursor(23, c); x11_ibmchar('#', 0x70);
}
for(r = 0; r < 24; r++) {
x11_setcursor(r, 0); x11_ibmchar('#', 0x70);
x11_setcursor(r, 79); x11_ibmchar('#', 0x70);
}
for(r = 2; r < 20; r++) {
for(c = 4; c <= 13; c++) {
int edge = (r == 2 || r == 19 || c == 4 || c == 13);
x11_setcursor(r, c);
x11_ibmchar(edge ? '#' : '.', edge ? 0x70 : 0x07);
}
}
put(1, 20, word, 0x70);
put(21, 20, "0123456789", 0x70);
put(22, 20, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0x70);
vt_x11_drain_events = 1;
vt_x11_flush();
mark[4] = 0x464C5553UL; /* FLUS */
for(;;) {
VT_SLEEP(100);
}
return 0;
}
+347
View File
@@ -0,0 +1,347 @@
-- loader.lua -- debugger-assisted COFF loader acceptance harness.
--
-- Boots the firmware with VT1200_SKIP_LEVEL1=1 (the documented divergence) to
-- the OS, then at frame LOADER_AT pokes:
-- * the loader payload at bit 0x01880000 (raw dwt/loader/build/loader.bin)
-- * a GSPLNK COFF image at LOADER_BUF (default bit 0x01900000)
-- * the loader descriptor at 0x01882C00
-- and sets PC = 0x01880000. It does not use the network, the rdebug service
-- or any firmware transport: this is the debugger-assisted milestone.
--
-- The loader itself copies the COFF sections to their linked bit addresses and
-- calls create_process(entry, "LOADER", 0x6000, 1, 0). This script only pokes
-- the input and reads the result; it never writes the app's code or markers.
--
-- Env:
-- LOADER_BIN loader payload (default dwt/loader/build/loader.bin)
-- LOADER_COFF app COFF (default dwt/loader/build/marker.coff)
-- LOADER_BUF COFF buffer bit address (default 0x01900000)
-- LOADER_AT injection frame (default 2500)
-- LOADER_END inspection/exit frame (default 2700)
-- LOADER_OUT evidence directory (default dwt/loader/evidence)
-- LOADER_SNAP 1 = write a PNG at LOADER_END (default 0)
-- LOADER_VERBOSE 1 = print the result block every 25 frames
local function hex(v, w) return string.format("%0" .. (w or 8) .. "X", v) end
local machine = manager.machine
local RENDER_MARK = tonumber(os.getenv("RENDER_MARK") or "0x01A08000")
local X11P = tonumber(os.getenv("X11_PROGRESS") or "0")
local cpu = machine.devices[':maincpu']
local dbg = machine.debugger
local prog = cpu.spaces['program']
if not dbg then
print("LOADER: debugger not enabled; pass -debug")
machine:exit()
return
end
local AT = tonumber(os.getenv("LOADER_AT") or "2500")
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
local BIN = os.getenv("LOADER_BIN") or "dwt/loader/build/loader.bin"
local COFF = os.getenv("LOADER_COFF") or "dwt/loader/build/marker.coff"
local BUF = tonumber(os.getenv("LOADER_BUF") or "0x01900000")
local OUT = os.getenv("LOADER_OUT") or "dwt/loader/evidence"
local SNAP = os.getenv("LOADER_SNAP") == "1"
local MODE = tonumber(os.getenv("LOADER_MODE") or "0")
local A8DESC = os.getenv("LOADER_A8_DESC") == "1"
local VERBOSE = os.getenv("LOADER_VERBOSE") == "1"
local LOADER_BASE = 0x01880000
local DESC = 0x01882C00
local RESULT = 0x01882D00
local RETURN_STUB = 0x01883100
local RETURN_SP = 0x01883110
local function rd(p) return io.open(p, "rb") end
local function slurp(p)
local f, err = rd(p)
if not f then error("cannot open " .. p .. ": " .. tostring(err)) end
local s = f:read("*a")
f:close()
return s
end
local function u16(s, o)
return s:byte(o + 1) + s:byte(o + 2) * 256
end
local function u32(s, o)
return s:byte(o + 1) + s:byte(o + 2) * 256
+ s:byte(o + 3) * 65536 + s:byte(o + 4) * 16777216
end
local function parse_coff(d)
local c = { data = d }
c.magic = u16(d, 0)
c.nscns = u16(d, 2)
c.opthdr = u16(d, 16)
c.sections = {}
if c.opthdr > 0 then
c.optmagic = u16(d, 20)
c.entry = u32(d, 20 + 16)
c.textstart = u32(d, 20 + 20)
c.datastart = u32(d, 20 + 24)
end
local base = 20 + c.opthdr
for i = 0, c.nscns - 1 do
local o = base + i * 40
local name = d:sub(o + 1, o + 8):gsub("%z", "")
c.sections[#c.sections + 1] = {
name = name,
paddr = u32(d, o + 8),
size = u32(d, o + 16), -- bits
scnptr = u32(d, o + 20),
flags = u32(d, o + 36),
}
end
return c
end
local function poke_bytes(bitaddr, data)
local n = math.floor((#data + 1) / 2)
for i = 0, n - 1 do
local lo = data:byte(i * 2 + 1) or 0
local hi = data:byte(i * 2 + 2) or 0
prog:write_u16(bitaddr + i * 16, lo + hi * 256)
end
return n
end
local function verify_bytes(bitaddr, data)
local n = math.floor((#data + 1) / 2)
local bad = 0
for i = 0, n - 1 do
local lo = data:byte(i * 2 + 1) or 0
local hi = data:byte(i * 2 + 2) or 0
if prog:read_u16(bitaddr + i * 16) ~= lo + hi * 256 then
bad = bad + 1
end
end
return n, bad
end
local function w32(a, v) prog:write_u32(a, v) end
local function r32(a)
local ok, v = pcall(function() return prog:read_u32(a) end)
if ok then return v end
return 0xDEADBEEF
end
local loader = slurp(BIN)
local coff = slurp(COFF)
local app = parse_coff(coff)
print(string.format("LOADER: payload %s (%d B) -> bit %s", BIN, #loader, hex(LOADER_BASE)))
print(string.format("LOADER: COFF %s (%d B) -> bit %s", COFF, #coff, hex(BUF)))
print(string.format("LOADER: file magic=%04X nscns=%d opthdr=%d optmagic=%s entry=%s textstart=%s",
app.magic, app.nscns, app.opthdr,
app.optmagic and string.format("%04X", app.optmagic) or "none",
hex(app.entry or 0), hex(app.textstart or 0)))
for _, s in ipairs(app.sections) do
print(string.format("LOADER: sec %-8s paddr=%s size=%d bits (%d B) scnptr=%#x flags=%#x",
s.name, hex(s.paddr), s.size, math.floor(s.size / 8), s.scnptr, s.flags))
end
local TRACE = os.getenv("LOADER_TRACE") == "1"
local frames = 0
local injected = false
local done = false
local last_count = -1
local function status_name(v)
local t = {
[0] = "OK",
[1] = "bad descriptor",
[2] = "truncated COFF / section headers",
[3] = "bad COFF magic",
[4] = "section size not whole words",
[5] = "target outside RAM",
[6] = "target overlaps loader page or COFF buffer",
[7] = "relocation records present (unsupported)",
[8] = "target not in a free-list block",
[9] = "no entry point",
}
return t[v] or ("error " .. tostring(v))
end
local function result_dump()
local st = r32(RESULT + 0x000)
print(string.format(
"LOADER: result status=%d (%s) entry=%s text=%s/%d bits data=%s/%d bits bss=%s/%d bits relocs=%d pcb=%s nscns=%d len=%d",
st, status_name(st), hex(r32(RESULT + 0x020)),
hex(r32(RESULT + 0x040)), r32(RESULT + 0x060),
hex(r32(RESULT + 0x080)), r32(RESULT + 0x0A0),
hex(r32(RESULT + 0x0C0)), r32(RESULT + 0x0E0),
r32(RESULT + 0x100), hex(r32(RESULT + 0x120)),
r32(RESULT + 0x140), r32(RESULT + 0x160)))
end
local function inject()
-- 1. loader itself
local n, bad = 0, 0
poke_bytes(LOADER_BASE, loader)
n, bad = verify_bytes(LOADER_BASE, loader)
print(string.format("LOADER: loader poked %d words, readback %d bad at %s",
n, bad, hex(LOADER_BASE)))
-- 2. COFF image
poke_bytes(BUF, coff)
n, bad = verify_bytes(BUF, coff)
print(string.format("LOADER: COFF poked %d words, readback %d bad at %s",
n, bad, hex(BUF)))
-- 3. pre-fill every .bss section with A5A5, so a loaded zero is meaningful
for _, s in ipairs(app.sections) do
if s.flags & 0x80 ~= 0 then
for i = 0, math.floor(s.size / 16) - 1 do
prog:write_u16(s.paddr + i * 16, 0xA5A5)
end
print(string.format("LOADER: pre-filled .bss %s..%s with A5A5",
hex(s.paddr), hex(s.paddr + s.size)))
end
end
-- 4. descriptor: magic "LD" "R1", buf, len, mode=0 (spin)
w32(DESC + 0x00, 0)
prog:write_u16(DESC + 0x00, 0x4C44)
prog:write_u16(DESC + 0x10, 0x5231)
w32(DESC + 0x20, BUF)
w32(DESC + 0x40, #coff)
w32(DESC + 0x60, MODE)
print(string.format("LOADER: descriptor @%s magic=%04X/%04X buf=%s len=%d mode=%d",
hex(DESC), prog:read_u16(DESC + 0x00), prog:read_u16(DESC + 0x10),
hex(r32(DESC + 0x20)), r32(DESC + 0x40), r32(DESC + 0x60)))
-- 5. return stub. mode 0 ignores it (the loader jumps to ORIG_PC
-- itself); mode 1 returns here, and the stub resumes the interrupted
-- PC so the caller yields and the scheduler can run the child.
local orig_pc = cpu.state['PC'].value
if MODE == 1 then
prog:write_u16(RETURN_STUB, 0xC080) -- jauc <orig_pc>
prog:write_u16(RETURN_STUB + 0x10, orig_pc & 0xFFFF)
prog:write_u16(RETURN_STUB + 0x20, math.floor(orig_pc / 0x10000) & 0xFFFF)
else
prog:write_u16(RETURN_STUB, 0xC0FF) -- jruc self
end
-- 6. enter the loader. A8=0 selects the fixed descriptor; SP points at
-- the return slot so mode=1 would return to the spin stub.
-- Leave SP alone for mode 0: the loader pushes onto the current
-- process stack, then restores it and resumes at ORIG_PC. (mode 1
-- would use the return stub.)
w32(0x01882A20, orig_pc) -- LD_ORIG_PC
print(string.format("LOADER: will resume at %s after create_process", hex(orig_pc)))
cpu.state['A8'].value = A8DESC and DESC or 0
if MODE == 1 then
-- Simulate a CALLA from the current process: push the return
-- address on the *firmware* stack (a real transport would do this).
local sp = cpu.state['SP'].value - 0x20
w32(sp, RETURN_STUB)
cpu.state['SP'].value = sp
end
cpu.state['PC'].value = LOADER_BASE
injected = true
print(string.format("LOADER: frame %d: entered loader at %s (caller SP=%s)",
frames, hex(LOADER_BASE), hex(cpu.state['SP'].value)))
end
local function inspect()
result_dump()
-- Verify the placement from RAM: .text/.data must equal the file bytes.
for _, s in ipairs(app.sections) do
if s.scnptr ~= 0 and s.size > 0 then
local nb = math.floor(s.size / 8)
local bytes = coff:sub(s.scnptr + 1, s.scnptr + nb)
local _, bad = verify_bytes(s.paddr, bytes)
print(string.format("LOADER: placed %s at %s: %d bytes, %d differing",
s.name, hex(s.paddr), nb, bad))
end
end
local base = COFF:match("([^/]+)$") or COFF
if base:find("marker") then
print(string.format("LOADER: marker app: start=%s data=%s data2=%s bss=%s count=%d",
hex(r32(0x018E0000)), hex(r32(0x018E0020)), hex(r32(0x018E0040)),
hex(r32(0x018E0060)), r32(0x018E0080)))
elseif base:find("xhello") then
print(string.format("LOADER: xhello: stage=%d strlen=%d display=%s root=%s window=%s sync=%s",
r32(0x01890140), r32(0x01890160), hex(r32(0x01890100)),
hex(r32(0x01890110)), hex(r32(0x01890120)), hex(r32(0x01890130))))
end
if base:find("render") then
print(string.format("RENDER: magic=%08X dpy=%08X win=%08X gc=%08X flus=%08X progress=%08X",
r32(RENDER_MARK), r32(RENDER_MARK + 0x20), r32(RENDER_MARK + 0x40),
r32(RENDER_MARK + 0x60), r32(RENDER_MARK + 0x80),
X11P ~= 0 and r32(X11P) or 0))
print(string.format("RENDER: pending=%d", r32(RENDER_MARK + 0xA0)))
end
print(string.format("LOADER: PC at inspection = %s", hex(cpu.state['PC'].value)))
end
emu.register_frame_done(function()
frames = frames + 1
if frames == AT and not injected then
inject()
if TRACE then dbg:command("trace /tmp/opencode/ld1.tr") end
end
if injected and TRACE and frames == AT + 1 then
dbg:command("trace off")
print("LOADER: trace written to /tmp/opencode/ld.tr")
end
if injected and not done then
if TRACE and frames <= AT + 40 then
print(string.format("LOADER: trace f=%d PC=%s SP=%s A14=%s status=%s entry=%s",
frames, hex(cpu.state['PC'].value), hex(cpu.state['SP'].value),
hex(cpu.state['A14'].value), hex(r32(RESULT + 0x000)),
hex(r32(RESULT + 0x020))))
end
local st = r32(RESULT + 0x000)
-- status 0 means OK; nonzero is final too once parsing has begun
if st ~= 0xFFFFFFFF and not done then
-- The loader writes 0 on success. A nonzero status is final
-- only after it has been written once (it is 0 from power-on,
-- so require the loader to have run: PC no longer at entry or
-- the app counter moved).
end
if VERBOSE and frames % 25 == 0 then
result_dump()
end
if base_contains_marker then
local c = r32(0x018E0080)
if c ~= last_count then
if last_count ~= -1 then
print(string.format("LOADER: marker counter %d -> %d (child running)",
last_count, c))
end
last_count = c
end
end
end
if frames >= ENDF then
if injected then
done = true
inspect()
if SNAP then
local screen = nil
for _, s in pairs(machine.screens) do screen = s break end
if screen then
local out = OUT .. "/loader_frame" .. frames .. ".png"
local ok, err = pcall(function() return screen:snapshot(out) end)
print(string.format("LOADER: snapshot %s ok=%s %s",
out, tostring(ok), tostring(err or "")))
end
end
end
machine:exit()
end
end, "vt1200-loader")
-- (Lua has no forward use of a name defined later; set a small flag here.)
base_contains_marker = (COFF:match("([^/]+)$") or ""):find("marker") ~= nil
dbg.visible_cpu = cpu
dbg.execution_state = 'run'
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
# render.sh -- Task 3 acceptance: paint the cell-grid test pattern, then
# snapshot the window. The PNG is the evidence.
set -eu
DIR=$(cd "$(dirname "$0")" && pwd)
APP=$(cd "$DIR/.." && pwd)
ROOT=$(cd "$APP/../.." && pwd)
M=${MAME:-/home/arti/code/k-space/mame0289/vt1200}
EV="$APP/evidence"
LOG="$APP/build/render.log"
mkdir -p "$EV" "$APP/build"
"$APP/build.sh" "$DIR/render.c" "$APP/src/vt1200/x11.c" "$APP/src/vt1200/xgfix.c" >"$APP/build/render-build.log" 2>&1
rm -f "$EV/loader_frame3400.png"
MARK=$(awk '/_mark/ {print $1; exit}' "$APP/build/render.map")
PROG=$(awk '/_vt_x11_progress/ {print $1; exit}' "$APP/build/render.map")
VT1200_SKIP_LEVEL1=1 LOADER_SNAP=1 RENDER_MARK="0x$MARK" X11_PROGRESS="0x$PROG" \
LOADER_AT=2500 LOADER_END=3400 \
LOADER_BIN="$ROOT/dwt/loader/build/loader.bin" \
LOADER_COFF="$APP/build/render.coff" \
LOADER_OUT="$EV" \
"$M" vt1200 -rompath "$ROOT/harness/mame/roms" \
-video none -sound none -nothrottle -skip_gameinfo \
-debug -debugger none \
-autoboot_script "$DIR/render.lua" -autoboot_delay 0 \
>"$LOG" 2>&1 || true
grep -E 'LOADER: snapshot|LOADER: result' "$LOG" | tail -4
[ -s "$EV/loader_frame3400.png" ] || { echo "render.sh: no snapshot (see $LOG)" >&2; exit 1; }
cp "$EV/loader_frame3400.png" "$EV/render_frame3400.png"
echo "render: $EV/render_frame3400.png"