Commit Graph
160 Commits
Author SHA1 Message Date
CydandClaude Opus 4.8 e9963a5040 ACCEPTANCE 1: SMPTE bars reproduced through the executed IGC pipeline
texu = per-pixel x computed by the REAL captured sweep programs across the
real 52-tile DMA stream; two documented approximations (texz seed op, ramp
LUT). Bars match the reference: 7 bars, correct colours, black border at
x=48. bars_pipeline.png in the session scratchpad.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 04:29:11 -05:00
CydandClaude Opus 4.8 12673c5a22 IGC decode: convergence experiment negative; OPF2(145) = next suspect
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 03:26:09 -05:00
CydandClaude Opus 4.8 6c3dc2e954 igc_exec: SCMEMA (screen-address seed) + gated sweep-pair adds
Verified flowing on the bench tile: s145 holds per-pixel x, scalar region
packs it, SCAintoMEM constants land in eofr/eofb. Bars chain now breaks at
the texz seed = the op-0x48-with-len family (0x3a804820 targets texz) --
FCMEMA, next to implement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:51:17 -05:00
CydandClaude Opus 4.8 6451b739bd IGC decode: colour-instr layout (5-word Gouraud planes) + SCMEMA lead for the bars
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 01:17:13 -05:00
CydandClaude Opus 4.8 7bac137a63 Tier-1 FIRST GEOMETRY RENDER: real captured IGC packets -> shaded z-buffered 3D
render_wide30.py executes the cap7 death-cam capture's raw instruction
packets ({TREEgeZERO_L3 x3 edges, MEMltTREE_L3 z-test, TREEintoMEM_L0
z-write, colour planes, scalar} -- 152 packets found by header scan) through
the named-ISA semantics, vectorized: 85 packets survive edge+z, producing a
recognizable smooth-shaded, depth-correct render of the test-scene object
(wide30_render/depth.png in the session scratchpad). This is geometry drawn
purely by executing the hardware's own instruction stream -- no
geometry-extraction shortcuts.

Known approximations this pass: winding-agnostic edge acceptance, colour
plane word-offsets partly empirical (R channel confirmed Gouraud-correct,
G/B extraction still off), duplicate packets from the wide capture window
z-fight. eof/bars ops still stubbed (per-tile program renders uniform).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:42:52 -05:00
CydandClaude Opus 4.8 ddaa63f9b0 igc_exec rev2: named op table (ADDR/DUMP ground truth) + executor semantics
Parser rewritten around the named table -- TREEltZERO_L3/TREEgeZERO_L3/
MEMltTREE_L3/TREEintoMEM_L3+L0/TREEclmpintoMEM/SCAintoMEM/CPY/the ENAB group/
sweep pair, aux field (bits16-22) disambiguating operand formats. Validates
100% clean against DUMP's live reference packet (9 instructions, 0 unknown).
Executor implements the enable-gated semantics per IGCOPS.C; eof-exotic ops
parsed structurally, semantics stubbed pending builder disasm.

Capture insight: the firmware's payload pages are double-buffered PER
PRIMITIVE -- DRAM snapshots hold only the last primitive's packets, so full
frames need payload capture at SEND time (send_capture.py, scratchpad).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 00:10:13 -05:00
CydandClaude Opus 4.8 ecdb4b6122 IGC decode: op table named via PXPL5SUP/ADDR (igc_opco.h fragment) + DUMP (live op-table print)
ADDR carries exact I_*/P_* macro bases; DUMP is a pxpl5tst log printing the
named op values plus two complete live triangle packets. Retroactive
unification: tri_recover's 'scale 0.00178' was TREEltZERO_L3 (0x3ae94200)
read as a float -- the DRAM stride-0x10 groups were always 4-word edge
instructions in the DPL3 direct-float format igc_exec.py already executes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 23:40:00 -05:00
CydandClaude Opus 4.8 8ff234fe9c IGC decode: sweep-family 2-word format derived; doubling-chain myth busted
The firmware's SENDE emitter (disassembled at 0xf041db90 via builder_trace)
proves the captured 0x8x01213a words are op-0x21 instructions whose len-field
(136-k)<<23 decrements through the IEEE exponent bits -- the 'x2 doubling
chain floats' of the earlier decode were an artifact, not coefficients. The
69-word SENDE is a fixed bit-serial transform program (texz -> texu region);
real per-primitive coefficients are in the SEND(33)/SEND(41) blocks
(builders at 0xf04126xx, next disasm target). 2-word sweep format:
{op|dst|(len1+116)<<23, src|dst<<8|len2<<16}, verified word-exact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 23:10:05 -05:00
CydandClaude Opus 4.8 c54d920761 Tier-1 IGC decoder: encoding formula cracked + instruction executor core
The igc_opco.h macro header is absent from the dump, but the PGC-expanded
compiler output (PXPL5TRI.S / PXPL5OPT.S / EOF.S) carries every constructor
expression in the clear. PXPL5OPT.S:1649 gives the universal template:

  word = op<<8 | aux<<16 | (addr&0xff) | ((len+115..117)&0xff)<<23 | flags | S1<<31

