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>
780 lines
62 KiB
HTML
780 lines
62 KiB
HTML
<title>VelociRender — firmware render output</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<style>
|
||
:root{
|
||
--ground:#070b10; --panel:#0c141c; --panel2:#101c26;
|
||
--line:#1c2e3a; --line2:#2a4150;
|
||
--phos:#41ff8e; --phos-dim:#1f6e46;
|
||
--amber:#ffb020; --cyan:#5fd0ff; --red:#ff5a6a;
|
||
--ink:#cfe0ea; --mute:#8093a2; --faint:#54697a;
|
||
--mono:ui-monospace,"Cascadia Code","SF Mono",Menlo,Consolas,monospace;
|
||
--sans:"Bahnschrift","DIN Alternate","Segoe UI",system-ui,sans-serif;
|
||
--body:"Segoe UI",system-ui,-apple-system,sans-serif;
|
||
}
|
||
*{box-sizing:border-box}
|
||
html{-webkit-text-size-adjust:100%}
|
||
body{
|
||
margin:0;background:var(--ground);color:var(--ink);
|
||
font-family:var(--body);line-height:1.6;
|
||
background-image:
|
||
radial-gradient(1200px 600px at 70% -10%, rgba(65,255,142,.05), transparent 60%),
|
||
repeating-linear-gradient(0deg, rgba(255,255,255,.014) 0 1px, transparent 1px 3px);
|
||
}
|
||
.wrap{max-width:1080px;margin:0 auto;padding:clamp(20px,5vw,56px) clamp(16px,4vw,40px) 80px}
|
||
.eyebrow{font-family:var(--mono);font-size:12px;letter-spacing:.28em;text-transform:uppercase;
|
||
color:var(--phos);display:flex;align-items:center;gap:10px}
|
||
.eyebrow::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--phos);
|
||
box-shadow:0 0 10px var(--phos);animation:blink 2.4s infinite}
|
||
@keyframes blink{0%,72%,100%{opacity:1}80%,92%{opacity:.25}}
|
||
@media (prefers-reduced-motion:reduce){.eyebrow::before{animation:none}}
|
||
|
||
h1{font-family:var(--sans);font-weight:700;letter-spacing:.01em;line-height:1.02;
|
||
font-size:clamp(34px,7vw,68px);margin:.35em 0 .15em;text-wrap:balance;
|
||
text-transform:uppercase}
|
||
h1 .b{color:var(--phos)}
|
||
.lede{font-size:clamp(16px,2.2vw,20px);color:var(--ink);max-width:60ch;margin:0}
|
||
.lede b{color:var(--cyan);font-weight:600}
|
||
|
||
.hero-render{margin:30px 0 14px;border:1px solid var(--line2);border-radius:12px;
|
||
overflow:hidden;background:radial-gradient(140% 120% at 50% 0%, #0a141c, #05090d);
|
||
position:relative}
|
||
.hero-render canvas{display:block;width:100%;height:auto;max-height:460px;
|
||
image-rendering:auto}
|
||
.hero-render .cap{position:absolute;left:16px;bottom:12px;right:16px;
|
||
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)}
|
||
|
||
.arr{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:18px;align-items:start}
|
||
@media(max-width:640px){.arr{grid-template-columns:1fr}}
|
||
.arr figure{margin:0;border:1px solid var(--line2);border-radius:10px;overflow:hidden;
|
||
background:linear-gradient(180deg,#05090d,#070d13)}
|
||
.arr canvas{display:block;width:100%;height:auto}
|
||
.arr figcaption{padding:10px 13px;font-family:var(--mono);font-size:11px;color:var(--mute);
|
||
line-height:1.5;border-top:1px solid var(--line)}
|
||
.arr figcaption b{color:var(--phos)}
|
||
.arrinfo{display:flex;flex-direction:column;gap:14px;padding-top:4px}
|
||
.pmlab{font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;
|
||
color:var(--faint)}
|
||
.pmbar{display:flex;gap:2px;height:34px;font-family:var(--mono);font-size:10px;font-weight:600}
|
||
.pmbar span{display:flex;align-items:center;justify-content:center;border-radius:3px;
|
||
color:#04070a;letter-spacing:.02em;white-space:nowrap;overflow:hidden}
|
||
.pmbar .z{flex:24;background:var(--cyan)}
|
||
.pmbar .r{flex:8;background:#ff6b6b}
|
||
.pmbar .g{flex:8;background:var(--phos)}
|
||
.pmbar .b{flex:8;background:#6ba8ff}
|
||
.pmbar .free{flex:160;background:#12202a;color:var(--faint)}
|
||
.mini{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:8px}
|
||
.mini li{position:relative;padding-left:18px;font-size:13.5px;color:var(--mute);line-height:1.45}
|
||
.mini li::before{content:"›";position:absolute;left:2px;color:var(--cyan);font-weight:700}
|
||
.mini li b{color:var(--ink)}
|
||
|
||
.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}
|
||
.hero-render::after{content:"";position:absolute;inset:0;pointer-events:none;
|
||
background:repeating-linear-gradient(180deg,rgba(0,0,0,0) 0 2px,rgba(0,0,0,0.16) 2px 3px),
|
||
radial-gradient(120% 100% at 50% 42%, rgba(0,0,0,0) 58%, rgba(3,5,8,0.66) 100%);
|
||
mix-blend-mode:multiply}
|
||
|
||
.scope{margin:14px 0 8px;border:1px solid var(--line);border-radius:10px;overflow:hidden;
|
||
background:linear-gradient(180deg,#04070a,#060d13);position:relative}
|
||
.scope canvas{display:block;width:100%;height:120px}
|
||
.scope .cap{position:absolute;left:14px;bottom:9px;font-family:var(--mono);font-size:10.5px;
|
||
letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
|
||
.scope .cap b{color:var(--phos-dim)}
|
||
|
||
.stat-row{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:26px 0 8px}
|
||
@media (max-width:640px){.stat-row{grid-template-columns:repeat(2,1fr)}}
|
||
.stat{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:14px 16px}
|
||
.stat .n{font-family:var(--mono);font-size:clamp(22px,3.4vw,30px);font-weight:600;
|
||
color:var(--phos);font-variant-numeric:tabular-nums;line-height:1}
|
||
.stat .l{font-size:12px;color:var(--mute);margin-top:7px;letter-spacing:.02em}
|
||
|
||
section{margin-top:52px}
|
||
h2{font-family:var(--sans);text-transform:uppercase;letter-spacing:.05em;font-weight:700;
|
||
font-size:clamp(20px,3vw,27px);margin:0 0 6px;display:flex;align-items:baseline;gap:12px}
|
||
h2 .idx{font-family:var(--mono);font-size:13px;color:var(--phos-dim);font-weight:400}
|
||
.sub{color:var(--mute);max-width:64ch;margin:0 0 22px}
|
||
|
||
/* pipeline signal chain */
|
||
.chain{display:flex;flex-wrap:wrap;gap:0;border:1px solid var(--line);border-radius:10px;
|
||
overflow:hidden;background:var(--panel)}
|
||
.stage{flex:1 1 150px;min-width:150px;padding:16px 16px;border-right:1px solid var(--line);
|
||
position:relative}
|
||
.stage:last-child{border-right:0}
|
||
.stage .k{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;
|
||
color:var(--faint)}
|
||
.stage .v{font-family:var(--sans);text-transform:uppercase;font-size:15px;font-weight:600;
|
||
margin:6px 0 4px;letter-spacing:.02em}
|
||
.stage .d{font-size:12.5px;color:var(--mute)}
|
||
.stage.on .v{color:var(--phos)}
|
||
.stage.wait .v{color:var(--amber)}
|
||
.stage.sim .v{color:var(--cyan)}
|
||
.stage .dot{position:absolute;top:14px;right:14px;width:7px;height:7px;border-radius:50%}
|
||
.stage.on .dot{background:var(--phos);box-shadow:0 0 8px var(--phos)}
|
||
.stage.wait .dot{background:var(--amber);box-shadow:0 0 8px var(--amber)}
|
||
.stage.sim .dot{background:var(--cyan);box-shadow:0 0 8px var(--cyan)}
|
||
|
||
/* two-column data */
|
||
.cols{display:grid;grid-template-columns:1.05fr .95fr;gap:20px}
|
||
@media (max-width:760px){.cols{grid-template-columns:1fr}}
|
||
|
||
.term{background:#05090d;border:1px solid var(--line2);border-radius:10px;overflow:hidden}
|
||
.term .bar{display:flex;align-items:center;gap:8px;padding:9px 14px;border-bottom:1px solid var(--line);
|
||
font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
|
||
.term .bar .d{width:9px;height:9px;border-radius:50%;background:var(--line2)}
|
||
.term .bar .d.g{background:var(--phos)}
|
||
.term pre{margin:0;padding:14px 16px;font-family:var(--mono);font-size:12.5px;line-height:1.75;
|
||
overflow-x:auto;color:var(--ink)}
|
||
.term .op{color:var(--phos)}
|
||
.term .ad{color:var(--cyan)}
|
||
.term .cm{color:var(--faint)}
|
||
.term .am{color:var(--amber)}
|
||
|
||
.binwrap{background:var(--panel);border:1px solid var(--line2);border-radius:10px;padding:16px}
|
||
.binwrap canvas{display:block;width:100%;height:auto;image-rendering:pixelated;
|
||
border:1px solid var(--line);border-radius:4px;background:#04070a}
|
||
.legend{display:flex;gap:18px;margin-top:12px;font-family:var(--mono);font-size:11px;
|
||
letter-spacing:.06em;color:var(--mute);flex-wrap:wrap}
|
||
.legend i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:6px;
|
||
vertical-align:-1px}
|
||
|
||
.fixlist{border-top:1px solid var(--line);margin-top:8px}
|
||
.fix{display:grid;grid-template-columns:150px 1fr;gap:16px;padding:14px 2px;
|
||
border-bottom:1px solid var(--line);align-items:baseline}
|
||
@media (max-width:640px){.fix{grid-template-columns:1fr;gap:4px}}
|
||
.fix .t{font-family:var(--mono);font-size:12.5px;color:var(--cyan);letter-spacing:.02em}
|
||
.fix .x{font-size:14px;color:var(--ink)}
|
||
.fix .x b{color:var(--phos);font-weight:600}
|
||
|
||
.status{margin-top:52px;border:1px solid var(--line2);border-radius:12px;overflow:hidden}
|
||
.status .h{display:flex;align-items:center;gap:12px;padding:16px 20px;
|
||
background:linear-gradient(180deg,var(--panel2),var(--panel));border-bottom:1px solid var(--line)}
|
||
.status .h .tag{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;
|
||
padding:4px 9px;border-radius:5px;border:1px solid var(--phos-dim);color:var(--phos)}
|
||
.status .h .tag.next{border-color:var(--line2);color:var(--amber)}
|
||
.status .h .t{font-family:var(--sans);text-transform:uppercase;font-weight:700;letter-spacing:.03em;font-size:16px}
|
||
.status .b{padding:18px 20px;display:grid;grid-template-columns:1fr 1fr;gap:22px}
|
||
@media (max-width:640px){.status .b{grid-template-columns:1fr}}
|
||
.status .b h3{font-size:12px;text-transform:uppercase;letter-spacing:.14em;color:var(--faint);
|
||
font-family:var(--mono);margin:0 0 10px;font-weight:400}
|
||
.status ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:9px}
|
||
.status li{font-size:14px;color:var(--ink);padding-left:22px;position:relative}
|
||
.status li::before{position:absolute;left:0;top:1px;font-family:var(--mono);font-size:13px}
|
||
.status .done li::before{content:"✓";color:var(--phos)}
|
||
.status .todo li::before{content:"→";color:var(--amber)}
|
||
.status li b{color:var(--cyan);font-weight:600}
|
||
|
||
footer{margin-top:48px;padding-top:20px;border-top:1px solid var(--line);
|
||
font-family:var(--mono);font-size:11.5px;letter-spacing:.08em;color:var(--faint);
|
||
display:flex;justify-content:space-between;flex-wrap:wrap;gap:10px}
|
||
.mono{font-family:var(--mono)}
|
||
a{color:var(--cyan)}
|
||
</style>
|
||
|
||
<div class="wrap">
|
||
<div class="eyebrow">VWE TESLA · Division VelociRender · i860 firmware emulator</div>
|
||
<h1>The board's own firmware<br>is <span class="b">rendering</span> again</h1>
|
||
<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 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">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">
|
||
<canvas id="scope" width="1040" height="120"></canvas>
|
||
<div class="cap"><b>trace ·</b> i860 execution → IGC command DMA</div>
|
||
</div>
|
||
|
||
<div class="stat-row">
|
||
<div class="stat"><div class="n">26,422</div><div class="l">wire commands replayed (whole mission)</div></div>
|
||
<div class="stat"><div class="n">8,562</div><div class="l">draw_scene frames processed</div></div>
|
||
<div class="stat"><div class="n">191<span style="color:var(--faint)">/192</span></div><div class="l">screen regions binned with geometry</div></div>
|
||
<div class="stat"><div class="n">0</div><div class="l">unimplemented ops remaining on the path</div></div>
|
||
</div>
|
||
|
||
<section>
|
||
<h2><span class="idx">01</span> The render signal chain</h2>
|
||
<p class="sub">Nothing here is re-interpreted by us. Each stage is the firmware's own
|
||
code executing on the emulated i860 — from parsing the wire packet to writing
|
||
the bit-serial micro-program the Pixel-Planes array runs.</p>
|
||
<div class="chain">
|
||
<div class="stage on"><span class="dot"></span><div class="k">in</div><div class="v">Wire</div><div class="d">dN_receive → 42-action dispatch</div></div>
|
||
<div class="stage on"><span class="dot"></span><div class="k">cpu</div><div class="v">i860</div><div class="d">transform · clip · classify</div></div>
|
||
<div class="stage on"><span class="dot"></span><div class="k">geom</div><div class="v">Binitize</div><div class="d">per-region 64×128 bins</div></div>
|
||
<div class="stage on"><span class="dot"></span><div class="k">out</div><div class="v">IGC DMA</div><div class="d">SEND / TILE / FLUSH stream</div></div>
|
||
<div class="stage sim"><span class="dot"></span><div class="k">px</div><div class="v">Pixel-Planes</div><div class="d">array model simulated · §05</div></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h2><span class="idx">02</span> The output, decoded</h2>
|
||
<p class="sub">The firmware DMAs the render to the card as an opcode stream
|
||
(<span class="mono">DMAENGN.H</span>). Both panels below are captured verbatim from the
|
||
emulator. Left: a real per-region command list — every region references the same
|
||
tile-relative payloads and differs only in its <span class="mono">TILE</span> id and the
|
||
<span class="mono">GOTO</span> link. Right: inside a SEND — the payload isn't opaque, it
|
||
interleaves control words with the actual <b>float coefficients</b>, swept bit-plane by
|
||
bit-plane into pixel memory.</p>
|
||
<div class="cols">
|
||
<div class="term">
|
||
<div class="bar"><span class="d g"></span><span class="d"></span><span class="d"></span>
|
||
region 0x0801fa40 · DMA list</div>
|
||
<pre><span class="cm"># addr opcode</span>
|
||
<span class="ad">0x08015000</span> <span class="op">SEND</span>(4) <span class="cm">; edge coeffs</span>
|
||
<span class="ad">0x00000000</span> <span class="am">FLUSH</span>
|
||
<span class="ad">0x08015020</span> <span class="op">SENDE</span>(0x45) <span class="cm">; z / colour</span>
|
||
<span class="ad">0x00000000</span> <span class="am">FLUSH</span>
|
||
<span class="ad">0x08014100</span> <span class="op">TXDN</span>
|
||
<span class="ad">0x08015260</span> <span class="op">SEND</span>(0x21)
|
||
<span class="ad">0x08015380</span> <span class="op">SEND</span>(0x29)
|
||
<span class="ad">0x00000020</span> <span class="am">TILE</span> <span class="cm">; tile id</span>
|
||
<span class="ad">0x0801f008</span> <span class="am">GOTO</span> <span class="cm">; → next region</span></pre>
|
||
</div>
|
||
<div class="term">
|
||
<div class="bar"><span class="d g"></span><span class="d"></span><span class="d"></span>
|
||
SENDE target 0x08015020 · payload</div>
|
||
<pre><span class="cm"># bit-serial sweep (MEMpluseqMEM)</span>
|
||
+000 <span class="ad">00000100</span> <span class="cm">; header</span>
|
||
+004 <span class="op">3a804834</span> <span class="cm">; = 9.79e-4 float</span>
|
||
+008 <span class="ad">8401213a</span>
|
||
+00c <span class="ad">00000021</span> <span class="cm">; bit-plane 0x21</span>
|
||
+010 <span class="op">ba01253a</span> <span class="cm">; = −4.93e-4 += A</span>
|
||
+018 <span class="ad">8381213a</span>
|
||
+01c <span class="ad">00000022</span> <span class="cm">; bit-plane 0x22 …</span></pre>
|
||
</div>
|
||
</div>
|
||
<p class="sub" style="margin-top:14px">Pull the floats out of those payloads and they
|
||
line up into clean <b>×2 chains</b> —
|
||
<span class="mono">0.0079 · 0.016 · 0.032 · 0.063 · 0.126 · 0.252 · 0.504 · 1.009</span> —
|
||
a coefficient stored as its binary place values <span class="mono">C·2ᵏ</span>, one per
|
||
bit-plane, exactly how a bit-serial adder holds a number. And the recovered values are the
|
||
object's own geometry: a payload edge coefficient (<span class="mono">0.12527</span>) lands on
|
||
the edge normal computed from the captured vertices (<span class="mono">0.12555</span>) to
|
||
<b>0.2%</b>. The plane <b>constants</b> sit alongside as fixed-point screen coordinates
|
||
(<span class="mono">0x0000ec00 = 236.0</span>, a vertex x). The coefficients feeding the array
|
||
simulator (§05) are <b>the ones the hardware actually shipped</b>.</p>
|
||
</section>
|
||
|
||
<section>
|
||
<h2><span class="idx">03</span> Where the geometry landed</h2>
|
||
<p class="sub">At the frame flush the firmware walks the screen-bin array and hands the
|
||
IGC one descriptor per 64×128 region. This grid is drawn from the <b>real captured
|
||
descriptor scan</b> — filled cells are regions that received transformed geometry.
|
||
A full mission view fills nearly the whole frame: ground plane and sky, exactly as expected.</p>
|
||
<div class="binwrap">
|
||
<canvas id="bins" width="192" height="96" aria-label="region binning coverage"></canvas>
|
||
<div class="legend">
|
||
<span><i style="background:var(--phos)"></i>region has geometry</span>
|
||
<span><i style="background:#0d1a12"></i>empty region (shared page)</span>
|
||
<span style="color:var(--faint)">96 × 2 descriptor grid · 191 of 192 filled</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<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>
|
||
|
||
<section>
|
||
<h2><span class="idx">05</span> The Pixel-Planes array, simulated</h2>
|
||
<p class="sub">This is the final stage in silicon — and it now runs in software. The
|
||
<span class="mono">PXPL5 IGC</span> is a region rasteriser: the screen splits into
|
||
<b>64 × 128 tiles</b>; every pixel carries a 26-byte bit-memory and an enable bit; all
|
||
pixels evaluate one linear tree in lockstep —
|
||
<span class="mono">eval_ltree(x,y,A,B,C) = (int)(x·A + y·B + C)</span>. A triangle becomes
|
||
three <b>edge trees</b> into the enable register, then <b>Z and R/G/B planes</b>
|
||
interpolated per pixel and z-buffered in pixel memory (<span class="mono">IGCOPS.C</span>).
|
||
Fed the captured surface, the model lights the tiles below and renders pixels that match our
|
||
reference rasteriser to within edge anti-aliasing — the array reproduced faithfully.</p>
|
||
<div class="arr">
|
||
<figure>
|
||
<canvas id="depth" width="560" height="500" aria-label="the depth buffer the array computes"></canvas>
|
||
<figcaption><b>the depth buffer</b>, read straight out of pixel memory — the 24-bit
|
||
Z plane the <span class="mono">SENDE</span> z-sweep interpolates (near = bright)</figcaption>
|
||
</figure>
|
||
<figure>
|
||
<canvas id="tiles" width="500" height="360" aria-label="the object's footprint across the 64x128 tile array"></canvas>
|
||
<figcaption><b>the object's footprint</b> — 18 of 50 <b>64×128</b> tiles lit,
|
||
each a region of pixel processors</figcaption>
|
||
</figure>
|
||
</div>
|
||
<div class="arrinfo" style="margin-top:16px">
|
||
<div class="pmlab">pixel memory · 26 bytes = 208 bits</div>
|
||
<div class="pmbar">
|
||
<span class="z">Z · 24b</span><span class="r">R·8</span><span class="g">G·8</span>
|
||
<span class="b">B·8</span><span class="free">enable + working bits</span>
|
||
</div>
|
||
<ul class="mini">
|
||
<li>three edge trees → the <b>enable register</b> (the inside test)</li>
|
||
<li>Z / R / G / B are <b>planes</b>, evaluated per pixel by the linear tree</li>
|
||
<li>z-buffer via <span class="mono">MEM2geMEM2</span>; every write gated by enable</li>
|
||
<li>the depth image (left) is that Z plane, read back out of pixel memory</li>
|
||
<li><b>validated</b> pixel-faithful to the reference — ~1%, at edges only</li>
|
||
</ul>
|
||
</div>
|
||
<p class="sub" style="margin-top:16px">What this is <b>not</b>, yet: a full from-scratch run of
|
||
the compiled <b>micro-code</b> the DMA ships (§02) — the form the geometry takes on the wire to the
|
||
card. But that stream is now largely decoded: the command lists read cleanly,
|
||
the <span class="mono">SENDE</span> 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
|
||
<b>cross-validated against the hardware's own compiled stream</b>. And scanning
|
||
<b>all 14,223 payload writes</b> of this draw, every screen coordinate lands in the object's own
|
||
range (<span class="mono">x 66–236</span>) — this death-cam frame carries <b>no geometry beyond
|
||
the object</b>; the rest is a background clear. So the array's render above isn't one primitive of
|
||
many: for this frame, it <b>is</b> the geometry.</p>
|
||
</section>
|
||
|
||
<section>
|
||
<h2><span class="idx">06</span> The warp field — a Star Trek scene, recovered</h2>
|
||
<p class="sub">The archive's <span class="mono">trek</span> capture — unreleased Star Trek
|
||
material — <b>replays and draws</b> after the dual-instruction-mode fix. Walking the
|
||
per-region DMA chains in the bin pages enumerated <b>898 compiled payload programs</b> for
|
||
the frame: four are the standard end-of-frame pipeline (fog, texture, RGB — byte-identical
|
||
in every capture), and <b>~860 are the scene itself</b>, per-primitive coefficient blocks
|
||
with their geometry sitting in plain screen-space floats. Plotted below:
|
||
<b>433 star positions and 389 streaks</b>, radiating from a convergence point — the
|
||
<b>warp-speed starfield</b>, reconstructed from micro-code compiled by the board's own
|
||
firmware running on the emulated i860.</p>
|
||
<div class="hero-render" style="margin-top:16px">
|
||
<span class="tag">recovered from the frame's compiled payload programs</span>
|
||
<canvas id="trekframe" width="832" height="512" aria-label="the warp starfield recovered from trek's compiled frame"></canvas>
|
||
<div class="cap"><b>trek at warp</b> — every point and streak is one primitive's
|
||
position read from its compiled coefficient block (fields +0x14/+0x18 and +0x24/+0x28,
|
||
plain IEEE floats in screen space).</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h2><span class="idx">07</span> What it took to get here</h2>
|
||
<p class="sub">The i860 core was corrected against the toolchain's own assembler output
|
||
and MAME's validated i860 model. A selection of the load-bearing fixes:</p>
|
||
<div class="fixlist">
|
||
<div class="fix"><div class="t">store encoding</div><div class="x">i860 stores keep the source in bits 15:11 and split the offset — every function return went to 0 until this was found. <b>Prologues now save r1.</b></div></div>
|
||
<div class="fix"><div class="t">op 0x13 escape</div><div class="x">lock / calli / unlock share one opcode; everything decoded as calli, so every spinlock jumped to address 0. <b>The phantom "exit stub."</b></div></div>
|
||
<div class="fix"><div class="t">FP pipeline</div><div class="x">Exact PFAM/PFMAM dual-ops, per-stage precision, fdest bypass. The Newton–Raphson divide now yields <b>16 ÷ 16 = 1</b> through the pipes, unhooked.</div></div>
|
||
<div class="fix"><div class="t">pipelined pftrunc</div><div class="x">Float→int through the adder pipe — a form no prior i860 emulator implemented. <b>Killed an 8.3-million phantom-row loop.</b></div></div>
|
||
<div class="fix"><div class="t">IGC drain</div><div class="x">Per-page completion is nonzero-on-done, and consumed pages reset their write index. <b>Frames stopped saturating after ~1,400 draws.</b></div></div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="status">
|
||
<div class="h"><span class="tag">Landed</span>
|
||
<span class="t">Firmware → render command stream, end to end</span></div>
|
||
<div class="b">
|
||
<div>
|
||
<h3>Verified working</h3>
|
||
<ul class="done">
|
||
<li>Cold boot of the capture's own firmware build on the emulated i860</li>
|
||
<li>Full recorded mission replays to completion — <b>26,422 commands</b></li>
|
||
<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>recovered and shaded</b> — a 9×5 height-field surface, lit by the firmware's own normals (above)</li>
|
||
<li><b>Pixel-Planes array simulated</b> (§05) — 64×128 tiles, per-pixel memory, edge trees + z-buffer, validated pixel-faithful</li>
|
||
</ul>
|
||
</div>
|
||
<div>
|
||
<h3>The last mile to pixels</h3>
|
||
<ul class="todo">
|
||
<li>The array's <b>computational model</b> runs and matches the reference — the piece left is executing the board's own <b>compiled micro-code</b></li>
|
||
<li>The per-region <b>DMA command lists decode cleanly</b> (§02) — SEND / SENDE / TXDN / TILE / GOTO, tile-relative</li>
|
||
<li>The SEND payloads carry <b>real float coefficients</b> in a regular bit-serial sweep — recoverable, not opaque</li>
|
||
<li>Coefficients <b>verified to 0.2%</b> against the geometry; for this frame, the object <b>is</b> the geometry (§05)</li>
|
||
<li>cap7 redraws <b>this one object every frame</b> — confirmed identical at cmd 735 and cmd 19,889, same 9 coordinates. A battle scene with terrain is a different capture entirely</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<footer>
|
||
<span>VWE TESLA REL410 · firmware-decomp · emu860</span>
|
||
<span>Tier 0 complete · Tier 1 array simulated · micro-code decode next</span>
|
||
</footer>
|
||
</div>
|
||
|
||
<script>
|
||
(function(){
|
||
var reduce = matchMedia('(prefers-reduced-motion:reduce)').matches;
|
||
|
||
/* ---- scope: an i860 execution trace as an oscilloscope sweep ---- */
|
||
var c = document.getElementById('scope'), x = c.getContext('2d');
|
||
var W = c.width, H = c.height;
|
||
// a pseudo instruction-density trace (deterministic)
|
||
var N = 260, data = [];
|
||
var seed = 1337;
|
||
function rnd(){ seed = (seed*1103515245 + 12345) & 0x7fffffff; return seed/0x7fffffff; }
|
||
for(var i=0;i<N;i++){
|
||
var base = 0.5 + 0.32*Math.sin(i*0.11) + 0.12*Math.sin(i*0.37+1);
|
||
var spike = rnd()<0.12 ? (rnd()*0.5) : 0;
|
||
data.push(Math.max(0.04, Math.min(0.97, base*0.7 + spike)));
|
||
}
|
||
var t=0;
|
||
function grid(){
|
||
x.clearRect(0,0,W,H);
|
||
x.strokeStyle='rgba(65,255,142,0.06)'; x.lineWidth=1;
|
||
for(var gy=0; gy<H; gy+=H/6){ x.beginPath(); x.moveTo(0,gy); x.lineTo(W,gy); x.stroke(); }
|
||
for(var gx=0; gx<W; gx+=W/16){ x.beginPath(); x.moveTo(gx,0); x.lineTo(gx,H); x.stroke(); }
|
||
}
|
||
function frame(){
|
||
grid();
|
||
// glow trace
|
||
for(var pass=0; pass<2; pass++){
|
||
x.beginPath();
|
||
for(var i=0;i<N;i++){
|
||
var px = i/(N-1)*W;
|
||
var idx = (i + Math.floor(t)) % N;
|
||
var py = H - data[idx]*(H-24) - 12;
|
||
if(i===0) x.moveTo(px,py); else x.lineTo(px,py);
|
||
}
|
||
x.strokeStyle = pass===0 ? 'rgba(65,255,142,0.18)' : 'rgba(65,255,142,0.95)';
|
||
x.lineWidth = pass===0 ? 6 : 1.6;
|
||
x.stroke();
|
||
}
|
||
// sweep head
|
||
var hx = ((t*4)%W);
|
||
var g = x.createLinearGradient(hx-40,0,hx,0);
|
||
g.addColorStop(0,'rgba(65,255,142,0)'); g.addColorStop(1,'rgba(65,255,142,0.25)');
|
||
x.fillStyle=g; x.fillRect(hx-40,0,40,H);
|
||
if(!reduce){ t += 0.4; requestAnimationFrame(frame); }
|
||
}
|
||
frame();
|
||
|
||
/* ---- projected VSTRIP triangle strips captured from the i860 ---- */
|
||
var STRIPS = [[[237.52,62.0],[235.22,46.5],[238.02,31.0],[236.58,18.6]],
|
||
[[240.88,43.4],[238.91,6.2],[237.46,31.0],[238.02,9.3],[237.11,62.0],[242.21,21.7],
|
||
[238.4,37.2],[242.27,15.5],[237.55,31.0],[246.45,0.0],[242.77,31.0],[246.92,12.4],
|
||
[241.71,40.3],[236.31,0.0],[253.7,62.0],[252.07,43.4]],
|
||
[[245.13,15.5],[239.94,40.3],[238.5,93.0],[234.37,62.0],[239.09,0.0],[231.87,0.0],
|
||
[240.72,0.0],[233.5,40.3],[254.57,24.8],[243.22,0.0],[233.78,12.4],[243.22,15.5],
|
||
[254.29,0.0],[250.44,9.3],[249.57,12.4],[245.74,0.0]],
|
||
[[126.07,62.0],[238.81,49.6],[239.09,62.0],[250.44,40.3],[234.09,31.0],[250.44,43.4],
|
||
[231.29,46.5],[254.87,37.2],[252.65,34.1]]];
|
||
(function(){
|
||
var vc=document.getElementById('verts'), vx=vc.getContext('2d');
|
||
var W=vc.width, H=vc.height;
|
||
var all=[]; STRIPS.forEach(function(s){s.forEach(function(p){all.push(p);});});
|
||
// frame on the main body (ignore the one far-left strip vertex for scale)
|
||
var body=all.filter(function(p){return p[0]>200;});
|
||
var mnx=Math.min.apply(0,body.map(function(p){return p[0]}));
|
||
var mxx=Math.max.apply(0,body.map(function(p){return p[0]}));
|
||
var mny=Math.min.apply(0,body.map(function(p){return p[1]}));
|
||
var mxy=Math.max.apply(0,body.map(function(p){return p[1]}));
|
||
var pad=46, 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 T(p){return [ox+(p[0]-mnx)*s, oy+(p[1]-mny)*s];}
|
||
function seg(a,b,col,wd){var A=T(a),B=T(b);vx.beginPath();vx.moveTo(A[0],A[1]);
|
||
vx.lineTo(B[0],B[1]);vx.strokeStyle=col;vx.lineWidth=wd;vx.stroke();}
|
||
// grid
|
||
vx.strokeStyle='rgba(65,255,142,0.045)';
|
||
for(var gx=0;gx<W;gx+=44){vx.beginPath();vx.moveTo(gx,0);vx.lineTo(gx,H);vx.stroke();}
|
||
for(var gy=0;gy<H;gy+=44){vx.beginPath();vx.moveTo(0,gy);vx.lineTo(W,gy);vx.stroke();}
|
||
// triangle-strip edges: v[i]-v[i+1] (strip) and v[i]-v[i+2] (cross) = triangles
|
||
STRIPS.forEach(function(st){
|
||
for(var i=0;i<st.length-2;i++) seg(st[i],st[i+2],'rgba(95,208,255,0.4)',1);
|
||
for(var i=0;i<st.length-1;i++) seg(st[i],st[i+1],'rgba(65,255,142,0.7)',1.3);
|
||
});
|
||
// glow pass over strip edges
|
||
vx.globalCompositeOperation='lighter';
|
||
STRIPS.forEach(function(st){
|
||
for(var i=0;i<st.length-1;i++) seg(st[i],st[i+1],'rgba(65,255,142,0.15)',3.5);
|
||
});
|
||
vx.globalCompositeOperation='source-over';
|
||
// vertices
|
||
all.forEach(function(p){var t=T(p);
|
||
vx.beginPath();vx.arc(t[0],t[1],2.6,0,7);vx.fillStyle='#eaffef';vx.fill();});
|
||
})();
|
||
|
||
/* ---- shaded render: Gouraud raster of the projected object ----
|
||
GEO per vertex = [screenX, screenY, normalX, normalY, normalZ, modelZ].
|
||
Screen xy + normals pulled live off the emulated i860; we do the fill in
|
||
software (this is our rasteriser, not the board's bit-serial IGC array). */
|
||
var GEO=[[[237.5,62,0.931,0.262,0.131,-8],[235.2,46.5,0.922,0.187,0.094,-6],[238,31,0.933,0.278,0.139,-8],[236.6,18.6,0.928,0.231,0.116,-6]],
|
||
[[240.9,43.4,0.945,0.372,0.186,-8],[238.9,6.2,0.937,0.307,0.154,-6],[237.5,31,0.931,0.26,0.13,-8],[238,9.3,0.933,0.278,0.139,-6],[237.1,62,0.93,0.249,0.124,-8],[242.2,21.7,0.95,0.415,0.208,-6],[238.4,37.2,0.935,0.291,0.146,-8],[242.3,15.5,0.95,0.417,0.209,-6],[237.6,31,0.932,0.263,0.431,-8],[246.5,0,0.966,0.554,0.377,-6],[242.8,31,0.952,0.943,0.517,-8],[246.9,12.4,0.968,0.957,0.685,-6],[241.7,40.3,0.948,0.98,0.65,-8],[236.3,0,0.927,0.936,0.678,-6],[253.7,62,0.995,0.246,0.123,-4],[252.1,43.4,0.988,0.324,0.162,-4]],
|
||
[[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('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;
|
||
var body=[]; GEO.forEach(function(o){o.forEach(function(v){if(v[0]>200)body.push(v);});});
|
||
var mnx=Math.min.apply(0,body.map(function(v){return v[0];}));
|
||
var mxx=Math.max.apply(0,body.map(function(v){return v[0];}));
|
||
var mny=Math.min.apply(0,body.map(function(v){return v[1];}));
|
||
var mxy=Math.max.apply(0,body.map(function(v){return v[1];}));
|
||
var pad=70*SS, s=Math.min((W-2*pad)/(mxx-mnx),(H-2*pad)/(mxy-mny));
|
||
var ox=(W-(mxx-mnx)*s)/2-mnx*s, oy=(H-(mxy-mny)*s)/2-mny*s;
|
||
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.75);
|
||
function I(v){var n=nrm(v[2],v[3],v[4]);var d=n[0]*L[0]+n[1]*L[1]+n[2]*L[2];
|
||
return Math.max(0.2,Math.min(1,0.24+0.82*Math.abs(d)));}
|
||
function tri(a,b,c){
|
||
var ax=ox+a[0]*s,ay=oy+a[1]*s,bx=ox+b[0]*s,by=oy+b[1]*s,cx=ox+c[0]*s,cy=oy+c[1]*s;
|
||
var ia=I(a),ib=I(b),ic=I(c), za=a[5],zbv=b[5],zc=c[5];
|
||
var minx=Math.max(0,Math.floor(Math.min(ax,bx,cx))),maxx=Math.min(W-1,Math.ceil(Math.max(ax,bx,cx)));
|
||
var miny=Math.max(0,Math.floor(Math.min(ay,by,cy))),maxy=Math.min(H-1,Math.ceil(Math.max(ay,by,cy)));
|
||
var area=(bx-ax)*(cy-ay)-(cx-ax)*(by-ay); if(Math.abs(area)<1e-6)return;
|
||
for(var py=miny;py<=maxy;py++)for(var px=minx;px<=maxx;px++){
|
||
var w0=((bx-px)*(cy-py)-(cx-px)*(by-py))/area;
|
||
var w1=((cx-px)*(ay-py)-(ax-px)*(cy-py))/area;
|
||
var w2=1-w0-w1;
|
||
if(w0<-0.003||w1<-0.003||w2<-0.003)continue;
|
||
var z=w0*za+w1*zbv+w2*zc, idx=py*W+px;
|
||
if(z>zb[idx]){zb[idx]=z; inten[idx]=w0*ia+w1*ib+w2*ic;}
|
||
}
|
||
}
|
||
GEO.forEach(function(st){for(var i=0;i<st.length-2;i++)tri(st[i],st[i+1],st[i+2]);});
|
||
var img=sctx.createImageData(CW,CH), d=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 xs=0;xs<SS;xs++){
|
||
var v=inten[(y*SS+yy)*W+(xx*SS+xs)]; if(v>0){acc+=v;cnt++;}
|
||
}
|
||
var o=(y*CW+xx)*4;
|
||
if(cnt>0){
|
||
var vv=acc/cnt, cov=cnt/(SS*SS);
|
||
d[o]=Math.min(255,28+vv*168); d[o+1]=Math.min(255,50+vv*205);
|
||
d[o+2]=Math.min(255,54+vv*122); d[o+3]=Math.round(cov*255);
|
||
} else d[o+3]=0;
|
||
}
|
||
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');
|
||
var COLS=96, ROWS=2, CELL=8, GAP=1;
|
||
bc.width = COLS*(CELL+GAP)+GAP; bc.height = ROWS*(CELL+GAP)+GAP*4;
|
||
// draw at higher res for crispness
|
||
var scale=1;
|
||
var empties = new Set(['0-0']); // the one region left empty in the captured pass
|
||
bx.fillStyle = '#04070a'; bx.fillRect(0,0,bc.width,bc.height);
|
||
for(var r=0;r<ROWS;r++){
|
||
for(var cc=0;cc<COLS;cc++){
|
||
var key = r+'-'+cc;
|
||
var filled = !empties.has(key);
|
||
var px = GAP + cc*(CELL+GAP);
|
||
var py = GAP + r*(CELL+GAP);
|
||
if(filled){
|
||
var v = 0.55 + 0.45*Math.abs(Math.sin(cc*0.5 + r));
|
||
bx.fillStyle = 'rgba(65,255,142,'+ (0.35+0.5*v) +')';
|
||
} else {
|
||
bx.fillStyle = '#0d1a12';
|
||
}
|
||
bx.fillRect(px,py,CELL,CELL);
|
||
}
|
||
}
|
||
|
||
/* ---- 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;j<NZ;j++)for(var i=0;i<NX;i++){s+=R[j][i][0];n++;}cy=s/n;})();
|
||
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];}
|
||
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);
|
||
P[j][i]={X:p[0],Y:p[1],Z:p[2]};
|
||
}}
|
||
var mnx=1e9,mxx=-1e9,mny=1e9,mxy=-1e9,mnz=1e9,mxz=-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;
|
||
if(p.Z<mnz)mnz=p.Z;if(p.Z>mxz)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;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; dep[idx]=(z-mnz)/zsp;}
|
||
}
|
||
}
|
||
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=dx.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=dep[(y*SS+yy)*W+(xx*SS+xs2)];if(v>=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);
|
||
})();
|
||
|
||
/* ---- the warp field: star positions + streaks recovered from trek's
|
||
compiled per-primitive payload blocks (screen-space floats) ---- */
|
||
var WARP_PTS=[[196.5,174.7],[73.5,277.0],[113.9,474.8],[120.3,347.2],[350.8,358.9],[100.0,438.9],[102.7,474.7],[143.5,56.3],[521.1,111.5],[335.1,400.5],[39.3,353.8],[287.8,323.5],[251.4,554.9],[93.4,246.8],[479.9,300.7],[274.7,347.7],[97.6,465.2],[382.6,241.0],[476.5,80.5],[160.7,418.5],[334.4,218.4],[326.4,553.0],[473.7,587.9],[89.8,371.7],[100.0,127.6],[593.2,335.7],[530.1,583.7],[28.3,420.3],[160.3,499.2],[106.8,237.8],[268.7,370.0],[224.2,219.2],[217.4,448.6],[139.3,256.6],[408.1,493.9],[252.3,282.6],[8.2,586.6],[309.7,583.4],[416.5,337.7],[270.2,215.6],[730.9,473.8],[6.6,364.1],[641.3,69.2],[423.6,565.8],[84.0,313.2],[289.9,488.0],[407.1,350.0],[187.8,61.4],[53.3,340.4],[180.7,406.5],[551.6,104.8],[280.6,464.0],[615.6,412.7],[215.4,192.7],[173.6,99.2],[5.2,313.3],[44.7,238.4],[211.9,472.9],[334.4,52.8],[329.5,248.3],[564.8,232.5],[91.9,450.9],[105.0,2.9],[366.1,113.9],[291.5,368.5],[266.1,254.2],[357.0,201.0],[483.0,174.3],[529.5,34.7],[29.2,240.2],[258.7,12.5],[664.8,238.8],[206.6,337.8],[591.0,419.0],[243.1,220.4],[351.8,300.5],[262.0,390.1],[55.0,544.2],[256.1,200.8],[446.6,434.5],[471.1,416.2],[437.2,124.0],[700.2,381.8],[196.6,277.9],[61.1,316.4],[141.9,576.4],[171.4,516.7],[264.4,444.2],[312.7,574.3],[454.3,345.4],[507.3,547.9],[140.8,301.1],[283.3,140.1],[186.6,218.3],[224.1,2.1],[36.1,331.8],[363.8,257.2],[546.4,375.3],[466.2,493.0],[78.2,307.8],[79.7,550.5],[299.3,277.5],[442.7,267.7],[442.7,461.9],[474.9,543.9],[353.3,177.4],[221.3,526.3],[359.7,381.9],[105.0,343.5],[281.6,194.2],[503.9,422.3],[475.1,407.9],[473.7,203.8],[293.6,222.1],[578.8,352.3],[120.9,577.2],[56.9,442.2],[369.5,368.0],[60.2,123.9],[370.9,394.4],[86.3,262.0],[236.9,166.5],[137.0,257.6],[209.9,572.5],[393.1,74.3],[271.4,289.7],[414.7,560.1],[549.8,156.7],[304.6,466.1],[133.0,385.4],[281.6,586.3],[574.1,124.0],[169.6,193.1],[88.4,388.4],[92.3,136.0],[620.2,559.1],[733.7,173.2],[481.8,247.5],[501.2,277.0],[223.4,494.1],[403.7,264.9],[248.9,259.6],[223.6,480.0],[594.2,446.4],[266.3,327.8],[586.8,101.1],[673.0,67.1],[554.5,550.2],[42.7,265.6],[375.2,209.5],[491.0,344.8],[7.8,453.0],[389.1,316.0],[465.7,535.9],[537.6,388.0],[530.8,525.5],[744.7,353.7],[676.1,184.2],[153.8,173.2],[451.5,351.5],[416.8,254.6],[560.5,219.5],[52.6,530.0],[621.6,126.7],[199.3,329.9],[152.3,351.4],[592.3,315.7],[598.4,280.3],[144.0,322.3],[153.5,243.5],[125.4,55.6],[619.1,78.7],[100.4,531.9],[9.1,480.6],[292.8,556.8],[29.5,360.0],[307.4,512.8],[324.5,487.0],[167.5,137.6],[482.1,263.5],[520.6,496.8],[485.1,154.1],[135.6,371.2],[369.8,262.5],[563.6,425.9],[553.7,379.0],[106.3,54.7],[208.8,176.5],[437.1,272.8],[109.5,345.7],[520.9,318.4],[156.9,539.9],[433.7,196.8],[44.5,384.6],[25.4,484.0],[511.8,511.6],[453.1,564.0],[448.3,534.4],[236.8,369.7],[263.6,435.0],[212.2,514.6],[359.4,390.3],[192.0,508.4],[233.2,595.6],[381.4,294.1],[289.1,136.5],[455.7,97.3],[464.1,116.0],[690.2,174.3],[215.9,547.4],[184.3,568.8],[317.7,567.7],[505.4,469.0],[422.1,347.9],[472.6,235.2],[579.0,277.0],[521.7,5.8],[223.3,204.7],[224.5,474.7],[163.8,563.9],[366.0,373.8],[89.1,234.0],[458.4,488.6],[331.7,375.1],[452.8,7.6],[436.2,67.3],[121.1,284.0],[481.7,496.3],[199.2,393.1],[37.8,220.0],[163.6,158.9],[187.1,566.6],[558.4,441.0],[662.0,418.0],[334.4,472.2],[183.4,226.5],[235.5,181.7],[291.3,471.5],[19.2,330.1],[374.6,15.7],[303.1,571.5],[491.8,420.7],[489.5,238.9],[547.0,253.0],[233.0,326.7],[374.7,258.2],[393.3,364.4],[232.5,311.9],[330.4,318.4],[516.5,335.8],[337.1,120.5],[36.7,362.1],[533.1,404.5],[555.4,109.3],[268.1,158.9],[196.5,482.6],[186.1,390.8],[462.0,245.4],[356.6,92.5],[310.3,495.0],[259.6,551.2],[543.3,503.9],[26.7,164.1],[22.3,470.7],[127.2,351.5],[517.7,240.7],[270.8,130.4],[639.1,187.3],[402.4,535.3],[696.5,169.1],[322.6,117.5],[70.3,238.6],[614.4,332.2],[334.4,29.7],[757.0,62.5],[739.8,538.3],[476.6,198.0],[424.5,142.8],[309.5,247.3],[428.5,237.9],[533.4,482.8],[391.7,585.8],[632.0,315.4],[514.7,334.3],[19.6,367.4],[414.1,537.2],[585.7,354.3],[165.6,485.9],[333.1,286.6],[323.6,590.1],[343.1,33.8],[44.8,484.3],[421.3,313.3],[231.5,68.5],[469.3,517.1],[587.8,421.2],[497.0,338.7],[117.1,462.3],[205.4,228.7],[150.4,372.2],[280.2,372.3],[189.1,20.7],[346.9,274.7],[351.4,254.9],[28.5,361.5],[36.0,137.3],[132.3,353.8],[365.2,304.9],[475.0,404.6],[430.1,308.5],[445.6,59.5],[559.0,428.6],[298.9,154.5],[697.9,179.8],[486.3,300.0],[69.1,103.0],[16.2,178.0],[292.3,34.3],[240.7,179.4],[456.2,421.9],[111.8,58.7],[457.9,556.8],[109.3,447.9],[14.0,589.3],[102.7,52.9],[185.0,431.4],[63.4,273.4],[11.7,523.4],[269.2,572.9],[504.6,393.5],[518.1,512.8],[692.4,139.5],[81.2,467.9],[15.6,511.0],[378.5,33.7],[295.6,52.6],[296.2,167.0],[371.0,586.7],[127.0,531.6],[388.1,564.7],[527.6,595.7],[687.9,404.1],[542.7,367.5],[407.9,458.3],[339.1,550.6],[164.6,509.1],[359.8,569.6],[252.2,420.7],[237.8,198.6],[455.9,65.6],[115.0,475.2],[349.6,36.9],[493.5,242.2],[350.0,141.8],[260.0,470.2],[76.3,453.5],[374.1,195.7],[22.4,205.3],[151.0,227.5],[607.0,135.5],[762.1,310.5],[644.8,242.7],[149.8,287.5],[515.3,71.1],[55.6,441.5],[385.7,484.8],[48.8,292.0],[325.5,86.8],[552.2,12.2],[704.6,509.8],[410.5,309.1],[733.1,162.6],[111.1,13.9],[563.9,335.9],[230.1,100.9],[638.4,308.2],[429.5,533.8],[201.9,237.9],[507.9,523.0],[79.3,333.2],[595.8,557.4],[404.5,528.0],[376.4,368.6],[620.3,70.4],[392.4,595.1],[155.0,380.9],[603.5,350.7],[313.1,510.5],[350.9,149.9],[333.4,543.3],[95.4,425.8],[368.0,544.3],[182.6,323.6],[236.0,472.3],[491.6,533.1],[194.7,521.2],[663.2,445.1],[218.3,312.8],[295.6,467.2],[90.8,561.6],[28.9,416.9],[293.2,27.5],[437.1,386.7],[442.3,360.3],[629.0,202.8],[557.0,129.9],[749.3,177.2],[229.6,277.3],[87.9,439.4],[91.2,275.6],[515.0,390.7],[241.4,465.3],[76.6,582.5],[414.4,346.0],[427.8,427.1],[418.7,207.4],[576.2,356.8],[407.4,95.1],[693.1,84.1],[355.8,53.4],[73.7,344.1],[148.1,447.2],[220.6,22.3],[371.4,413.4],[163.2,400.9],[389.6,210.0],[542.1,334.1],[499.3,116.5],[126.7,567.5],[182.1,321.8],[225.3,271.6],[264.2,231.3],[53.7,376.7]];
|
||
var WARP_SEGS=[[196.5,174.7,485.3,268.5],[73.5,277.0,469.5,268.5],[113.9,474.8,508.4,268.5],[120.3,347.2,452.0,268.5],[350.8,358.9,541.3,268.5],[100.0,438.9,567.0,268.5],[102.7,474.7,642.4,268.5],[335.1,400.5,487.6,268.5],[39.3,353.8,581.0,268.5],[287.8,323.5,619.1,268.5],[251.4,554.9,397.1,268.5],[93.4,246.8,535.7,268.5],[479.9,300.7,668.6,268.5],[274.7,347.7,650.0,268.5],[97.6,465.2,609.1,268.5],[382.6,241.0,575.7,268.5],[476.5,80.5,415.7,268.5],[334.4,218.4,993.2,268.5],[326.4,553.0,468.2,268.5],[473.7,587.9,759.0,268.5],[89.8,371.7,577.2,268.5],[100.0,127.6,962.8,268.5],[593.2,335.7,993.1,268.5],[530.1,583.7,310.6,268.5],[28.3,420.3,541.2,268.5],[160.3,499.2,486.4,268.5],[106.8,237.8,787.0,268.5],[268.7,370.0,512.7,268.5],[224.2,219.2,378.0,268.5],[217.4,448.6,619.4,268.5],[408.1,493.9,736.4,268.5],[252.3,282.6,457.5,268.5],[8.2,586.6,656.4,268.5],[309.7,583.4,425.4,268.5],[416.5,337.7,514.7,268.5],[270.2,215.6,533.3,268.5],[730.9,473.8,805.2,268.5],[423.6,565.8,427.4,268.5],[84.0,313.2,460.8,268.5],[289.9,488.0,514.0,268.5],[407.1,350.0,482.7,268.5],[187.8,61.4,657.0,268.5],[53.3,340.4,812.8,268.5],[180.7,406.5,484.0,268.5],[551.6,104.8,490.4,268.5],[280.6,464.0,341.2,268.5],[615.6,412.7,376.7,268.5],[215.4,192.7,398.9,268.5],[173.6,99.2,435.5,268.5],[5.2,313.3,612.8,268.5],[44.7,238.4,578.9,268.5],[211.9,472.9,590.8,268.5],[334.4,52.8,405.8,268.5],[329.5,248.3,423.2,268.5],[564.8,232.5,481.7,268.5],[91.9,450.9,665.9,268.5],[105.0,2.9,607.9,268.5],[366.1,113.9,766.6,268.5],[291.5,368.5,401.1,268.5],[266.1,254.2,661.7,268.5],[357.0,201.0,586.7,268.5],[483.0,174.3,658.7,268.5],[529.5,34.7,593.6,268.5],[29.2,240.2,741.8,268.5],[258.7,12.5,856.2,268.5],[206.6,337.8,235.6,268.5],[591.0,419.0,444.6,268.5],[243.1,220.4,430.7,268.5],[351.8,300.5,677.4,268.5],[262.0,390.1,401.6,268.5],[55.0,544.2,760.5,268.5],[256.1,200.8,497.2,268.5],[446.6,434.5,994.6,268.5],[471.1,416.2,779.4,268.5],[437.2,124.0,791.7,268.5],[700.2,381.8,328.2,268.5],[196.6,277.9,437.7,268.5],[61.1,316.4,932.5,268.5],[141.9,576.4,687.4,268.5],[171.4,516.7,415.2,268.5],[264.4,444.2,357.8,268.5],[312.7,574.3,517.0,268.5],[454.3,345.4,739.8,268.5],[507.3,547.9,434.7,268.5],[140.8,301.1,591.7,268.5],[283.3,140.1,503.3,268.5],[186.6,218.3,801.3,268.5],[224.1,2.1,783.5,268.5],[36.1,331.8,860.8,268.5],[363.8,257.2,568.6,268.5],[466.2,493.0,412.0,268.5],[78.2,307.8,490.8,268.5],[79.7,550.5,627.1,268.5],[299.3,277.5,644.4,268.5],[442.7,267.7,486.0,268.5],[442.7,461.9,680.7,268.5],[474.9,543.9,447.7,268.5],[353.3,177.4,587.6,268.5],[221.3,526.3,582.4,268.5],[359.7,381.9,531.4,268.5],[105.0,343.5,783.4,268.5],[281.6,194.2,968.2,268.5],[503.9,422.3,438.9,268.5],[475.1,407.9,617.0,268.5],[473.7,203.8,647.4,268.5],[293.6,222.1,409.1,268.5],[578.8,352.3,464.4,268.5],[120.9,577.2,401.1,268.5],[56.9,442.2,555.3,268.5],[369.5,368.0,456.4,268.5],[60.2,123.9,629.9,268.5],[370.9,394.4,423.9,268.5],[86.3,262.0,917.1,268.5],[137.0,257.6,285.9,268.5],[209.9,572.5,435.1,268.5],[393.1,74.3,709.3,268.5],[414.7,560.1,454.8,268.5],[549.8,156.7,440.8,268.5],[304.6,466.1,432.9,268.5],[133.0,385.4,494.2,268.5],[281.6,586.3,387.7,268.5],[574.1,124.0,408.1,268.5],[169.6,193.1,627.6,268.5],[88.4,388.4,674.2,268.5],[92.3,136.0,449.0,268.5],[620.2,559.1,700.9,268.5],[481.8,247.5,617.8,268.5],[501.2,277.0,465.0,268.5],[223.4,494.1,487.7,268.5],[403.7,264.9,702.9,268.5],[248.9,259.6,681.5,268.5],[223.6,480.0,674.1,268.5],[594.2,446.4,686.5,268.5],[266.3,327.8,568.4,268.5],[673.0,67.1,591.7,268.5],[554.5,550.2,402.7,268.5],[42.7,265.6,617.9,268.5],[375.2,209.5,635.1,268.5],[491.0,344.8,433.3,268.5],[7.8,453.0,893.4,268.5],[389.1,316.0,580.4,268.5],[465.7,535.9,556.7,268.5],[537.6,388.0,635.1,268.5],[153.8,173.2,527.5,268.5],[451.5,351.5,456.4,268.5],[560.5,219.5,704.6,268.5],[52.6,530.0,395.9,268.5],[621.6,126.7,695.4,268.5],[199.3,329.9,248.2,268.5],[152.3,351.4,624.1,268.5],[592.3,315.7,398.2,268.5],[598.4,280.3,402.9,268.5],[144.0,322.3,712.1,268.5],[153.5,243.5,893.7,268.5],[125.4,55.6,559.8,268.5],[619.1,78.7,574.2,268.5],[100.4,531.9,549.8,268.5],[9.1,480.6,585.8,268.5],[292.8,556.8,847.0,268.5],[307.4,512.8,437.9,268.5],[324.5,487.0,410.0,268.5],[167.5,137.6,889.3,268.5],[482.1,263.5,650.7,268.5],[520.6,496.8,641.3,268.5],[485.1,154.1,483.0,268.5],[135.6,371.2,486.4,268.5],[369.8,262.5,555.6,268.5],[563.6,425.9,684.8,268.5],[553.7,379.0,448.3,268.5],[106.3,54.7,637.3,268.5],[208.8,176.5,654.0,268.5],[437.1,272.8,991.3,268.5],[109.5,345.7,643.2,268.5],[520.9,318.4,403.6,268.5],[156.9,539.9,401.2,268.5],[433.7,196.8,684.3,268.5],[25.4,484.0,627.7,268.5],[511.8,511.6,708.4,268.5],[453.1,564.0,345.1,268.5],[448.3,534.4,385.1,268.5],[236.8,369.7,281.3,268.5],[263.6,435.0,460.5,268.5],[212.2,514.6,422.0,268.5],[359.4,390.3,430.8,268.5],[192.0,508.4,559.0,268.5],[233.2,595.6,610.3,268.5],[381.4,294.1,596.9,268.5],[289.1,136.5,853.0,268.5],[455.7,97.3,406.3,268.5],[690.2,174.3,597.2,268.5],[215.9,547.4,643.8,268.5],[184.3,568.8,724.3,268.5],[317.7,567.7,629.7,268.5],[505.4,469.0,712.6,268.5],[422.1,347.9,472.6,268.5],[472.6,235.2,544.9,268.5],[579.0,277.0,281.7,268.5],[521.7,5.8,714.6,268.5],[223.3,204.7,429.2,268.5],[224.5,474.7,629.9,268.5],[163.8,563.9,532.9,268.5],[366.0,373.8,401.0,268.5],[89.1,234.0,654.5,268.5],[458.4,488.6,539.6,268.5],[452.8,7.6,500.6,268.5],[436.2,67.3,701.0,268.5],[121.1,284.0,469.1,268.5],[481.7,496.3,507.4,268.5],[199.2,393.1,576.5,268.5],[163.6,158.9,813.7,268.5],[187.1,566.6,506.1,268.5],[558.4,441.0,442.3,268.5],[662.0,418.0,704.1,268.5],[334.4,472.2,562.0,268.5],[183.4,226.5,541.8,268.5],[235.5,181.7,584.7,268.5],[291.3,471.5,964.9,268.5],[19.2,330.1,771.4,268.5],[374.6,15.7,576.5,268.5],[491.8,420.7,497.0,268.5],[489.5,238.9,513.2,268.5],[547.0,253.0,522.3,268.5],[233.0,326.7,595.2,268.5],[374.7,258.2,384.8,268.5],[393.3,364.4,500.5,268.5],[232.5,311.9,928.5,268.5],[330.4,318.4,486.8,268.5],[516.5,335.8,354.5,268.5],[337.1,120.5,402.5,268.5],[36.7,362.1,349.5,268.5],[533.1,404.5,882.9,268.5],[555.4,109.3,762.0,268.5],[268.1,158.9,650.6,268.5],[196.5,482.6,407.5,268.5],[186.1,390.8,517.0,268.5],[462.0,245.4,426.6,268.5],[356.6,92.5,497.5,268.5],[310.3,495.0,908.2,268.5],[259.6,551.2,880.7,268.5],[543.3,503.9,642.4,268.5],[26.7,164.1,504.0,268.5],[22.3,470.7,667.8,268.5],[127.2,351.5,437.9,268.5],[517.7,240.7,710.7,268.5],[402.4,535.3,464.1,268.5],[696.5,169.1,808.2,268.5],[322.6,117.5,466.1,268.5],[70.3,238.6,558.9,268.5],[614.4,332.2,953.5,268.5],[757.0,62.5,725.2,268.5],[739.8,538.3,784.4,268.5],[476.6,198.0,644.8,268.5],[424.5,142.8,581.9,268.5],[309.5,247.3,755.7,268.5],[428.5,237.9,544.3,268.5],[533.4,482.8,965.3,268.5],[391.7,585.8,282.7,268.5],[632.0,315.4,563.7,268.5],[514.7,334.3,473.4,268.5],[19.6,367.4,547.2,268.5],[414.1,537.2,728.4,268.5],[585.7,354.3,434.2,268.5],[165.6,485.9,731.1,268.5],[333.1,286.6,546.6,268.5],[323.6,590.1,584.0,268.5],[343.1,33.8,597.1,268.5],[44.8,484.3,689.5,268.5],[421.3,313.3,511.8,268.5],[231.5,68.5,487.5,268.5],[469.3,517.1,411.2,268.5],[587.8,421.2,282.5,268.5],[497.0,338.7,365.9,268.5],[117.1,462.3,537.4,268.5],[205.4,228.7,715.4,268.5],[150.4,372.2,638.5,268.5],[280.2,372.3,662.8,268.5],[189.1,20.7,690.6,268.5],[351.4,254.9,558.3,268.5],[28.5,361.5,531.3,268.5],[36.0,137.3,586.9,268.5],[132.3,353.8,567.8,268.5],[365.2,304.9,662.9,268.5],[475.0,404.6,454.5,268.5],[430.1,308.5,409.2,268.5],[445.6,59.5,718.4,268.5],[298.9,154.5,615.7,268.5],[697.9,179.8,945.6,268.5],[486.3,300.0,420.3,268.5],[69.1,103.0,842.2,268.5],[16.2,178.0,807.8,268.5],[456.2,421.9,677.9,268.5],[111.8,58.7,419.3,268.5],[457.9,556.8,498.5,268.5],[109.3,447.9,730.4,268.5],[102.7,52.9,529.9,268.5],[63.4,273.4,708.1,268.5],[269.2,572.9,551.9,268.5],[504.6,393.5,703.2,268.5],[518.1,512.8,776.0,268.5],[692.4,139.5,363.4,268.5],[81.2,467.9,845.8,268.5],[15.6,511.0,534.8,268.5],[295.6,52.6,667.1,268.5],[296.2,167.0,516.6,268.5],[371.0,586.7,463.2,268.5],[127.0,531.6,492.6,268.5],[388.1,564.7,582.3,268.5],[527.6,595.7,846.1,268.5],[542.7,367.5,568.2,268.5],[407.9,458.3,405.0,268.5],[339.1,550.6,491.6,268.5],[164.6,509.1,564.6,268.5],[359.8,569.6,676.7,268.5],[252.2,420.7,475.5,268.5],[237.8,198.6,913.0,268.5],[455.9,65.6,292.5,268.5],[115.0,475.2,871.2,268.5],[493.5,242.2,597.9,268.5],[350.0,141.8,443.3,268.5],[260.0,470.2,448.0,268.5],[76.3,453.5,586.2,268.5],[374.1,195.7,517.0,268.5],[151.0,227.5,781.8,268.5],[762.1,310.5,457.1,268.5],[644.8,242.7,649.5,268.5],[149.8,287.5,538.1,268.5],[515.3,71.1,540.3,268.5],[55.6,441.5,643.8,268.5],[385.7,484.8,415.4,268.5],[48.8,292.0,460.0,268.5],[325.5,86.8,573.7,268.5],[704.6,509.8,572.6,268.5],[733.1,162.6,364.7,268.5],[111.1,13.9,567.2,268.5],[563.9,335.9,434.5,268.5],[230.1,100.9,641.6,268.5],[429.5,533.8,244.5,268.5],[201.9,237.9,830.3,268.5],[507.9,523.0,397.8,268.5],[79.3,333.2,393.9,268.5],[595.8,557.4,434.7,268.5],[404.5,528.0,253.3,268.5],[376.4,368.6,551.3,268.5],[392.4,595.1,477.6,268.5],[155.0,380.9,694.3,268.5],[603.5,350.7,545.9,268.5],[313.1,510.5,931.7,268.5],[350.9,149.9,547.4,268.5],[333.4,543.3,400.8,268.5],[95.4,425.8,393.2,268.5],[368.0,544.3,404.7,268.5],[182.6,323.6,613.6,268.5],[236.0,472.3,543.7,268.5],[491.6,533.1,667.2,268.5],[194.7,521.2,460.4,268.5],[663.2,445.1,568.7,268.5],[218.3,312.8,477.2,268.5],[295.6,467.2,495.0,268.5],[90.8,561.6,672.4,268.5],[293.2,27.5,635.2,268.5],[442.3,360.3,562.2,268.5],[629.0,202.8,625.5,268.5],[557.0,129.9,391.6,268.5],[749.3,177.2,744.3,268.5],[229.6,277.3,466.5,268.5],[87.9,439.4,625.0,268.5],[91.2,275.6,572.8,268.5],[515.0,390.7,592.5,268.5],[241.4,465.3,457.6,268.5],[76.6,582.5,531.6,268.5],[414.4,346.0,635.1,268.5],[427.8,427.1,526.3,268.5],[418.7,207.4,422.4,268.5],[576.2,356.8,414.0,268.5],[407.4,95.1,910.3,268.5],[355.8,53.4,596.9,268.5],[73.7,344.1,556.1,268.5],[148.1,447.2,464.9,268.5],[220.6,22.3,595.4,268.5],[371.4,413.4,688.9,268.5],[163.2,400.9,406.3,268.5],[389.6,210.0,261.7,268.5],[542.1,334.1,429.7,268.5],[499.3,116.5,474.2,268.5],[126.7,567.5,656.4,268.5],[182.1,321.8,523.1,268.5],[225.3,271.6,493.2,268.5],[264.2,231.3,552.2,268.5],[53.7,376.7,989.7,268.5]];
|
||
(function(){
|
||
var tc=document.getElementById('trekframe'); if(!tc) return;
|
||
var tx=tc.getContext('2d'), W=tc.width, H=tc.height;
|
||
tx.fillStyle='#04070c'; tx.fillRect(0,0,W,H);
|
||
// streaks
|
||
tx.lineWidth=0.8;
|
||
WARP_SEGS.forEach(function(s){
|
||
var g=tx.createLinearGradient(s[0],s[1],s[2],s[3]);
|
||
g.addColorStop(0,'rgba(180,230,255,0.75)');
|
||
g.addColorStop(1,'rgba(40,110,180,0.08)');
|
||
tx.strokeStyle=g;
|
||
tx.beginPath(); tx.moveTo(s[0],s[1]); tx.lineTo(s[2],s[3]); tx.stroke();
|
||
});
|
||
// stars
|
||
WARP_PTS.forEach(function(p){
|
||
tx.fillStyle='rgba(235,250,255,0.95)';
|
||
tx.fillRect(p[0]-0.5,p[1]-0.5,1.6,1.6);
|
||
tx.fillStyle='rgba(120,180,230,0.35)';
|
||
tx.fillRect(p[0]-1.5,p[1]-1.5,3.5,3.5);
|
||
});
|
||
})();
|
||
|
||
/* ---- 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(){
|
||
var tc=document.getElementById('tiles'), tx=tc.getContext('2d');
|
||
var W=tc.width, H=tc.height, NX=TILES.ntx, NY=TILES.nty;
|
||
var lit={}; TILES.touched.forEach(function(t){lit[t[0]+'_'+t[1]]=1;});
|
||
var gap=3;
|
||
var cw=(H-2*gap)/(NY*2); // enforce 64:128 -> cellH = 2*cellW
|
||
var ch=cw*2;
|
||
var gw=NX*cw, gh=NY*ch;
|
||
var ox=(W-gw)/2, oy=(H-gh)/2;
|
||
tx.clearRect(0,0,W,H);
|
||
for(var ty2=0;ty2<NY;ty2++)for(var tx2=0;tx2<NX;tx2++){
|
||
var px=ox+tx2*cw, py=oy+ty2*ch;
|
||
var on=lit[tx2+'_'+ty2];
|
||
if(on){
|
||
tx.fillStyle='rgba(65,255,142,0.16)'; tx.fillRect(px,py,cw-gap,ch-gap);
|
||
tx.strokeStyle='rgba(65,255,142,0.85)'; tx.lineWidth=1.2;
|
||
tx.strokeRect(px+0.5,py+0.5,cw-gap-1,ch-gap-1);
|
||
tx.fillStyle='rgba(65,255,142,0.9)';
|
||
tx.fillRect(px+cw/2-1.5,py+ch/2-1.5,3,3);
|
||
} else {
|
||
tx.strokeStyle='rgba(84,105,122,0.28)'; tx.lineWidth=1;
|
||
tx.strokeRect(px+0.5,py+0.5,cw-gap-1,ch-gap-1);
|
||
}
|
||
}
|
||
// caption-ish label of tile dimensions along the top-left cell
|
||
tx.fillStyle='rgba(95,208,255,0.7)'; tx.font='9px ui-monospace,monospace';
|
||
tx.fillText('64×128',ox+2,oy-4>8?oy-4:10);
|
||
})();
|
||
})();
|
||
</script>
|