First frame from a live scene: trek, assembled from the emit stream
trekframe.py parses the captured payload blocks (0x100 headers, rowid words 0x300|row, packed edge fields) into per-scanline spans and composites them onto an 832x512 canvas -- 21 blocks / 124 spans from the trek demo's compiled stream: distinct multi-colored objects + slanted band elements. dumppay.py dumps the payload region at chosen draws (scene verified byte-identical draws 1-300 = compile-once static showcase). Readout gains §06 with the frame rendered from the span data. First-approximation decode (edge-field scaling still being pinned) -- but every mark is a span the firmware's rasteriser emitted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
"""Dump the payload region MEMORY (0x8014000-0x8018000) at draw ~30 and draw ~300 of
|
||||
trek — the full compiled scene at two depths, independent of when writes occurred.
|
||||
Saves trek_pay30.pkl / trek_pay300.pkl for frame assembly."""
|
||||
import sys, time, struct, pickle
|
||||
sys.path.insert(0, r'C:\VWE\TeslaRel410\emulator\firmware-decomp')
|
||||
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||
import emu860, dis860, emu_main
|
||||
emu860.Mem.log = lambda self, *a, **k: None
|
||||
S = r'C:\Users\cyd\AppData\Local\Temp\claude\c--VWE-TeslaRel410\4e848c76-6e89-4034-8047-d8d491cb32d8\scratchpad'
|
||||
snap = pickle.load(open(S + r'\snap_trek0.pkl', 'rb'))
|
||||
r = emu_main.MainRunner(r'C:\VWE\TeslaRel410\dpl3-revive\patha\trek.raw.bin',
|
||||
fw='capfw7', max_cmds=30000)
|
||||
cpu = r.cpu
|
||||
cpu.mem.pages = {k: bytearray(v) for k, v in snap['pages'].items()}
|
||||
cpu.ctrl.clear(); cpu.ctrl.update(snap['ctrl'])
|
||||
cpu.r = list(snap['r']); cpu.f = list(snap['f']); cpu.cr = dict(snap['cr']); cpu.pc = snap['pc']
|
||||
cpu._apipe = list(snap['apipe']); cpu._mpipe = list(snap['mpipe']); cpu._fp_pipes()
|
||||
cpu._lpipe = list(snap['lpipe']); cpu._gpipe = list(snap['gpipe'])
|
||||
cpu._kr, cpu._ki, cpu._t = snap['kr'], snap['ki'], snap['t']
|
||||
cpu.lcc = snap['lcc']; r.qi = snap['qi']; r.heap = list(snap['heap'])
|
||||
|
||||
def dump(tag):
|
||||
words = {}
|
||||
for a in range(0x08014000, 0x08018000, 4):
|
||||
w = cpu.mem.r32(a)
|
||||
if w:
|
||||
words[a] = w
|
||||
pickle.dump({'mem': words, 'qi': r.qi},
|
||||
open(S + r'\trek_pay%s.pkl' % tag, 'wb'))
|
||||
print("dumped %d nonzero words at cmd %d -> trek_pay%s.pkl" %
|
||||
(len(words), r.qi, tag), flush=True)
|
||||
|
||||
t0 = time.time(); startq = r.qi
|
||||
targets = [(startq + 60, '30'), (startq + 600, '300')]
|
||||
ti = 0
|
||||
while time.time() - t0 < 800 and ti < len(targets):
|
||||
if r.qi >= targets[ti][0]:
|
||||
dump(targets[ti][1]); ti += 1
|
||||
continue
|
||||
h = r.hooks.get(cpu.pc)
|
||||
if h:
|
||||
if h(cpu) == 'done': break
|
||||
continue
|
||||
if not cpu.step(): break
|
||||
print("done at cmd %d in %.0fs" % (r.qi, time.time() - t0))
|
||||
@@ -362,7 +362,25 @@
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2><span class="idx">06</span> What it took to get here</h2>
|
||||
<h2><span class="idx">06</span> First frame from a live scene — the Star Trek demo</h2>
|
||||
<p class="sub">The archive's <span class="mono">trek</span> capture — unreleased Star Trek
|
||||
material — now <b>replays and draws</b>. After the dual-instruction-mode fix, its full
|
||||
pipeline runs on the emulated i860: classify, LOD select, rasterise, and <b>emit compiled
|
||||
micro-code</b> per 64×128 tile. Below is that frame, assembled by reading the emit stream
|
||||
back out of DRAM: <b>21 payload blocks, 124 scanline spans</b>, each block a primitive of
|
||||
the scene. This is a <b>first-approximation decode</b> (the span/edge field scaling is
|
||||
still being pinned), drawn from the board's own compiled render program — the deepest
|
||||
read-out of this hardware yet.</p>
|
||||
<div class="hero-render" style="margin-top:16px">
|
||||
<span class="tag">decoded from the compiled emit stream · first approximation</span>
|
||||
<canvas id="trekframe" width="832" height="512" aria-label="frame assembled from trek's compiled emit stream"></canvas>
|
||||
<div class="cap"><b>trek, draw 1–300 (static showcase scene)</b> — every mark is a span
|
||||
the firmware's rasteriser emitted; colors distinguish payload blocks (primitives).</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2><span class="idx">07</span> What it took to get here</h2>
|
||||
<p class="sub">The i860 core was corrected against the toolchain's own assembler output
|
||||
and MAME's validated i860 model. A selection of the load-bearing fixes:</p>
|
||||
<div class="fixlist">
|
||||
@@ -699,6 +717,32 @@
|
||||
dx.putImageData(img,0,0);
|
||||
})();
|
||||
|
||||
/* ---- trek frame: spans decoded from the compiled emit stream ----
|
||||
Each block = one primitive's payload; span = [row, edgeA, edgeB] (x = edge*4). */
|
||||
var TREK=[[[423,3,9],[424,2,8],[425,1,7],[426,6,6],[427,6,6]],[[423,15,21],[424,14,20],[425,13,19],[426,12,18],[427,12,18]],[[428,3,9],[429,2,8],[430,1,7],[431,6,6],[432,6,6]],[[423,24,182]],[[280,4,4],[281,4,4],[282,4,4],[283,4,4],[284,4,4],[285,4,4],[286,4,4],[287,4,4],[438,171,171]],[[280,8,12],[281,8,12],[282,8,12],[283,8,12],[284,8,12],[285,8,12],[286,8,12],[287,8,12],[438,171,171]],[[280,16,20],[281,16,20],[282,16,20],[283,16,20],[284,16,20],[285,16,20],[286,16,20],[287,16,20],[438,171,171]],[[272,204,204],[273,203,203],[274,202,202],[275,201,201],[276,200,200],[277,200,200],[278,200,200],[279,200,200],[439,177,177]],[[264,196,196],[265,195,195],[266,194,194],[267,193,193],[268,192,192],[269,192,192],[270,192,192],[271,192,192],[439,177,177]],[[256,188,188],[257,187,187],[258,186,186],[259,185,185],[260,184,184],[261,184,184],[262,184,184],[263,184,184],[439,177,177]],[[280,177,184]],[[415,28,204],[416,27,203],[417,26,202],[418,25,201],[419,24,200],[420,24,200],[421,24,200],[422,24,200]],[[415,28,196],[416,27,195],[417,26,194],[418,25,193],[419,24,192],[420,24,192],[421,24,192],[422,24,192]],[[415,28,188],[416,27,187],[417,26,186],[418,25,185],[419,24,184],[420,24,184],[421,24,184],[422,24,184]],[[264,202,202],[265,201,201],[266,200,200],[267,200,200],[286,169,169]],[[260,194,194],[261,193,193],[262,192,192],[263,192,192],[286,169,169]],[[256,186,186],[257,185,185],[258,184,184],[259,184,184],[286,169,169]],[[314,183,183]],[[456,98,98],[457,98,98]],[[286,207,207]],[[440,107,107],[441,106,106],[442,105,105],[443,104,104],[444,103,103],[445,102,102],[446,101,101],[447,100,100],[448,99,99],[314,194,194]]];
|
||||
(function(){
|
||||
var tc=document.getElementById('trekframe'); if(!tc) return;
|
||||
var tx=tc.getContext('2d'), W=tc.width, H=tc.height;
|
||||
// starfield-dark ground
|
||||
tx.fillStyle='#05080d'; tx.fillRect(0,0,W,H);
|
||||
var COLS=['#41ff8e','#5fd0ff','#ffb020','#ff5a6a','#eaffef','#a078ff','#c8c85a','#5ac8a0'];
|
||||
TREK.forEach(function(blk,bi){
|
||||
tx.fillStyle=COLS[bi%COLS.length];
|
||||
tx.globalAlpha=0.9;
|
||||
blk.forEach(function(s){
|
||||
var y=s[0], x1=s[1]*4, x2=s[2]*4;
|
||||
tx.fillRect(Math.min(x1,x2), y, Math.max(4, Math.abs(x2-x1)), 1.6);
|
||||
});
|
||||
// glow pass
|
||||
tx.globalAlpha=0.25;
|
||||
blk.forEach(function(s){
|
||||
var y=s[0], x1=s[1]*4, x2=s[2]*4;
|
||||
tx.fillRect(Math.min(x1,x2)-1, y-1, Math.max(6, Math.abs(x2-x1)+2), 4);
|
||||
});
|
||||
tx.globalAlpha=1;
|
||||
});
|
||||
})();
|
||||
|
||||
/* ---- IGC tile footprint: which 64x128 tiles the array lit for the object ---- */
|
||||
var TILES={ntx:10,nty:5,touched:[[0,2],[1,1],[1,2],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3],[4,1],[4,2],[4,3],[5,1],[5,2],[6,1],[6,2],[7,1],[8,1]]};
|
||||
(function(){
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"""THE TREK FRAME, first assembly: parse every content payload block in trek_emit.pkl
|
||||
(pages 0x8015800-0x8017fff, skipping the fixed background at 0x8015000-7ff), extract
|
||||
per-scanline span groups {rowid 0x300|row, packed edge words}, and composite all spans
|
||||
onto an 832x512 canvas (x = C*4 hypothesis). Each block gets its own shade. PNG out."""
|
||||
import pickle, struct, collections
|
||||
S = r'C:\Users\cyd\AppData\Local\Temp\claude\c--VWE-TeslaRel410\4e848c76-6e89-4034-8047-d8d491cb32d8\scratchpad'
|
||||
d = pickle.load(open(S + r'\trek_emit.pkl', 'rb'))
|
||||
emits = d['emits']
|
||||
|
||||
def asf(w):
|
||||
return struct.unpack('<f', struct.pack('<I', w & 0xffffffff))[0]
|
||||
|
||||
# content words in address order, deduped (last write wins per address)
|
||||
mem = {}
|
||||
for qi, pc, a, w in emits:
|
||||
if 0x08015800 <= a < 0x08018000:
|
||||
mem[a] = w
|
||||
addrs = sorted(mem)
|
||||
print("content words: %d span %#x..%#x" % (len(addrs), addrs[0], addrs[-1]))
|
||||
|
||||
# split into blocks at 0x00000100 headers
|
||||
blocks = []
|
||||
cur = None
|
||||
for a in addrs:
|
||||
w = mem[a]
|
||||
if w == 0x100 and (cur is None or a - cur[-1][0] > 4 or len(cur) > 3):
|
||||
if cur: blocks.append(cur)
|
||||
cur = []
|
||||
if cur is not None:
|
||||
cur.append((a, w))
|
||||
if cur: blocks.append(cur)
|
||||
print("blocks found: %d sizes: %s" % (len(blocks), [len(b) for b in blocks][:20]))
|
||||
|
||||
# parse each block: rowid words (0x300..0x6ff) delimit row groups; collect byte-3
|
||||
# fields of packed words within the group as edge coordinates
|
||||
def parse_block(blk):
|
||||
spans = []
|
||||
row = None
|
||||
edges = []
|
||||
for a, w in blk:
|
||||
if 0x300 <= w <= 0x6ff:
|
||||
if row is not None and edges:
|
||||
spans.append((row & 0x1ff, min(edges), max(edges)))
|
||||
row = w
|
||||
edges = []
|
||||
elif row is not None:
|
||||
f = asf(w)
|
||||
if 1e-5 < abs(f) < 1e3 and 1 < ((w >> 23) & 0xff) < 254:
|
||||
continue # increment float
|
||||
if w == 0xec00 or w == 0x100:
|
||||
continue # constants
|
||||
b3 = w & 0xff
|
||||
b2 = (w >> 8) & 0xff
|
||||
if (w >> 16) & 0xff <= 0x20 and 0 < b3 <= 250:
|
||||
edges.append(b3)
|
||||
if row is not None and edges:
|
||||
spans.append((row & 0x1ff, min(edges), max(edges)))
|
||||
return spans
|
||||
|
||||
W, H = 832, 512
|
||||
canvas = [[0] * W for _ in range(H)]
|
||||
total = 0
|
||||
for bi, blk in enumerate(blocks):
|
||||
spans = parse_block(blk)
|
||||
total += len(spans)
|
||||
shade = (bi % 6) + 2
|
||||
for row, c1, c2 in spans:
|
||||
if not (0 <= row < H):
|
||||
continue
|
||||
x1 = max(0, min(W - 1, c1 * 4)); x2 = max(0, min(W - 1, c2 * 4))
|
||||
for x in range(min(x1, x2), max(x1, x2) + 1):
|
||||
canvas[row][x] = shade
|
||||
if spans:
|
||||
rs = [s[0] for s in spans]
|
||||
print(" block %2d @%#x: %3d spans rows[%d..%d]" %
|
||||
(bi, blk[0][0], len(spans), min(rs), max(rs)))
|
||||
print("total spans: %d" % total)
|
||||
|
||||
# ASCII preview
|
||||
print("\nFRAME (832x512 -> 104x40):")
|
||||
for y in range(0, H, H // 40):
|
||||
line = ""
|
||||
for x in range(0, W, W // 104):
|
||||
v = canvas[y][x]
|
||||
line += " .:-=+*#%@"[min(9, v)] if v else " "
|
||||
print(" " + line)
|
||||
|
||||
# PNG via PPM
|
||||
img = bytearray(W * H * 3)
|
||||
PAL = [(7, 11, 17), (20, 30, 40), (65, 255, 142), (95, 208, 255), (255, 176, 32),
|
||||
(255, 90, 106), (234, 255, 239), (160, 120, 255), (200, 200, 90), (90, 200, 160)]
|
||||
for y in range(H):
|
||||
for x in range(W):
|
||||
r_, g_, b_ = PAL[min(9, canvas[y][x])]
|
||||
o = (y * W + x) * 3
|
||||
img[o], img[o + 1], img[o + 2] = r_, g_, b_
|
||||
open(S + r'\trekframe.ppm', 'wb').write(b'P6\n%d %d\n255\n' % (W, H) + bytes(img))
|
||||
try:
|
||||
from PIL import Image
|
||||
Image.open(S + r'\trekframe.ppm').save(S + r'\trekframe.png')
|
||||
print("wrote trekframe.png")
|
||||
except Exception as e:
|
||||
print("ppm only:", e)
|
||||
Reference in New Issue
Block a user