From b78b14b7f954612c66012a4479d1ec2b042ab96d Mon Sep 17 00:00:00 2001 From: Cyd Date: Thu, 16 Jul 2026 16:07:01 -0500 Subject: [PATCH] =?UTF-8?q?Array:=20read=20out=20the=20depth=20buffer;=20s?= =?UTF-8?q?how=20it=20in=20the=20readout=20=C2=A705?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- emulator/firmware-decomp/igc_array.py | 38 +++++++ emulator/firmware-decomp/render-readout.html | 105 ++++++++++++++----- 2 files changed, 119 insertions(+), 24 deletions(-) diff --git a/emulator/firmware-decomp/igc_array.py b/emulator/firmware-decomp/igc_array.py index 994d6de..a02b01b 100644 --- a/emulator/firmware-decomp/igc_array.py +++ b/emulator/firmware-decomp/igc_array.py @@ -161,6 +161,44 @@ class IGCArray: _wword(pix, G_BIT0, C_BITS, gv) _wword(pix, B_BIT0, C_BITS, bv) + def readout_depth(self, bg=(7, 11, 17)): + """Read the 24-bit z stored in pixel memory back into a depth image (near=bright). + This is the plane the SENDE z-sweep interpolates, straight out of pixel memory.""" + img = bytearray(self.W * self.H * 3) + for y in range(self.H): + o = y * self.W * 3 + for x in range(self.W): + img[o] = bg[0]; img[o + 1] = bg[1]; img[o + 2] = bg[2]; o += 3 + # gather the touched z range for contrast + zmin, zmax = Z_FAR, 0 + for t in self.tiles.values(): + if not t.touched: + continue + for pix in t.mem: + z = _rword(pix, Z_BIT0, Z_BITS) + if z != Z_FAR: + zmin = min(zmin, z); zmax = max(zmax, z) + span = (zmax - zmin) or 1 + for (tx, ty), t in self.tiles.items(): + if not t.touched: + continue + for ly in range(TILE_Y): + gy = t.y0 + ly + if gy >= self.H: + break + base = ly << TILE_X_BITS + for lx in range(TILE_X): + gx = t.x0 + lx + if gx >= self.W: + break + z = _rword(t.mem[base + lx], Z_BIT0, Z_BITS) + if z == Z_FAR: + continue + v = 1.0 - (z - zmin) / span # near = bright + o = (gy * self.W + gx) * 3 + img[o] = int(30 + v * 120); img[o + 1] = int(90 + v * 150); img[o + 2] = int(120 + v * 130) + return img + def readout(self, bg=(7, 11, 17)): """Read pixel memory back out into an RGB framebuffer (row-major, RGB bytes).""" img = bytearray(self.W * self.H * 3) diff --git a/emulator/firmware-decomp/render-readout.html b/emulator/firmware-decomp/render-readout.html index dc200fb..a3c3257 100644 --- a/emulator/firmware-decomp/render-readout.html +++ b/emulator/firmware-decomp/render-readout.html @@ -323,32 +323,37 @@ reference rasteriser to within edge anti-aliasing — the array reproduced faithfully.

- -
the object's footprint across the array — 18 of 50 tiles lit, - each a 64×128 region of pixel processors
+ +
the depth buffer, read straight out of pixel memory — the 24-bit + Z plane the SENDE z-sweep interpolates (near = bright)
+
+
+ +
the object's footprint — 18 of 50 64×128 tiles lit, + each a region of pixel processors
-
-
pixel memory · 26 bytes = 208 bits
-
- Z · 24bR·8G·8 - B·8enable + working bits -
-
    -
  • three edge trees → the enable register (the inside test)
  • -
  • Z / R / G / B are planes, evaluated per pixel by the linear tree
  • -
  • z-buffer via MEM2geMEM2; every write gated by enable
  • -
  • output read back out of pixel memory → framebuffer
  • -
  • validated pixel-faithful to the reference — ~1%, at edges only
  • -
-
-

What this is not, yet: execution of the compiled - micro-code the DMA actually ships (§02, right) — the form the ground and sky take, since - they carry no stored vertices. That stream is now partly decoded: the per-region command lists - read cleanly, and the SEND payloads turn out to carry the real float coefficients - interleaved with a regular bit-serial sweep — so what remains is pinning the control-word fields - and running that sweep, not reversing an opaque binary. This section is the array's - computational model: the pixels that micro-code produces.

+
+
pixel memory · 26 bytes = 208 bits
+
+ Z · 24bR·8G·8 + B·8enable + working bits +
+
    +
  • three edge trees → the enable register (the inside test)
  • +
  • Z / R / G / B are planes, evaluated per pixel by the linear tree
  • +
  • z-buffer via MEM2geMEM2; every write gated by enable
  • +
  • the depth image (left) is that Z plane, read back out of pixel memory
  • +
  • validated pixel-faithful to the reference — ~1%, at edges only
  • +
+
+

What this is not, yet: a full from-scratch run of + the compiled micro-code the DMA ships (§02) — the form the ground and sky take, since they + carry no stored vertices. But that stream is now largely decoded: the command lists read cleanly, + the SENDE z-sweep resolves into a regular 4-word instruction, and its + coefficients are the same ones driving this depth buffer — the array's inputs are + cross-validated against the hardware's own compiled stream. What remains is numeric + reconstruction across every region, not reversing an opaque binary.

@@ -636,6 +641,58 @@ } } + /* ---- depth buffer: the same surface, coloured by camera Z (the array's z-plane) ---- */ + (function(){ + var sc=document.getElementById('depth'); if(!sc) return; + var dx=sc.getContext('2d'); + var CW=sc.width, CH=sc.height, SS=2, W=CW*SS, H=CH*SS; + var XS=GRID.xs, ZS=GRID.zs, R=GRID.rows, NX=XS.length, NZ=ZS.length; + var cx=8, cz=-4, cy=0; + (function(){var s=0,n=0;for(var j=0;jmxx)mxx=p.X;if(p.Ymxy)mxy=p.Y; + if(p.Zmxz)mxz=p.Z;} + var pad=0.1*W, s=Math.min((W-2*pad)/(mxx-mnx),(H-2*pad)/(mxy-mny)); + var ox=(W-(mxx-mnx)*s)/2, oy=(H-(mxy-mny)*s)/2, zsp=(mxz-mnz)||1; + function SXp(p){return (p.X-mnx)*s+ox;} function SYp(p){return (mxy-p.Y)*s+oy;} + var dep=new Float32Array(W*H); dep.fill(-2); var zb=new Float32Array(W*H); for(var k=0;kzb[idx]){zb[idx]=z; dep[idx]=(z-mnz)/zsp;} + } + } + for(var j=0;j=0){acc+=v;cnt++;}} + var o=(y*CW+xx)*4; + if(cnt>0){var vv=1-acc/cnt,cov=cnt/(SS*SS); // near = bright + dd[o]=30+vv*120; dd[o+1]=90+vv*150; dd[o+2]=120+vv*130; dd[o+3]=Math.round(cov*255);} + else dd[o+3]=0; + } + dx.putImageData(img,0,0); + })(); + /* ---- 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(){