From 264b53a6a08dcda85415667f015343c02af58590 Mon Sep 17 00:00:00 2001 From: "opencode.energia.arti.ee" Date: Tue, 3 Nov 2026 19:00:42 +0000 Subject: [PATCH] feat(termtris): 80x24 cell-grid X11 renderer (6x13 cells) --- apps/termtris/build/render.coff | Bin 0 -> 3788 bytes apps/termtris/build/render.map | 85 +++++ apps/termtris/evidence/render_frame3400.png | Bin 0 -> 19845 bytes apps/termtris/src/vt1200/x11.c | 190 +++++++++++ apps/termtris/src/vt1200/x11.h | 25 ++ apps/termtris/tests/render.c | 59 ++++ apps/termtris/tests/render.lua | 347 ++++++++++++++++++++ apps/termtris/tests/render.sh | 29 ++ 8 files changed, 735 insertions(+) create mode 100644 apps/termtris/build/render.coff create mode 100644 apps/termtris/build/render.map create mode 100644 apps/termtris/evidence/render_frame3400.png create mode 100644 apps/termtris/src/vt1200/x11.c create mode 100644 apps/termtris/src/vt1200/x11.h create mode 100644 apps/termtris/tests/render.c create mode 100644 apps/termtris/tests/render.lua create mode 100755 apps/termtris/tests/render.sh diff --git a/apps/termtris/build/render.coff b/apps/termtris/build/render.coff new file mode 100644 index 0000000000000000000000000000000000000000..e00108eea8a5beaf981a4766b570c1bd1a3f4ed0 GIT binary patch literal 3788 zcmbVPe@t7~6~52lnOoP??J~&;?dG(2j$va-)@}*ihMVF*h+pCo+GRf^n}7!{#317& zRN5q$33-^Ui9l7GsZu==hG-Iu)*=5j&6+?oCX`H_kQ!~0|6#;yW3g$OHf`(MckZ+C zdu-9xJ;{%A&yVk%d+vAdyIf+G?B{R(;l(3WjI}Yw9%WBRRnk3as}Tb{v0D<}3B1%_ zLo_%X6&7_&ez(N9{IXXv*3cCShv2x?m;hEnO2XWL9NNc?LBpA$Ayt<|j-;gK(|Vx> zBmBBMqn$!sszvWZBRBm_1x`4ta-LIOpRxYc%52?hb(fES3cX-@WA1}hR$fM4Vt z@S060l~&``g_yE7HTPadnR_p%_$}7YBqZlWb9QYiJlSh>zUnj&6JtyS({qsgRC&I%%8wpt%f`{_RMX2 zEWu-LqGYYNj#+-Ew0U^V+7#iOm@FZwetM)OeiE@eeM(q$!1}GyW;?QH+~(fxvbCB2 zsXUNvGk>UcV_QtoH{+dbg=}!lEho@ccXg_v}?LTnv zi%&lFr7s^k%$_;&Y_sQROY5<=<6im1N#Aqrr#ha0;p?Zr@lD1q@?C6IzM9S{ed$T}b_7P8f$eTMv%M9hN>%c+zPrOtj#)%nwP0 zi4~rc!jsG3LjfzhHr0TrTc4z8k}lQ!2Jyr!E+0hvCVz-M4$p?k60>~Yd%sCt|H4G| zI(e2XZ16H-?a)-oyAZ3l22QRcj$%}X&j028raN9k<{hpmVE+TOgN9%JoUPB3=K-t` zvt@bA+WmR zr;cI|$4yCi&ZaZUm?;CPkd9F=#hxqqkhM=XEgm;-D$?Qydt7qm63_H`T=FLtkC{_S z<>C!+TFSUQ<09{vs6mSqHLik%Z!Kh$tHR@HfputUR(L)sP!22w8T@ybPlsiHi!<0Qz<7-rHt~wokHJDt;*HrK!wXwFFS2HQhX?YQ0{=(PkzhRqi-~+RtKzq zTyc3A*j;Cxi92`Q9bqf75|lY&b|PT;)OvjQLx|<)|EwogMo^{F z-091s=88Q+cPQ=iv2x*Sq|QA+)6+fL8b;V*tNBgT%?A$aZ)`7<{yf7 zM#N=R%?{1k&QKUOx&cxZYWJuMv7&>#d ztG5#aghd7hqR1NV9=Na#GZ-1@i3IVFm|7FWAj+VX_eWnE3>HNVs4(eMYV&CUC~Bae sDiJ|y#cF)9LRDWXQH%!9MTeqly);ctBn``um`^B-UEbgCAMRoQ0wPc!NdN!< literal 0 HcmV?d00001 diff --git a/apps/termtris/build/render.map b/apps/termtris/build/render.map new file mode 100644 index 0000000..da7b319 --- /dev/null +++ b/apps/termtris/build/render.map @@ -0,0 +1,85 @@ +***************************************************** +TMS340 COFF Linker Version 5.01 +***************************************************** +Tue Nov 03 19:00:04 2026 + +OUTPUT FILE NAME: +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] diff --git a/apps/termtris/evidence/render_frame3400.png b/apps/termtris/evidence/render_frame3400.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc882f79cf41180dd315f52b75d34e04c2c59cd GIT binary patch literal 19845 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 literal 0 HcmV?d00001 diff --git a/apps/termtris/src/vt1200/x11.c b/apps/termtris/src/vt1200/x11.c new file mode 100644 index 0000000..d2ea2bc --- /dev/null +++ b/apps/termtris/src/vt1200/x11.c @@ -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(); +} diff --git a/apps/termtris/src/vt1200/x11.h b/apps/termtris/src/vt1200/x11.h new file mode 100644 index 0000000..6ac8351 --- /dev/null +++ b/apps/termtris/src/vt1200/x11.h @@ -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 diff --git a/apps/termtris/tests/render.c b/apps/termtris/tests/render.c new file mode 100644 index 0000000..ccb4e2c --- /dev/null +++ b/apps/termtris/tests/render.c @@ -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; +} diff --git a/apps/termtris/tests/render.lua b/apps/termtris/tests/render.lua new file mode 100644 index 0000000..2931358 --- /dev/null +++ b/apps/termtris/tests/render.lua @@ -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 + 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' diff --git a/apps/termtris/tests/render.sh b/apps/termtris/tests/render.sh new file mode 100755 index 0000000..a088feb --- /dev/null +++ b/apps/termtris/tests/render.sh @@ -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"