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>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""The captured object is a complete 9x5 height-field grid (verified: all 45 cells
|
||||
filled, x in {0..16 step 2}, z in {0..-8 step 2}, y = height). Reconstruct the FULL
|
||||
grid connectivity (2 tris per quad) and render a clean solid surface from a chosen
|
||||
3/4 view, Gouraud-lit by the captured normals. Real data; only the camera is ours."""
|
||||
import sys, pickle, math, json
|
||||
S = r'C:\Users\cyd\AppData\Local\Temp\claude\c--VWE-TeslaRel410\4e848c76-6e89-4034-8047-d8d491cb32d8\scratchpad'
|
||||
objs = pickle.load(open(S + r'\vfull.pkl', 'rb'))['objs']
|
||||
YAW = float(sys.argv[1]) if len(sys.argv) > 1 else 40.0
|
||||
PITCH = float(sys.argv[2]) if len(sys.argv) > 2 else 28.0
|
||||
OUT = sys.argv[3] if len(sys.argv) > 3 else 'gridsurf'
|
||||
|
||||
allv = [v for o in objs for v in o]
|
||||
xs = sorted(set(round(v['mx'], 2) for v in allv))
|
||||
zs = sorted(set(round(v['mz'], 2) for v in allv))
|
||||
grid = {(round(v['mx'], 2), round(v['mz'], 2)): v for v in allv}
|
||||
cx = sum(xs)/len(xs); cz = sum(zs)/len(zs)
|
||||
cy = sum(v['my'] for v in allv)/len(allv)
|
||||
|
||||
ry = math.radians(YAW); rp = math.radians(PITCH)
|
||||
cyw, syw = math.cos(ry), math.sin(ry); cp, sp = math.cos(rp), math.sin(rp)
|
||||
def rot(x, y, z):
|
||||
x, z = x*cyw + z*syw, -x*syw + z*cyw
|
||||
y, z = y*cp - z*sp, y*sp + z*cp
|
||||
return x, y, z
|
||||
def _n(a, b, c):
|
||||
m = math.sqrt(a*a+b*b+c*c) or 1.0
|
||||
return a/m, b/m, c/m
|
||||
LIGHT = _n(0.35, 0.55, 0.72)
|
||||
|
||||
# rotate every grid vertex + normal, cache projected + intensity
|
||||
P = {}
|
||||
for (x, z), v in grid.items():
|
||||
X, Y, Z = rot(v['mx']-cx, (v['my']-cy)*1.8, v['mz']-cz) # exaggerate height 1.8x for relief
|
||||
nx, ny, nz = rot(v['nx'], v['ny'], v['nz'])
|
||||
n = _n(nx, ny, nz)
|
||||
d = n[0]*LIGHT[0] + n[1]*LIGHT[1] + n[2]*LIGHT[2]
|
||||
inten = max(0.16, min(1.0, 0.22 + 0.85*abs(d)))
|
||||
P[(x, z)] = [X, Y, Z, inten]
|
||||
|
||||
allp = list(P.values())
|
||||
mnx = min(p[0] for p in allp); mxx = max(p[0] for p in allp)
|
||||
mny = min(p[1] for p in allp); mxy = max(p[1] for p in allp)
|
||||
spanx = (mxx-mnx) or 1; spany = (mxy-mny) or 1
|
||||
IW, IH = 620, 560
|
||||
PADf = 0.1
|
||||
sc = min(IW*(1-2*PADf)/spanx, IH*(1-2*PADf)/spany)
|
||||
oxp = (IW - spanx*sc)/2; oyp = (IH - spany*sc)/2
|
||||
def SX(p): return int((p[0]-mnx)*sc + oxp)
|
||||
def SY(p): return int((mxy-p[1])*sc + oyp) # flip Y (model up -> screen up)
|
||||
|
||||
fb = [[0.0]*IW for _ in range(IH)]
|
||||
zb = [[-1e9]*IW for _ in range(IH)]
|
||||
def tri(a, b, c):
|
||||
ax, ay, bx, by, cx2, cy2 = SX(a), SY(a), SX(b), SY(b), SX(c), SY(c)
|
||||
ia, ib, ic = a[3], b[3], c[3]; za, zbv, zc = a[2], b[2], c[2]
|
||||
area = (bx-ax)*(cy2-ay) - (cx2-ax)*(by-ay)
|
||||
if abs(area) < 1e-6: return
|
||||
x0 = max(0, min(ax, bx, cx2)); x1 = min(IW-1, max(ax, bx, cx2))
|
||||
y0 = max(0, min(ay, by, cy2)); y1 = min(IH-1, max(ay, by, cy2))
|
||||
for py in range(y0, y1+1):
|
||||
for px in range(x0, x1+1):
|
||||
w0 = ((bx-px)*(cy2-py) - (cx2-px)*(by-py))/area
|
||||
w1 = ((cx2-px)*(ay-py) - (ax-px)*(cy2-py))/area
|
||||
w2 = 1 - w0 - w1
|
||||
if w0 < -0.004 or w1 < -0.004 or w2 < -0.004: continue
|
||||
z = w0*za + w1*zbv + w2*zc
|
||||
if z > zb[py][px]:
|
||||
zb[py][px] = z; fb[py][px] = w0*ia + w1*ib + w2*ic
|
||||
|
||||
nt = 0
|
||||
for i in range(len(xs)-1):
|
||||
for j in range(len(zs)-1):
|
||||
a = P[(xs[i], zs[j])]; b = P[(xs[i+1], zs[j])]
|
||||
c = P[(xs[i], zs[j+1])]; d = P[(xs[i+1], zs[j+1])]
|
||||
tri(a, b, c); tri(b, d, c); nt += 2
|
||||
print("yaw=%.0f pitch=%.0f grid %dx%d %d tris" % (YAW, PITCH, len(xs), len(zs), nt))
|
||||
|
||||
ramp = " .:-=+*#%@"
|
||||
for ry2 in range(0, IH, IH//38):
|
||||
line = " "
|
||||
for rx in range(0, IW, IW//74):
|
||||
v = fb[ry2][rx]; line += ramp[min(9, int(v*9.99))] if v > 0 else ' '
|
||||
print(line)
|
||||
|
||||
img = bytearray(IW*IH*3)
|
||||
for y in range(IH):
|
||||
for x in range(IW):
|
||||
v = fb[y][x]
|
||||
if v > 0:
|
||||
r = int(min(255, 28+v*168)); g = int(min(255, 50+v*205)); b = int(min(255, 54+v*122))
|
||||
else:
|
||||
r, g, b = 7, 11, 17
|
||||
o = (y*IW+x)*3; img[o], img[o+1], img[o+2] = r, g, b
|
||||
open(S + '\\' + OUT + '.ppm', 'wb').write(b'P6\n%d %d\n255\n' % (IW, IH) + bytes(img))
|
||||
print("wrote %s.ppm (%dx%d)" % (OUT, IW, IH))
|
||||
@@ -44,6 +44,15 @@
|
||||
font-family:var(--mono);font-size:11.5px;letter-spacing:.1em;color:var(--mute);
|
||||
text-transform:uppercase;line-height:1.5}
|
||||
.hero-render .cap b{color:var(--phos)}
|
||||
.duo{display:grid;grid-template-columns:1.5fr 1fr;gap:14px;margin-top:18px}
|
||||
@media(max-width:640px){.duo{grid-template-columns:1fr}}
|
||||
.duo figure{margin:0;border:1px solid var(--line2);border-radius:10px;overflow:hidden;
|
||||
background:radial-gradient(130% 110% at 50% 12%, #0a141c, #05090d)}
|
||||
.duo canvas{display:block;width:100%;height:auto}
|
||||
.duo figcaption{padding:10px 13px;font-family:var(--mono);font-size:11px;
|
||||
letter-spacing:.05em;color:var(--mute);line-height:1.5;border-top:1px solid var(--line)}
|
||||
.duo figcaption b{color:var(--phos)}
|
||||
|
||||
.hero-render .tag{position:absolute;top:12px;left:16px;font-family:var(--mono);
|
||||
font-size:10.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--faint);
|
||||
border:1px solid var(--line2);border-radius:5px;padding:4px 9px;z-index:2}
|
||||
@@ -152,15 +161,16 @@
|
||||
<p class="lede">A cycle-faithful Intel i860 interpreter now runs the game's
|
||||
<b>actual shipped firmware</b> — booted, initialised, and fed a complete recorded
|
||||
mission over the wire. It replays every command, emits the real
|
||||
<b>PXPL5 IGC render stream</b>, and projects the scene geometry. Below is the object
|
||||
it drew, pulled straight off the emulated chip and <b>shaded from its own vertex
|
||||
normals</b> — the first frame from this board's firmware in thirty years.</p>
|
||||
<b>PXPL5 IGC render stream</b>, and projects the scene geometry. Below is an object
|
||||
it drew, its vertices and normals pulled straight off the emulated chip and
|
||||
<b>reconstructed in 3D</b> — geometry this board's firmware has not computed in thirty years.</p>
|
||||
|
||||
<div class="hero-render">
|
||||
<span class="tag">software-rasterised · not the hardware shader</span>
|
||||
<canvas id="shaded" width="900" height="620" aria-label="shaded render of the projected mech"></canvas>
|
||||
<div class="cap"><b>the object in cap7's death-camera view</b> — Gouraud-shaded from the
|
||||
firmware's projected vertices and per-vertex normals, pulled live off the emulated i860.</div>
|
||||
<span class="tag">reconstructed · firmware geometry + normals</span>
|
||||
<canvas id="shaded" width="880" height="560" aria-label="the object rebuilt as a 3D height-field surface"></canvas>
|
||||
<div class="cap"><b>the object cap7 was drawing</b> — its 45 projected vertices resolve into a
|
||||
complete 9×5 height-field surface, rebuilt here in true 3D and lit by the board's own
|
||||
per-vertex normals. Pulled live off the emulated i860.</div>
|
||||
</div>
|
||||
|
||||
<div class="scope">
|
||||
@@ -242,21 +252,24 @@
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2><span class="idx">04</span> The object it draws — wireframe</h2>
|
||||
<p class="sub">One step upstream of the micro-code, the firmware transforms each model
|
||||
through its matrix and writes <b>screen-space vertices</b>, organised as
|
||||
<span class="mono">VSTRIP</span> triangle strips. Below is a wireframe reconstructed
|
||||
<b>live from the emulated i860</b> — the actual object in cap7's death-camera view,
|
||||
four strips, 45 vertices, exactly as the board's firmware projected it. This is the
|
||||
real geometry the Pixel-Planes array was about to shade.</p>
|
||||
<div class="binwrap">
|
||||
<canvas id="verts" width="620" height="440" aria-label="projected triangle-strip wireframe"></canvas>
|
||||
<div class="legend">
|
||||
<span><i style="background:var(--phos)"></i>strip edge</span>
|
||||
<span><i style="background:var(--cyan);opacity:.55"></i>triangle cross-edge</span>
|
||||
<span><i style="background:#eaffef"></i>projected vertex</span>
|
||||
<span style="color:var(--faint)">4 strips · 45 vertices · i860 output</span>
|
||||
</div>
|
||||
<h2><span class="idx">04</span> How the death-camera saw it</h2>
|
||||
<p class="sub">The surface above is the same 45 vertices, sorted back into model space —
|
||||
they form an exact <b>9 × 5 height-field grid</b> (x and z in even 2-unit steps, y the
|
||||
height at every node; all 45 cells filled). What the board actually wrote to screen is
|
||||
below: cap7's death-camera views that surface nearly <b>edge-on</b>, so the frame the i860
|
||||
projected is a thin, folded sliver. It is authentic output — just an awkward angle. Left,
|
||||
that projection Gouraud-shaded; right, its raw <span class="mono">VSTRIP</span> wireframe.</p>
|
||||
<div class="duo">
|
||||
<figure>
|
||||
<canvas id="surf" width="500" height="560" aria-label="the death-camera's edge-on shaded projection"></canvas>
|
||||
<figcaption><b>the board's screen-space frame</b> — Gouraud-shaded,
|
||||
grazing near-edge-on angle</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<canvas id="verts" width="420" height="560" aria-label="raw VSTRIP wireframe"></canvas>
|
||||
<figcaption><b>raw VSTRIP wireframe</b> — 4 strips, 45 vertices,
|
||||
exactly as the i860 emitted them</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -285,13 +298,13 @@
|
||||
<li>Scene graph, transform, frustum classify on real geometry</li>
|
||||
<li>Real PXPL5 IGC opcode stream emitted and decoded (<b>SEND / TILE / FLUSH</b>)</li>
|
||||
<li>Geometry binned into <b>191 screen regions</b></li>
|
||||
<li>Object <b>wireframe reconstructed</b> from the projected VSTRIP strips (above)</li>
|
||||
<li>Object <b>recovered and shaded</b> — a 9×5 height-field surface, lit by the firmware's own normals (above)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>The last mile to pixels</h3>
|
||||
<ul class="todo">
|
||||
<li>The wireframe is here — a <b>shaded, textured</b> frame is what remains</li>
|
||||
<li>Our software-shaded frame is here — the <b>board's own hardware-shaded, textured</b> frame is what remains</li>
|
||||
<li>The SEND payloads are <b>bit-serial Pixel-Planes micro-code</b>, not stored triangles</li>
|
||||
<li>A shaded frame needs the <b>128×64 pixel-processor array simulator</b></li>
|
||||
<li>The model is known — <span class="mono">IGCOPS.C</span>: <span class="mono">eval_ltree = Ax+By+C</span>, an enable register, bit-serial MEM ops</li>
|
||||
@@ -407,7 +420,7 @@
|
||||
[[245.1,15.5,0.961,0.384,0.192,-4],[239.9,40.3,0.941,0.341,0.171,-4],[238.5,93,0.935,0.446,0.223,-4],[234.4,62,0.919,0.643,0.216,-4],[239.1,0,0.938,0.645,0.424,-4],[231.9,0,0.909,0.951,0.539,-4],[240.7,0,0.944,0.984,0.685,-4],[233.5,40.3,0.916,0.263,0.132,-2],[254.6,24.8,0.998,0.415,0.208,-2],[243.2,0,0.954,0.461,0.231,-2],[233.8,12.4,0.917,0.431,0.215,-2],[243.2,15.5,0.954,0.461,0.231,-2],[254.3,0,0.997,0.614,0.207,-2],[250.4,9.3,0.982,0.64,0.501,-2],[249.6,12.4,0.979,0.94,0.58,-2],[245.7,0,0.964,0.939,0.723,-2]],
|
||||
[[126.1,62,0.494,0.914,0.206,0],[238.8,49.6,0.937,0.447,0.223,0],[239.1,62,0.938,0.448,0.224,0],[250.4,40.3,0.982,0.402,0.201,0],[234.1,31,0.918,0.432,0.216,0],[250.4,43.4,0.982,0.402,0.201,0],[231.3,46.5,0.907,0.639,0.37,0],[254.9,37.2,0.999,0.916,0.508,0],[252.7,34.1,0.991,0.926,0.663,0]]];
|
||||
(function(){
|
||||
var sc=document.getElementById('shaded'), sctx=sc.getContext('2d');
|
||||
var sc=document.getElementById('surf'), sctx=sc.getContext('2d');
|
||||
var CW=sc.width, CH=sc.height, SS=2, W=CW*SS, H=CH*SS;
|
||||
var inten=new Float32Array(W*H), zb=new Float32Array(W*H);
|
||||
for(var i=0;i<W*H;i++) zb[i]=-1e9;
|
||||
@@ -454,6 +467,71 @@
|
||||
sctx.putImageData(img,0,0);
|
||||
})();
|
||||
|
||||
/* ---- true-3D surface: the object rebuilt as its 9x5 height-field grid ----
|
||||
xs/zs = model grid axes; rows[z][x] = [heightY, nx,ny,nz]. All 45 cells real,
|
||||
captured off the i860. We reconstruct the grid connectivity (2 tris/quad) and
|
||||
light it with the firmware's own normals from a clean 3/4 camera. */
|
||||
var GRID={"xs":[0,2,4,6,8,10,12,14,16],"zs":[-8,-6,-4,-2,0],"rows":[[[-2.167,0.948,0.98,0.65],[-1.295,0.952,0.943,0.517],[-1.439,0.932,0.263,0.431],[-1.591,0.935,0.291,0.146],[-1.87,0.93,0.249,0.124],[-2.311,0.931,0.26,0.13],[-2.014,0.945,0.372,0.186],[-2.446,0.933,0.278,0.139],[-3.021,0.931,0.262,0.131]],[[-2.158,0.927,0.936,0.678],[-1.016,0.968,0.957,0.685],[-0.584,0.966,0.554,0.377],[-1.007,0.95,0.417,0.209],[-1.016,0.95,0.415,0.208],[-1.726,0.933,0.278,0.139],[-1.735,0.937,0.307,0.154],[-2.023,0.928,0.231,0.116],[-2.589,0.922,0.187,0.094]],[[-2.158,0.944,0.984,0.685],[-1.735,0.909,0.951,0.539],[-1.87,0.938,0.645,0.424],[-1.87,0.919,0.643,0.216],[-1.448,0.935,0.446,0.223],[-1.582,0.941,0.341,0.171],[-1.16,0.961,0.384,0.192],[-1.016,0.988,0.324,0.162],[-1.448,0.995,0.246,0.123]],[[-2.446,0.964,0.939,0.723],[-2.598,0.979,0.94,0.58],[-2.733,0.982,0.64,0.501],[-2.455,0.997,0.614,0.207],[-1.735,0.954,0.461,0.231],[-1.448,0.917,0.431,0.215],[-0.872,0.954,0.461,0.231],[-0.441,0.998,0.415,0.208],[-0.719,0.916,0.263,0.132]],[[-3.021,0.991,0.926,0.663],[-3.021,0.999,0.916,0.508],[-3.165,0.907,0.639,0.37],[-2.589,0.982,0.402,0.201],[-1.87,0.918,0.432,0.216],[-1.726,0.982,0.402,0.201],[-1.151,0.938,0.448,0.224],[-0.432,0.937,0.447,0.223],[0,0.494,0.914,0.206]]]};
|
||||
(function(){
|
||||
var sc=document.getElementById('shaded'), sx=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; // grid centre
|
||||
(function(){var s=0,n=0;for(var j=0;j<NZ;j++)for(var i=0;i<NX;i++){s+=R[j][i][0];n++;}cy=s/n;})();
|
||||
function nrm(a,b,c){var m=Math.sqrt(a*a+b*b+c*c)||1;return [a/m,b/m,c/m];}
|
||||
var L=nrm(0.35,0.55,0.72);
|
||||
var YAW=40*Math.PI/180, PIT=27*Math.PI/180;
|
||||
var cyw=Math.cos(YAW),syw=Math.sin(YAW),cp=Math.cos(PIT),sp=Math.sin(PIT);
|
||||
function rot(x,y,z){var x2=x*cyw+z*syw, z2=-x*syw+z*cyw; var y2=y*cp-z2*sp, z3=y*sp+z2*cp; return [x2,y2,z3];}
|
||||
// project every grid node
|
||||
var P=[];
|
||||
for(var j=0;j<NZ;j++){P[j]=[];for(var i=0;i<NX;i++){
|
||||
var c=R[j][i];
|
||||
var p=rot(XS[i]-cx,(c[0]-cy)*1.8,ZS[j]-cz);
|
||||
var nn=rot(c[1],c[2],c[3]); nn=nrm(nn[0],nn[1],nn[2]);
|
||||
var d=nn[0]*L[0]+nn[1]*L[1]+nn[2]*L[2];
|
||||
var inten=Math.max(0.16,Math.min(1,0.22+0.85*Math.abs(d)));
|
||||
P[j][i]={X:p[0],Y:p[1],Z:p[2],I:inten};
|
||||
}}
|
||||
var mnx=1e9,mxx=-1e9,mny=1e9,mxy=-1e9;
|
||||
for(var j=0;j<NZ;j++)for(var i=0;i<NX;i++){var p=P[j][i];
|
||||
if(p.X<mnx)mnx=p.X;if(p.X>mxx)mxx=p.X;if(p.Y<mny)mny=p.Y;if(p.Y>mxy)mxy=p.Y;}
|
||||
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;
|
||||
function SXp(p){return (p.X-mnx)*s+ox;}
|
||||
function SYp(p){return (mxy-p.Y)*s+oy;}
|
||||
var inten=new Float32Array(W*H), zb=new Float32Array(W*H);
|
||||
for(var k=0;k<W*H;k++)zb[k]=-1e9;
|
||||
function tri(a,b,c){
|
||||
var ax=SXp(a),ay=SYp(a),bx=SXp(b),by=SYp(b),cx2=SXp(c),cy2=SYp(c);
|
||||
var area=(bx-ax)*(cy2-ay)-(cx2-ax)*(by-ay); if(Math.abs(area)<1e-6)return;
|
||||
var x0=Math.max(0,Math.floor(Math.min(ax,bx,cx2))),x1=Math.min(W-1,Math.ceil(Math.max(ax,bx,cx2)));
|
||||
var y0=Math.max(0,Math.floor(Math.min(ay,by,cy2))),y1=Math.min(H-1,Math.ceil(Math.max(ay,by,cy2)));
|
||||
for(var py=y0;py<=y1;py++)for(var px=x0;px<=x1;px++){
|
||||
var w0=((bx-px)*(cy2-py)-(cx2-px)*(by-py))/area;
|
||||
var w1=((cx2-px)*(ay-py)-(ax-px)*(cy2-py))/area;
|
||||
var w2=1-w0-w1; if(w0<-0.004||w1<-0.004||w2<-0.004)continue;
|
||||
var z=w0*a.Z+w1*b.Z+w2*c.Z, idx=py*W+px;
|
||||
if(z>zb[idx]){zb[idx]=z; inten[idx]=w0*a.I+w1*b.I+w2*c.I;}
|
||||
}
|
||||
}
|
||||
for(var j=0;j<NZ-1;j++)for(var i=0;i<NX-1;i++){
|
||||
var a=P[j][i],b=P[j][i+1],c=P[j+1][i],d=P[j+1][i+1];
|
||||
tri(a,b,c); tri(b,d,c);
|
||||
}
|
||||
var img=sx.createImageData(CW,CH), dd=img.data;
|
||||
for(var y=0;y<CH;y++)for(var xx=0;xx<CW;xx++){
|
||||
var acc=0,cnt=0;
|
||||
for(var yy=0;yy<SS;yy++)for(var xs2=0;xs2<SS;xs2++){var v=inten[(y*SS+yy)*W+(xx*SS+xs2)];if(v>0){acc+=v;cnt++;}}
|
||||
var o=(y*CW+xx)*4;
|
||||
if(cnt>0){var vv=acc/cnt,cov=cnt/(SS*SS);
|
||||
dd[o]=Math.min(255,28+vv*168);dd[o+1]=Math.min(255,50+vv*205);
|
||||
dd[o+2]=Math.min(255,54+vv*122);dd[o+3]=Math.round(cov*255);}
|
||||
else dd[o+3]=0;
|
||||
}
|
||||
sx.putImageData(img,0,0);
|
||||
})();
|
||||
|
||||
/* ---- region binning grid from the real descriptor scan ---- */
|
||||
// 96x2 pass, 191/192 filled: the single empty cell recorded in the capture.
|
||||
var bc = document.getElementById('bins'), bx = bc.getContext('2d');
|
||||
|
||||
Reference in New Issue
Block a user