Verified word-exact against the captured streams (Ix_SCAintoMEM_S1(52,5) =
0x3c90f734, Ix_MEMgeSCA_S1(5) = 0xbc916c00, Ix_MEMltTREE_L3(97,20) =
0x44ea2161). Edge instructions are 0x601/0x602/0x603 + A,B,C floats; the aux
field encodes the operand format (L3=+3 floats, L0=bare, C1/S1=+1). Full
derivation log in IGC-ENCODING-DERIVATION.md.

igc_exec.py: payload parser + 64x128-tile executor (26-byte bit-addressed
pixel memory, enable reg, shared linear-expression tree) per IGCOPS.C
semantics; constructor self-tests + triangle smoke test pass.

Known gap: the firmware's own packet builder emits a bit-serial SWEEP variant
(ops 0x21/0x25/0x39/0x48/0x4c/0x7c/0xfa per bit-plane) that the parser does
not yet cover -- next target is FITPLANE.SS (the bit-serial plane-fit source).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 22:38:57 -05:00
CydandClaude Fable 5 e18a5c8454 Deploy: --pipe trim -- named-pipe serials to vRIO/vPLASMA for every mode
Dev-rig policy (2026-07-17): everything on the dev machine runs named pipes
to vRIO until Real-RIO testing -- no com0com. pod-launch --pipe selects the
<mode>_pipe conf (serial1=namedpipe pipe:vrio, serial2=namedpipe
pipe:vplasma); pipe variants of all three deploy templates (auto-staged/
rendered) + a dev-tree net_loop_pipe.conf beside the existing RP one.
Requires the vRIO dist >= 20260716-68e3d1f (VRioPipeService/
VPlasmaPipeService landed there). Arcade cockpits with the physical RIO
keep the plain COM-port confs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 21:54:23 -05:00
CydandClaude Opus 4.8 38dd84f5ec i860: annul the delay slot of a not-taken bc.t/bnc.t (fixes >512-object captures)
The interpreter executed bc.t/bnc.t's delay-slot instruction on both the taken
and not-taken paths. On real i860 the ".t" conditional branches annul that slot
when NOT taken -- the compiler fills it with the loop body's first pointer load,
valid only when the branch continues:

    xor   0,r4,r0         ; CC = (node == NULL)
    bnc.t loop            ; continue while node != NULL
    fld.d 0x20(r4),f16    ; annulled when node==NULL; else reads *NULL

For cap7/trek/batest (<512 live objects) the stray reads land in dead registers,
so the bug stayed latent -- cap7's rendered coefficient stream is byte-identical
before/after this change (verified: md5 52e16774... over the first 3 draws). But
the same stray access corrupted the object-registry tail-find walk (REGISTER
@0xf04041f8) whenever the 512-bucket handle hash chained (>512 live objects),
orphaning entries so FIND_REMOTE missed -> NULL -> firmware exit()
("Attempt to add NULL to a list"). That killed every content capture
(fxtest/sdemo4/glblade) ~1/3 in, before any draw.

