Walking the bin-page DMA chains enumerated 898 SEND payload programs per frame:
4 = the standard end-of-frame pipeline; ~860 = the scene's per-primitive
coefficient blocks at 0x815f000+, far beyond the old dump window. Their
geometry sits in plain screen-space IEEE floats (+0x14/+0x18 and +0x24/+0x28):
433 star positions + 389 streaks radiating from a convergence point = the
Star Trek warp-speed starfield, reconstructed from micro-code compiled by the
original firmware on the emulated i860. Readout §06 now renders the warp field
from the embedded position/segment data. Tools: dumpcontent.py (payload-region
dump), content_frame.py (chain walk + payload parse + reconstruction).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cross-checks against the vendor source settle the semantics: the 0x3xx words
are pixel-memory bit addresses / opcode fields (EOF.S emits them as hardcoded
constants; DIVPXMAP.H maps dvpx_* bit addresses 0-207 incl. eofr/g/b at
184/192/200), 0xec00 is a hardcoded control constant (EOF.S:2313), and the
whole 2,541-word payload set is 98.98% identical between trek and klngvid =
the standard screen program shared across captures. §06 now presents the
render as an emit-stream structural map with the demo-specific geometry still
to be located (beyond the 0x8014000-0x8018000 window; bin pages / far heap are
the candidates). The EOF.S<->EOF.C correlation method for the exact per-macro
decode is proven (send_em catalog: 30 emit sites with inline constants).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
capemit.py records every emitter-range write plus the DMA stream during trek
draws; analyze_emit.py decodes it. Findings: TILE ids = a 13x4 grid (row<<5|col,
832/64 x 512/128); the famous SENDE block at 0x8015020 is byte-identical across
captures = the fixed BACKGROUND-CLEAR program (not scene coeffs); trek's scene
content lives in further payload blocks (0x8015800..0x80168c8+, many primitives)
with a readable per-scanline span structure: row ids (0x39f,0x3a0,... = rows
415..422 observed), place-value indices, and edge constants in x2 chains. A
first-guess parser already extracts 8 consecutive spans forming a leaning
polygon edge -- rasterized geometry read directly from the compiled micro-code.
Full multi-block decode + scene assembly = next session (see memory).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
THE bug that silently culled all non-VSTRIP geometry in every capture. In DIM
the chip executes FP+core instruction PAIRS, and a delayed branch's slot is the
whole NEXT PAIR (2 words). The serial interpreter executed one delay word,
silently skipping the pair's core half -- e.g. the corner transform's final
fst.d z,w (0xf04213a4, in the delay pair of its bri): the last bbox corner kept
stale z/w, the in-place buffer decayed across passes (stale w=0 loses the
translation), every object classified fully-outside, and the classify->clip-draw
path emitted nothing while the VSTRIP path (different codegen) worked.
Fix: DIM state machine (_dim/_dim_on/_dim_exit/_dim_half) per i860 PRM ch.8 --
entry d.fpop -> one more serial instr -> DIM; exit pair-with-D=0 -> one more
pair -> serial; pair halves tracked positionally (reset at control transfers);
fnop/d.fnop (0xb0000000/0xb0000200, the shrd-encoded FP-slot filler whose 0x200
bit IS the D bit) recognized as FP halves -- missing it misaligned the halves,
missed the fnop(D=0) exit markers, and leaked DIM into serial code. Delay-slot
width decided from DIM state at branch FETCH time.
Acceptance: (1) corner transform now writes all 8 corners, w=1.0, and the
model-view matrix gains its real translation row (was zeros -- the concat had
the same bug); (2) cap7 regression clean, now 90 verts/frame vs 45 (a second
instance survives the no-longer-false cull); (3) klngvid runs past its draws
cleanly (previously wandered into the data segment).
Debug chain: flowtrace/cliptrace/planecheck/xformcheck2/wandertrap.py
(scratchpad) -- plane test hand-verified correct 10/10, inputs proven stale,
final store traced to the skipped delay pair.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
probe_live.py boots a capture (with the required r.start()) and counts per-draw
hits at the known polygon sites (quadA/triB/emit-header/VSTRIP). Discriminates
real scene content from the background-only test bench. Findings so far: every
cap* capture + ravtest + dtest + sharksval = the SAME hardware test bench (one
9x5 patch; sharksval draws it 3x); real content lives in the extra-action
captures (sdemo/glblade/munga/trek/fxtest/klng*), which DO replay (fxtest ran
6K+ commands incl. act25/42 with no faults) but have much larger scene builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
capture_triage.py parses each capture's first slice (no emulation) and reports
the embedded firmware build, action census, init config, and any actions outside
cap7's known-good set. Results: all captures share build 0x31440 + the same init
config; ravtest (RAV = the walled terrain map), dtest, cap6/cap8/capture/
sharksval are clean cap7-profile candidates; sdemo/duane/fishspls add morph;
bt3/munga7 add act31/43/45/list_remove. cap6's earlier "boot derail" was likely
just an under-budgeted run (87K cmds to parse), not a fault.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
payload_scan_mid.py scans a mid-mission frame (snapfull1, cmd 19,889): identical
9 coordinates as cmd 735 (65.5/72/181/236, all x[66,236]). So cap7 redraws the
same static surface patch every frame across its whole length -- no terrain, no
battle, ever. Corrects the earlier "mid-mission frames have battle scenes" guess:
a full battle scene lives in a different capture entirely. Readout last-mile
updated. The decode + array render fully account for cap7's content.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
payload_scan.py scans all writes into the payload region across the draw: 14,223
writes, every recoverable screen coordinate in x[66,236] = the object's range,
nothing near 0/400/832. So this death-cam frame carries no geometry beyond the
object -- the ~64 triangles account for essentially all payload writes; the rest
is a background clear. The "ground and sky still to render" was a wrong premise
for THIS frame: the array's object render IS this frame's geometry. Wider battle
scenes with terrain live in mid-mission frames (a separate capture). Readout §05
+ last-mile updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
frame_geometry.py snapshots each SEND payload (deduped by content) and pulls
fixed-point screen coords. Honest outcome: only 11 sparse coords, all in the
object's x-range, no terrain -- the payloads store compiled edge/plane
coefficients, not extractable vertex lists, so "grep coords and plot" is a dead
end. A real from-scratch render needs the full plane-role assembly (slopes +
constants -> lines -> triangles -> array), which stays unsolved. Documented in
MICROCODE-DECODE-NOTES.md so the shortcut isn't retried.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
frame_primitives.py decodes every coeff-copy word over the draw: 1051 SEND,
334 SENDE, 384 TILE, 327 GOTO, 231 STOP across 105 tiles -- but only 2 distinct
SEND payload addresses (0x08015xxx/0x08017xxx). That's double-buffering: the
firmware compiles each primitive into an alternating coeff block and SENDs it,
so the content changes over time while the addresses don't. A from-scratch full
frame must snapshot each primitive's payload at its SEND, then run all of them.
Readout §05 + notes updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
edge_decode.py: the edge payloads' non-float words carry the plane constant/
vertex terms as .8 fixed-point screen coordinates (0x0000ec00 = 60416 = 236.0,
a screen-x in the object's range), not IEEE floats -- which is why they weren't
in the float list. Locates the last missing piece for edge reconstruction:
A/B slope (float, verified 0.2%) + C (fixed-point coord). Readout §02 notes it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
edge_verify.py: the edge SEND payload float 0.12527 matches the object's own
screen-space edge normal (0.12555, computed from the captured vertices) to
0.2%, and 0.1262 to 0.5%. Edges are pure screen geometry (no z-scale ambiguity),
so this is a hard confirmation that the compiled micro-code carries the real
coefficients. Readout §02 states the 0.2% match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
igc_array.py gains readout_depth() -> the 24-bit Z stored in pixel memory as a
depth image (near=bright), i.e. the plane the decoded SENDE z-sweep interpolates.
Readout §05 now shows the depth buffer next to the tile footprint, ties the
z-decode to a visible array output, and reframes the "remaining work" as numeric
reconstruction across regions (the coefficients are already cross-validated).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full SENDE dump (ctrl_split.py) resolves into a regular 4-word instruction, one
per bit-plane: word0 = constant increment float, word1 = [length countdown,
source bit-addr] with op field 0x3a, word2 = the coefficient's place-value (the
x2 chain, -1.009..-0.00788), word3 = destination bit-plane (0x21..0x31 = a
17-bit fixed-point z/color). A MEMpluseqMEM bit-serial accumulation. SEND edge
payloads use the same value encoding with a different (header + 3-word group)
framing. Control-word split for the z/color path is now decoded; from-scratch
render still needs numeric reconstruction + plane mapping + the C term.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The payload floats group into clean x2 doubling chains (0.0079 0.016 0.032 ...
1.009) = a coefficient stored as its binary place values C*2^k across the
bit-planes, exactly how a bit-serial adder holds a number. Recovered base
coefficients correlate with the object's own screen-space edge/z slopes
(decode_corr.py, chain_decode.py), so igc_array.py's inputs are cross-validated
against the compiled stream. Fixed-point scales from FOOTER.SS (Czscale=2^20,
Ctexscale=2^16). Readout §02 + decode notes updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
§02 now shows the actual per-region DMA command list captured from the emulator
(region 0x0801fa40: SEND/SENDE/TXDN/TILE/GOTO) and the real SENDE payload with
its embedded float coefficients (bit-serial MEMpluseqMEM sweep). §05 + the
last-mile list updated: the micro-code is partly decoded (lists clean, payloads
carry recoverable floats), remaining work is the control-word field split.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The coefficient-copy (0xf0411cd4) writes per-region DMA command lists; captured
from the cap7 death-cam they decode cleanly against DMAENGN.H ({addr,opcode}
pairs, SEND/SENDE/TXDN/TILE/GOTO/FLUSH). Every region references the same
tile-relative payloads and differs only in TILE(id) + the GOTO link.
Dumping the SEND payloads shows they are NOT opaque: they interleave control
words with embedded IEEE floats = the edge/plane/colour coefficients, loaded as
a bit-serial MEMpluseqMEM sweep (regular 4-word instruction: increment float +
length/bit-address control + dest plane). So the micro-code decode is now
extraction + bit-serial execution, not blind ISA reversing -- the remaining
blocker is the control-word field split (igc_opco.h is not in the dump).
Full findings + next steps in MICROCODE-DECODE-NOTES.md; probes coefdump.py
(DMA lists) + payload_dump.py (payload floats), restore from snapv2.pkl.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signal-chain Pixel-Planes stage flips to "simulated"; new section shows the
64x128 tile footprint the array lit for the object (18/50), the 26-byte
pixel-memory bit layout (Z24 + RGB), and states the validation + the honest
remaining gap (the compiled micro-code binary, still undecoded, that carries
the ground/sky).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
igc_array.py implements the array model from PXPL5SUP/IGCOPS.C + IGCTYPES.H:
the screen is partitioned into 64x128 tiles; every pixel owns a 26-byte
bit-addressable memory + enable bit; all pixels evaluate the same linear
tree eval_ltree(x,y,A,B,C)=(int)(x*A+y*B+C) in lockstep. A triangle is drawn
exactly as the hardware does (PXPL5GEO tri_zb_rgb): three edge trees -> the
enable register, then z + r/g/b planes interpolated per pixel, z-buffered via
MEM2geMEM2, writes gated by enable, read back out of pixel memory.
Driven by the captured 9x5 surface it lights 18/50 tiles and produces pixels
that match shade_render.py to ~1% (edge anti-aliasing only) -- validating the
array against the reference rasteriser. This is the array's computational
model, not a decode of the compiled bit-serial micro-code (that binary
encoding is still undecoded); it produces the pixels that micro-code would.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 45 VSTRIP vertices captured off the i860 sort back into an exact 9x5
model-space grid (x,z in even 2-unit steps, y = height at every node; all
45 cells filled). cap7's death-camera views it nearly edge-on, which is why
the raw screen projection looks like a folded sliver. gridsurf.py rebuilds
the true grid connectivity (2 tris/quad) and shades it from the firmware's
own per-vertex normals -> a clean solid surface. render-readout.html now
leads with that true-3D reconstruction and shows the grazing projection +
wireframe as "how the death-camera saw it".
Also resolved a long-standing red herring: the (1,1,10,10) extent bounds
that earlier sessions chased as the "empty-bins bug" appear identically in
the working frame -- they're the per-frame marker rect, not the geometry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Captures the object cap7's death-camera view draws (4 VSTRIP strips, 45
verts) with its per-vertex normals straight off the emulated i860, and
shades it in software with a barycentric z-buffered fill (shade_render.py).
This is our rasteriser showing the firmware's geometry lit by the firmware's
own normals -- not the board's bit-serial Pixel-Planes array (that stays the
Tier-1 build). cap7-geometry.json is the portable capture; render-readout.html
is the published readout with the shaded frame as its hero.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
VSTRIP triangle-strip wireframe from the emulated i860's transform output
Vertex node layout (capfw7): model xyz @+0x00, normal @+0x10, next @+0x2c (linked
list = triangle strip), screen Y @+0x28, screen X @+0x40. Hook the transform loop
tail (0xf041614c), read node=r22-0x40 + f13/f16 = screen (x,y), group strips by
node-address gap. cap7 death-cam object = 4 strips / 45 verts -> a real wireframe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- pfgt/pfle/pfeq are always pipelined: fdest <- A-pipe retire, push undefined;
bit7 selects pfle (inverted CC sense) -- was misread as result precision.
316 pipelined compares in the firmware previously desynced the A-pipe.
- h_igcwait now models full consumption: done-status nonzero AND write-index
reset (+0x7f8=0), otherwise queue pages saturate at 0x7f0 across frames and
enqueueing silently stops (observed: even the per-frame marker item stopped
binning by cmd ~4300).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The frame-1 extent bug traced to four `pftrunc.sd fN,f0,fX` ops (P=1)
converting the region bounds through the A-pipe -- our fix/ftrunc was
scalar-only (MAME's core never implemented the pipelined form: "pipelined
not functional yet"). The drains then retired neighboring values (the
homogeneous w=1.0f as raw bits -> y1=0x3F800000>>7=8323072 -> ~8.3M phantom
tile rows = the 49K uniform bins / 4096 same-page descriptors).
Pipe stages now carry (value, rp, raw): pipelined ftrunc/fix push the
truncated integer BITS (64-bit pair for .sd results), and _retire() writes
raw entries back verbatim instead of re-encoding floats. Extent probe after
fix: x0=y0=x1=y1=0 (sane empty bound), single visit instead of a hot loop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
unhooked; draw_scene #1 runs with 41K IGC board accesses
The functional pipe model is replaced with exact i860 semantics, validated
against MAME's i860 core (src_opers[] and insn_dualop/insn_fadd_sub verbatim):
- Precision bits: SOURCE = bit8, RESULT = bit7 (swapped from our old reading;
.ds is invalid except famov). For DUAL ops: sp = multiplier source prec,
rp = adder source prec AND all results.
- Dual ops (sub 0x00-0x1f): bit10 selects PFAM (fdest <- A-pipe retire) vs
PFMAM (fdest <- M-pipe retire) -- NOT "pipelined". FLAGM operands (the
A-pipe entries in the DPC table) read the M-pipe in the PFMAM family.
T-loads on DPC 2,3,6,7,8,0xb,0xc; K-loads on DPC 1,3,5,7 (from fsrc1 at
mul precision; T from the M-pipe last stage).
- fdest BYPASS: pipelined ops whose source register equals fdest read the
retiring pipe value instead of the stale register (dual mul: op2 only;
dual add + scalar pipelined fadd/fmul: both operands).
- Pipe stages carry (value, result-precision); push rounds to single when
rp=0 (exact magic-constant float->int bit games); retire encodes with the
pusher's precision. Adder = 3 stages; multiplier = 2 (double) / 3 (single);
pfld = 3-stage load pipe; graphics fiadd/fisub = 1-stage pipe, 64-bit .dd.
ACCEPTANCE: (1) the firmware's own Newton-Raphson integer divide now computes
16/16 = 1 through the pipes -- the intdiv hook is retired (authentic
execution); (2) draw_scene #1 from the snapshot executes real render phases:
coefficient fld.d bursts, 0x800-byte queue-page initialization, tile enqueue,
and the first region flush -- 40,959 IGC/board MMIO accesses (previously 0).
Frame still running at the profiling time budget (~240M steps in).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
VR_COMMS.C boot_860() shows the host boots the render board by streaming the
whole .MNG over the wire: args860 ("i860 50MHz and kicking"), a 40-byte
code860 packet holding the 7-word header {csize,dsize,bsize,cstart,dstart,
bstart,entry}, then the code/data/bss segments. cap7's preamble is therefore
a complete firmware image -- and a DIFFERENT BUILD than sda4/RPLIVE/VREND.MNG
(text 0x31440 vs 0x39ec0), which explains the residual global-address
mismatches (version skew) when replaying cap7 against the sda4 build.
The wire header also confirms the link layout from the protocol itself:
cstart=0xf0400000, dstart=0x00001000, bstart=0x0001f4a0, entry=0xf0400000.
Note the streamed "bss" content is NOT zeros (10685/14272 nonzero bytes).
extract_capfw.py reassembles the image (capfw7.mng + capfw7.bss, committed);
it boots in emu860 to 0xf040062c then needs op 0x0d (new ctrl-reg variant).
run_to_draw.py = marathon runner with board-stream dump.
Next: re-derive the hook addresses (main/dN_receive/allocator/...) for the
capture build and replay cap7 against its own firmware, version-matched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
processes the full wire boot incl. the downloaded PAZ/sfx module
ISA fixes, all derived from the toolchain's own .S<->.O pairs (AS860.ZIP:
OPTFLOAT/TRISTRIP/ZBUF32, plus DNC.O) and the firmware's linked COFF header:
- DATA_BASE = 0x1000 DEFINITIVE: VREND.MNG carries its original COFF header in
the file tail (.data vaddr 0x1000, .bss 0x1f940, entry 0xf0400000).
- Integer loads: even opcodes are register-indexed (EA = src2 + src1); op 4/5
size flag = instr bit0 (0 = ld.s 16-bit, 1 = ld.l 32-bit); ld.b/ld.s
sign-extend.
- Integer stores: st.s/st.l selected by offset bit0, same split-offset rule.
- FP loads/stores: FP register lives in the DEST field for both fld and fst
(fst does NOT use the integer split-store encoding); flag bits: bit0 =
auto-increment (base <- EA), bit1 1=.l/0=.d, bit2 = .q; .d/.q span register
pairs/quads. ~450 fld.d + ~300 fst.d were previously read/written 32-bit.
- bla (op 0x2d, was misdecoded as shrd): branch-on-LCC-and-add with the
sign-dependent LCC rule (src1<0 -> signed sum >= 0), so spent countdown
loops terminate. 335 bla instructions in the firmware.
- CORE ESCAPE (op 0x13): sub-op 1 = lock, 2 = calli, 7 = unlock. Previously
everything decoded as calli, so every spinlock acquire jumped to address 0 --
this was the phantom "exit stub" behind most earlier derails.
- f2b: IEEE overflow -> +/-inf instead of raising.
emu_main.py (new): runs the firmware's OWN main() (0xf0403f10) and feeds real
wire captures through a hooked dN_receive, so init/do_init/dispatch/handlers
all execute authentically. Provides the transputer-monitor environment
(processor id, DRAM region descriptors in the shared control block, sbrk/
shared-block seed slots) and hooks only the link primitives (bla busy-wait,
dN_mynode/dN_nodes/dN_receive/dN_send, putchar path, spinlocks, page allocator
+ virt->phys translator pending Tier-2 VRENDMON).
KEY DISCOVERY: the capture's args860/code860/data860/bss860 preamble is the
host DOWNLOADING an additional i860 module (the PAZ/sfx renderer layer, banner
"i860 50MHz") which installs the runtime handler tables and system objects.
Feeding it through the firmware's own handlers, the module loads and makes the
first IGC board-register writes. State: 834+ wire commands processed (module
download + init + create); first draw_scene sits at command 1568.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Env-gated hooks to continue the create()/jump-table investigation without
changing default behavior:
- EMU_DATA_BASE (default 0): overrides I860.DATA_BASE for the .data link base.
- EMU_INDEXED=1 (default off): makes EVEN integer load opcodes register-indexed
(EA = base + index), per the ground-truth `ld.l r30(r31),r31` in VR_REMOT.S.
Sweep finding (EMU_INDEXED=1): create() RETURNS at DATA_BASE=0xff0 making real
subroutine calls, confirming register-indexed loads + a ~0x1000 data base are
correct. Residual: do_init derails to (DATA_BASE+0x30) for base >= 0xfe0 while
create needs >= ~0xfe0 -- a single flat base can't satisfy both yet, pointing
to a segment/relocation nuance (or a separate do_init jump-table derail).
Details + repro in the tier-0 memory. Default behavior unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Worked the full-render marathon by tracing faults in the real VREND.MNG
firmware and matching them to the ground-truth AS860 assembly (VR_REMOT.S)
and C source (VR_REMOT.C). Six interpreter bugs found and fixed:
1. subs/subu direction: computed src2-src1; must be src1-src2 with
CC=(src1<src2). Corrupted every subtraction, compare and bounds-check.
2. logical CC: all i860 logicals (and/andnot/or/xor + .h) set CC=(result==0).
Had wrongly limited it to the AND family, so the `xor 0x0,rN,r0; bnc`
zero-test idiom spun forever.
3. STORE encoding (the big one): i860 stores encode the SOURCE register in
bits 15:11 (src1), not the dest field, and SPLIT the 16-bit offset across
bits 20:16 (high) + bits 10:0 (low). The old decode saved the wrong
register to the wrong offset, so function prologues never stored r1 and
every `bri r1` return jumped to 0.
4. ld.b/st.b: op 0x00/0x01 = ld.b, op 0x03 = st.b (byte). Proven by byte-scan
loops (r5 advanced by 1) and a save/restore pair at 0xf042b418 -> b430.
5. Mem page-span: r16/r32/w16/w32 crashed on 64KB page boundaries; now fall
back to byte access across the boundary.
6. fmlow.dd (FP subop 0x21): the i860 has no integer multiply, so ints are
ixfr'd into FP regs, multiplied via fmlow.dd, and fxfr'd back. Implemented
as a 32x32 -> 64 multiply across the destination register pair.
emu_replay.py now runs the source-accurate init sequence: velocirender_init
on the init(0) command, then do_init before the first real command. Result:
boot idles at 0xf0400590; velocirender_init returns; do_init runs to
completion (~9200 steps of real allocator / name-table / scene-root setup).
Remaining blocker (documented in the tier-0 memory): create()'s switch needs
register-indexed integer loads (VR_REMOT.S: `ld.l r30(r31),r31`) together
with the correct .data link base (~0x1000, not 0) -- the two errors currently
cancel for the immediate paths but break the indexed jump-table dispatch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Feeds the real captured VelociRender wire (dpl3-revive/patha/*.raw.bin, from the
soft-renderer work) to the firmware's own command handlers in emu860 -- the path
to building a scene and rendering a frame with the board's own code.
- Parses captures via vrboard's Assembler (skips the .BTL boot, frames
action+payload). cap7 = a clean small scene (17 zones / 22 instances / 24
geometry / 1 view).
- Calls each command's handler directly with the payload pointer in r16 --
verified valid: create's prologue reads [r16+0]=type, [r16+4]=handle, i.e. the
raw wire layout (no CCB wrapping needed). Bypasses the un-emulated transputer.
- Confirmed WIRE-action -> handler map by function identity (create/flush/
dcs_link/list_add/draw_scene/statistics/set_texmap_texels).
Two blockers identified for a full frame (documented in the memory + README):
1. The dispatch jump table is indexed by an INTERNAL command code, not the
wire action (velocirender_input remaps wire->internal first, e.g. draw=9
->12, flush=3->30); the high-frequency per-frame commands (0x09, 0x1d
artics, 0x2a) still need that remap reversed.
2. init(0) must run first to set up the allocator/name-table/scene-root --
cold create() faults without it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
emu860.py -- an i860 interpreter (reuses the dis860 decoder) that executes the
real Division firmware. This is the foundation of the preservation-grade
emulator (run the board's own code, vs the GL bridge which interprets the wire).
- Boots VREND.MNG cleanly: i860 init (psr/dirbase/fsr), enables the MMU, then
idle-spins at 0xf0400590 waiting for a transputer interrupt (init complete).
Board-config regs modelled (0xfffff720=2, 0xfffff70c=1) via map_control().
- Call harness (cpu.call): invoke firmware functions directly, bypassing the
(un-emulated) transputer that normally drives the CCB.
- Correct memory map: .data/.bss linked LOW (DATA_BASE=0), so globals resolve.
- FP unit with double precision (register pairs): fadd/fsub/fmul/famov/frcp/
frsqr/fix/ftrunc/fgt/feq/fxfr/fiadd/fisub.
- Delay slots, control regs, sparse MMIO memory with trap/logging, tail-trace
and stopat debug aids.
draw_scene now runs real code (incl. a double int->float conversion) until it
needs accumulated scene state -- next step is replaying a captured wire command
sequence through the command dispatcher (jump table @0x47cb8) so the scene
builds and draw_scene emits the IGC coefficient stream (Tier-1 handoff).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Predator/IR vision: reverse-engineered from the original firmware and
confirmed by the build team -- it is the Division board's TEXTURE-VALUE RAMP
mode (a "check your texture maps" diagnostic the devs hijacked), NOT a
grayscale squash or a false-colour palette. Located in VREND.MNG (effect
handler @0xe6c0, wire action 0x1b, type -1 ON / -2 OFF); ramp colours from
VR_DRAW.C. Renderer reworked to match: vrview_gl now does the 4-ramp
lerp(color0,color1,luminance(texel)) in the mesh pass (grayscale+defog
removed). Live-rendered on a new night-clear arena egg; crew A/B verdict
pending.
Firmware-decomp toolchain (emulator/firmware-decomp/), all built from the
project's own artifacts and validated:
- coff860.py i860 COFF reader (symbols/sections), names match AS860 source
- derive860.py derives the i860 opcode map from matched .S<->.O pairs
- dis860.py i860 disassembler (98% on clean ground truth; proven on
VREND.MNG -- velocirender_statistics decodes correctly)
- sigmatch860.py reloc-invariant signature matcher onto the stripped image
- i860-encoding.md / FIRMWARE-SYMBOLS.txt / README.md
PVISION-IMPLEMENTATION-GUIDE.md: self-contained hand-off for the BT411 team.
HARDWARE-ARCHITECTURE.md + hardware-photos/ (15 board shots): the Division
VelociRender card is a 2-board stack driving a 3-processor pipeline --
INMOS IMS T425-J25S (comms/control, runs vrendmon.btl) + Intel i860 XP-50 (FP
geometry, runs vrender.mng) + Division PXPL IGC 5.2 ASIC with ~48x PXPL EMC
5.1 (UNC Pixel-Planes-5 SIMD array; "EMC" = the firmware's configEMCs) +
Analog Devices ADV7150 RAMDAC + NTSC. Plus the VWE Video Distribution Board
(P/N 1404: AMD MACH130 + 3x Brooktree Bt477) for the 3-VGA-head cockpit split.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- IR / predator-vision thermal: the cockpit IR button toggles it via the
wire (dpl_Effect on action 0x1b, mode -1 ON / -2 OFF -> board.pvision).
vrview_gl present pass remaps scene luminance to a thermal palette
(default heat/Predator; mono|green|amber via VRVIEW_PVISION_PALETTE;
VRVIEW_PVISION / bridge 'v' key force it on). Exact palette + HUD-exempt
pending crew review.
- Texture filter default flipped to bilinear (operator preference); the
i860 board itself point-sampled, so VRVIEW_FILTER=nearest reverts. (A CRT
present-pass bleed/scanline prototype was built and rejected; removed.)
- Searchlight: no code needed -- night A/B proved it is a VIEW-FOG push-back
(fog near 5->60, far 400->500 on), already rendered by the existing view
fog path. Not a light cone. Added TESTNITE.EGG (arena1/night/fog) +
gauge_arena_night_pipe.conf for the test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fresh extract of the final TeslaPod410.zip (renderer bundled) to C:\Games,
configured via configure.ps1, launched end-to-end on the co-located rig --
user-verified working as expected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A fresh extract has net_*.conf.tmpl but no rendered confs until postinstall/
configure.ps1 runs; the old "pass --conf" error led straight to renaming the
template by hand (which then breaks configure: the tokens never substitute
and the tmpl is no longer found). Detect the sitting .tmpl and point at the
real fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
render-bridge/freeze.ps1: PyInstaller onefile of live_bridge.py + Dave's
vrboard/vrview/vrview_gl (+glcontext collected for moderngl) -> dist\
renderer.exe (~33 MB). GL backend live-verified from a pure-dist launch
(no --root/--renderer overrides) on 2026-07-10. package.ps1 bundles the
frozen exe automatically when present; DEPLOYMENT-PLAN's renderer OPEN
item is resolved. Ignore the build-artifact dirs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Retire the bundled-silent-installer requirement: postinstall now detects an
existing Npcap service and moves on, warns clearly when absent, and still
honors a bundled deploy\npcap.exe if one is shipped. package.ps1 no longer
flags the missing bundle as a warning.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
configure.ps1: bay-IP pick now filters to Up physical adapters and fails
loudly on multiple candidates instead of coin-tossing by InterfaceMetric;
-BayIp forces the choice (and permits binding a virtual adapter on a dev
rig); -ConsoleIp sets WATTCP gateway/nameserver (default: the bay IP --
needs only to be a live on-subnet host, the console DIALS the pod);
-Root is validated up front.
DEPLOYMENT-PLAN: record the four first-smoke-test findings (2026-07-10,
end-to-end egg->mission achieved): SendToRxAdapters on the pod NIC, host
must not hold the game IP, NIC checksum offload silently kills co-located
console->pod IP (ARP works, TCP silent -- disable offload+LSO), and bt/rp
require sound + a packaged renderer (closes the renderer OPEN item: ship one).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--no-sound strips VWE_AWE32, the FAST (28Hz SOS) clock never ticks, and
BTL4OPT hangs forever in the RIO-reset busy-wait (L4RIO.cpp SetDTR loop on
a frozen Now()) -- looks exactly like a vRIO/serial failure but isn't.
Root-caused during the 2026-07-10 dist smoke test. Only safe with a
SLOW-clock conf (setenv arg2=s). Warn at launch + document in usage/README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Real-NE2000 semantics: accept only own-MAC unicast + broadcast (multicast
excluded -- the guest stack is unicast+ARP only), and drop npcap's loopback
of our own injected frames. Without it, a live-LAN host download floods the
emulated NIC at wire rate (65k+ frames/min) and wedges netnub before boot
("discarding..."). Filter failure is non-fatal: logs and runs unfiltered.
Also log the first 12 unicast accepts/drops with the live PAR registers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Complete arg surface of the single installed entry-point -- mode selector
(bt/rp + recognized-unsupported), feature toggles, window placement, path
overrides, diagnostics, and the exit codes the console reads back (game
ExitCodeID passed through; 2=bad args; 3=mode not yet wired).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- net-boot/loop_rp.bat: GO.BAT-style netnub loop for RP (mirror of loop.bat,
retargeted to rpl4opt). deploy/net_rp.conf.tmpl now calls it instead of the
single-shot netnub + DOS pause -- the deployable runs in a loop until the
supervisor kills DOSBox (per operator: no single-shot in the product).
- DLL closure resolved: dosbox-x.exe is a 182MB STATIC build -- import table is
only Windows system DLLs, delay-import table empty, libpng/SDL/zlib embedded.
Nothing to bundle; wpcap.dll comes from the Npcap install. package.ps1's "no
DLLs" case is now informational, and the plan/README note the static build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Network-agnostic, air-gapped pod install for the two DOSBox titles (BT/RP 4.10),
fitting the existing TeslaConsole/TeslaLauncher pod-bay architecture.
emulator/DEPLOYMENT-PLAN.md
Full design: bridge on the one NIC (pods form a source-proven P2P TCP mesh, so
NAT/slirp is out); launcher keeps the bay IP, the DOSBox guest bridges at
bayIP+100 (egg/mesh/game endpoint); two-edit +100 convention (postinstall +
console DOSBox flag); static, air-gapped, <=32 pods.
emulator/pod-launch/ (C# net8 supervising entry-point)
Creates a Job Object (KILL_ON_JOB_CLOSE), launches DOSBox-X + the render bridge
into it and blocks -- kill this process and both die (kernel-enforced, even on
a hard TerminateProcess of a hung session; verified). Mode dispatch: bt/rp
wired (also serve camera + live mission-review via egg hostType); review +
diagnostics recognized but fail clean until their DOS launch args are known.
emulator/deploy/ (install side)
postinstall.bat (thin elevated wrapper) + configure.ps1 (NIC detect, realnic
bind as a contiguous letter-leading GUID fragment, game IP = bayIP+100, stable
MAC, render net_*.conf templates, stamp WATTCP.CFG my_ip) + tokenized conf
templates + package.ps1 (assemble the zip). Render/bind/stamp + packaging
verified against a scratch tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>