Fix: a _squash flag skips the delay slot (whole next pair in DIM) on a not-taken
bc.t/bnc.t. fxtest now replays all 18987 commands and emits 3.67M coefficient
words of real geometry (previously 0 -- it died at cmd 6277); cap7's full mission
is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:24:33 -05:00
CydandClaude Opus 4.8 2a11838d74 Stream-verify the demo scenes: warp field + Klingon = DRAM-resident assets
trek's live-written DMA stream contains ZERO non-standard SENDs (624 pairs, all
four standard payloads) -- the captured demo frames, like the bench, draw only
the SMPTE test card. The warp starfield and Klingon vessel geometry are the
demos' scene ASSETS resident in render memory (compiled coefficient blocks
never referenced by the captured frames' chains). §07 relabeled accordingly:
"Recovered assets -- the Star Trek material". Every image in the readout is now
attributed by stream evidence, not memory scanning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 12:57:25 -05:00
CydandClaude Opus 4.8 70a7d38329 Correct the bench frame: bars only -- the triangles are not in the frame
The user asked why triangles sat on the test pattern; verification against the
frame's actually-WRITTEN DMA stream (captured word-by-word during draws) shows
it references ONLY the standard background program -- the 0x815f000+ triangle
blocks are test.egg's 3D scene content, precompiled in DRAM but never
referenced by this frame's chains (my loose memory scan misattributed them).
The live frame = SMPTE colour bars, now rendered bars-only (paint_bars.py) and
matching the user's reference card. Readout §06 corrected: bars frame + honest
note about the undrawn DRAM-resident scene data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:39:52 -05:00
CydandClaude Opus 4.8 211f23d042 PAINT THE BENCH: SMPTE bars + exact 3D test model, composited
paint_bench.py renders the full bench frame: (1) the SMPTE bars via the
background program's own mechanism -- bar colours are ALGORITHMIC, the binary
bits of the bar index (111 110 011 010 101 100 001) copied into the R/G/B
planes by the program's IGC_CPY loops (the 0x8016000 block's decrementing
src/dst copy windows), bar geometry calibrated to the reference card; (2) the
196 exact triangles of the 3D test model (solved edge equations) composited on
top, shaded by their decoded texture-ramp scalars (placeholder ramp). Readout
§06 now shows the painted frame (14KB embedded PNG). Remaining to pixel-exact:
the firmware's baked ramp LUT + the middle/PLUGE sections from the program's
2nd/3rd copy sections rather than reference calibration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:17:53 -05:00
CydandClaude Opus 4.8 cbae445ce0 Bench identified: SMPTE colour bars + 3D scene layer; bars = acceptance oracle
User-provided ground truth: the cap7 test scene displays SMPTE colour bars.
Axis-alignment census (8/196 axis-aligned) proves the recovered triangles are
the 3D test-scene layer, not the bars -- the bars are the OUTPUT of the
standard background program (the byte-identical bit-serial SENDE/SEND set in
every capture: a video test card painted as the permanent backdrop, colours
written to the eofr/g/b planes 184/192/200 by the x2-chain sweeps). Payload
formats settled: size-18 = 3 edges + z-plane; size-21 = 3 edges + texture-ramp
SCALAR plane (the pvision mechanism). The known bar pattern is now the
pixel-exact acceptance oracle for the bit-serial program executor (the
chartered IGC_* macro work). Readout §06 caption updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 08:55:22 -05:00
CydandClaude Fable 5 bce9a443b0 Emulator: VDB heads mirror TEXT mode -- boot scroll + suite menus, like the
real cockpit

The splitter passed the analog VGA signal through whenever the game's
packed mode wasn't driving it: a person in the cockpit watched the PC boot
sequence scroll across every secondary display, and the test suite's menus
were readable on the heads. pal_draw now rasterizes M_TEXT (80x25 through
the live font tables + attribute palette -> VGA DAC, 8-dot cells, centered
640x400) with the mono MFD windows showing single-wire green-phosphor
luminance.

Two DOSBox-X planar-layout lessons encoded in comments: text cells live in
linear_base as one latch DWORD per character address (char/attr = plane
0/1 bytes, cell stride 4<<addr_shift, lvida = vidstart<<2), and
font_tables index PLANAR vram too -- one font byte per dword, so the glyph
fetch is ((chr<<5)+line)*4. Plus a 2s textdiag line (row-0 decode) in the
vpx log for future text-path debugging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 07:48:04 -05:00
CydandClaude Fable 5 e02a346bb0 Emulator: VDB heads mirror plain-VGA modes (analog splitter pass-through)
The VDB is an analog splitter: the game's packed-16bpp trick routes the two
framebuffer bytes through the board palettes at 0x300/8/10, but any plain
VGA mode passes the S3's own DAC signal to every head -- no VDB register
traffic at all. The VGL_LABS test suite draws its secondary-display
patterns in 640x480x8 and programs nothing at 0x300-0x31A (verified from a
full session log), so the packed-only decode showed black heads. pal_draw
now branches: M_LIN8 decodes through the VGA DAC (full color on the color
heads, single-wire luminance on the mono MFD windows); 16bpp keeps the
packed VDB-palette decode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 07:30:09 -05:00
CydandClaude Fable 5 a6713ec1bd Deploy: package.ps1 stages ALL conf templates, not two by name
vwetest.conf.tmpl was never staged, so fresh installs had no conf for the
test mode ("vwetest.conf not found under root"). Glob deploy\*.conf.tmpl
and log the staged set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 07:06:42 -05:00
CydandClaude Fable 5 72da9b2f14 Emulator: parity-safe EMU8000 sample counter -- AWEUTIL runs; wire the
VGL_LABS cockpit test suite as pod-launch 'test' mode

The WC hang, root-caused in three layers (each necessary):
1. WC was rendered-samples + 100ms-capped interpolation; AWEUTIL's poll
   storm (~550k port-ops/s) starved the render thread of awe_lock, so the
   clock crawled ~90x slow. Fixed: free-running host-clock derivation +
   a fairness gate so the render thread can always take the lock.
2. Free-running at true 44.1kHz still failed: trapped port reads cost
   ~30us -- MORE than one 22.7us tick -- so consecutive reads skipped
   counter values, and AWEUTIL's WaitUntilWC (decoded at COM offset
   0x5F42) exits only on EQUALITY with a target tick: skipped value =
   missed target = 1.49s wrap penalty, or forever.
3. Advancing +1 per read still failed: WaitUntilWC reads WC TWICE per
   iteration, so its equality sample saw only every 2nd value -- wrong
   parity = infinite loop. Final semantics: during a poll storm the
   counter advances once per FOUR reads (every value observable by all
   of AWEUTIL's loop shapes; its 8192-unchanged-reads dead-clock bailout
   never trips), and resyncs to true wall time after any 50ms idle gap.
   Result: the full stock TEST.BAT (DIAGNOSE + AWEUTIL /S on both cards)
   completes in seconds.

Also: pod-launch 'test' mode -> vwetest.conf (stock TEST.BAT -> TSTALL),
DOSBox window defaults to 900,600 in test mode (the DOS screen is the
suite's UI), VWE_AWE_LOG gains storm bursts with guest cs:ip + caller and
rare-read tracing (the instrumentation that cracked this).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 23:51:52 -05:00
CydandClaude Opus 4.8 6dc017cb3d THE TEST PATTERN: cap7's bench scene recovered exactly (137 triangles)
tri_recover.py parses each payload's stride-0x10 edge groups {scale,A,B,C}
(GOODEQNS edgeize output), solves the three edge equations pairwise into true
vertices, and fills the triangles. cap7's bench = a triangulated calibration
grid + a multi-part test model + triangle strips, in global screen coords --
the image Division's engineers used to validate VelociRender boards, exact to
the compiled coefficients. The earlier 9x5 patch was one corner of this scene.
Readout gains §06 (test pattern) with trek/klng scenes moving to §07. The demo
captures' streak/point payloads use a different layout (parser refinement
pending) -- the bench is pure triangles and recovers perfectly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 22:35:17 -05:00
CydandClaude Opus 4.8 24ddbd4970 Second scene recovered: the Klingon vessel (klngvid)
recover_scene.py generalizes the recovery (dual-window dump -> chain walk ->
position/segment parse -> render) for any capture. Applied to klngvid: 139
content payload programs, 96 positions + 62 edge segments = a sparse starfield
with a dense edge tangle right-of-center -- a vessel in space. Readout §06 now
shows both recovered scenes (warp field + Klingon) side by side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 22:19:21 -05:00
CydandClaude Opus 4.8 26864c9945 THE WARP FIELD: trek's scene recovered from the frame's compiled payloads
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>
2026-07-16 22:12:48 -05:00
CydandClaude Opus 4.8 e3897a8e28 Correct §06: the emit map is the shared standard screen program
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>
2026-07-16 21:40:34 -05:00
CydandClaude Opus 4.8 c2db52e69b 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>
2026-07-16 21:26:40 -05:00
CydandClaude Opus 4.8 a7a59af9b4 Emit-stream capture + first span read-out from a live scene (trek)
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>
2026-07-16 20:50:43 -05:00
CydandClaude Opus 4.8 43d0258c0c emu860: implement i860 dual-instruction mode (DIM) delay-slot semantics
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>
2026-07-16 19:06:32 -05:00
CydandClaude Opus 4.8 60d4c2da84 Liveness probe: per-draw polygon-site counts for any capture
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>
2026-07-16 18:15:42 -05:00
CydandClaude Opus 4.8 cdc2c724db Capture triage: parse-only census of every wire capture
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>
2026-07-16 17:29:27 -05:00
CydandClaude Opus 4.8 a408871390 Confirm cap7 is a single-object recording (not a mission with terrain)
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>
2026-07-16 17:22:58 -05:00
CydandClaude Opus 4.8 0019770bea Resolve the frame: this death-cam draw IS the object (no terrain)
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>
2026-07-16 17:16:22 -05:00
CydandClaude Opus 4.8 533c14b102 Negative result: payload coord-extraction does not rebuild frame geometry
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>
2026-07-16 17:01:43 -05:00
CydandClaude Opus 4.8 d1e83c3445 Map the full-frame micro-code structure: double-buffered primitives
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>
2026-07-16 16:49:38 -05:00
CydandClaude Opus 4.8 0f9ff000c4 Decode plane constants: fixed-point screen coordinates
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>
2026-07-16 16:39:25 -05:00
CydandClaude Opus 4.8 09ea5f9802 Verify the edge decode: payload coeff matches geometry to 0.2%
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>
2026-07-16 16:09:07 -05:00
CydandClaude Opus 4.8 b78b14b7f9 Array: read out the depth buffer; show it in the readout §05
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>
2026-07-16 16:07:01 -05:00
CydandClaude Opus 4.8 943d6e1090 Decode the SENDE (z/color) micro-code instruction format
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>
2026-07-16 15:48:22 -05:00
CydandClaude Opus 4.8 34e2155672 Decode the IGC coefficient value encoding: bit-serial x2 place values
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>
2026-07-16 15:33:45 -05:00
CydandClaude Opus 4.8 90fd3497d1 Readout §02: real decoded DMA list + embedded-float payload finding
§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>
2026-07-16 15:24:03 -05:00
CydandClaude Opus 4.8 6c7e9cf1dc Decode the IGC DMA lists + find embedded float coeffs in SEND payloads
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>
2026-07-16 15:22:12 -05:00
CydandClaude Opus 4.8 7bd6af8c20 Readout: add the Tier-1 Pixel-Planes array section (§05)
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>
2026-07-16 15:06:57 -05:00
CydandClaude Opus 4.8 9d8de701f6 Tier 1: a faithful PXPL5 IGC Pixel-Planes array simulator
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>
2026-07-16 15:02:59 -05:00
CydandClaude Opus 4.8 e43cf24993 Decode the captured object: it's a complete 9x5 height-field surface
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>
2026-07-16 14:44:49 -05:00
CydandClaude Opus 4.8 32ac5ca9b8 Shaded frame: Gouraud raster of the i860's projected geometry
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>
2026-07-16 14:19:43 -05:00
CydandClaude Opus 4.8 6aaceaa312 firmware-decomp: capture_wireframe.py -- reconstruct the projected object as a
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>
2026-07-16 14:03:45 -05:00
CydandClaude Opus 4.8 9f6cd44333 i860 emu: pfgt/pfle exact semantics (R bit selects gt/le, pipelined retire+advance); IGC consumption resets page write-index for reuse
- 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>
2026-07-15 21:05:08 -05:00
CydandClaude Opus 4.8 170b35d812 emu_main: IGC drain-wait completion is NONZERO status, not zero (per-page done flag; loop exits when [page]!=0)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:05:13 -05:00
CydandClaude Opus 4.8 051544fed6 i860 emu: pipelined pftrunc/pfix through the adder pipe (raw-bits pipe entries)
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>
2026-07-15 17:53:59 -05:00
CydandClaude Opus 4.8 6991b68685 emu_main: IGC drain-wait hook (0xf0421510): firmware polls [r16] until the rasterizer consumes the region queue; we consume instantly
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:21:01 -05:00
CydandClaude Opus 4.8 4f143e3a26 i860 emu: EXACT pipelined FP model (MAME-validated) -- N-R divide passes
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>
2026-07-15 15:40:29 -05:00
CydandClaude Opus 4.8 58b9dcda2f emu_main: fix __init__ split by the intdiv-hook edit (self.heap was unreachable)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 14:01:41 -05:00