neural-drive / index.html
codelion's picture
move first-in-browser claim onto ignition/boot screen
0f7a949 verified
Raw
History Blame Contribute Delete
16.3 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>NEURAL DRIVE — a world model you can steer</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;500;600;700&family=Azeret+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{
--bg:#08090c; --bg2:#0d0f14; --panel:#111319; --line:#1d212b;
--amber:#ffb020; --amber-hot:#ff6a00; --cyan:#57e6d4; --ink:#eceae4;
--dim:#727a88; --dimmer:#3a404c; --red:#ff5566;
--grid:rgba(255,255,255,.028);
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
background:var(--bg); color:var(--ink);
font-family:'Chakra Petch',sans-serif; letter-spacing:.02em;
min-height:100%; overflow-x:hidden;
background-image:
radial-gradient(1200px 600px at 70% -10%, rgba(255,176,32,.06), transparent 60%),
radial-gradient(900px 500px at 10% 110%, rgba(87,230,212,.05), transparent 60%),
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
background-size:auto,auto,44px 44px,44px 44px;
}
/* film grain */
body::after{
content:""; position:fixed; inset:0; pointer-events:none; z-index:99; opacity:.05;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
mix-blend-mode:overlay;
}
.wrap{max-width:980px; margin:0 auto; padding:34px 22px 60px; position:relative; z-index:1}
/* header */
header{margin-bottom:24px}
.brand{display:flex; flex-direction:column; gap:6px; width:100%}
.lede{font-family:'Azeret Mono',monospace; font-size:13px; color:var(--dim); line-height:1.55; margin-top:12px; max-width:none; width:100%}
.lede b{color:var(--ink); font-weight:500}
.mlink{color:var(--amber); text-decoration:none; white-space:nowrap; border-bottom:1px solid rgba(255,176,32,.35); transition:border-color .15s}
.mlink:hover{border-color:var(--amber)}
.kicker{font-family:'Azeret Mono',monospace; font-size:11px; letter-spacing:.42em; color:var(--amber); text-transform:uppercase}
h1{font-size:clamp(30px,6vw,54px); font-weight:700; line-height:.92; letter-spacing:-.01em}
h1 .slash{color:var(--dimmer); font-weight:400}
h1 em{color:var(--cyan); font-style:normal}
.sub{font-family:'Azeret Mono',monospace; font-size:12px; color:var(--dim); max-width:42ch; line-height:1.5; margin-top:8px}
.spec{font-family:'Azeret Mono',monospace; font-size:11px; color:var(--dim); text-align:right; line-height:1.9; white-space:nowrap}
.spec b{color:var(--ink); font-weight:500}
/* console */
.console{
border:1px solid var(--line); background:linear-gradient(180deg,var(--panel),#0b0d12);
border-radius:14px; padding:16px; position:relative;
box-shadow:0 40px 80px -40px rgba(0,0,0,.9), inset 0 1px 0 rgba(255,255,255,.03);
}
.viewport{position:relative; border-radius:9px; overflow:hidden; background:#000; aspect-ratio:2/1;
outline:1px solid var(--line); box-shadow:inset 0 0 0 1px rgba(0,0,0,.6), 0 0 60px -20px rgba(255,176,32,.25);}
#screen{width:100%; height:100%; display:block; image-rendering:pixelated; filter:saturate(1.06) contrast(1.03)}
/* scanlines + vignette + glow overlays on the viewport */
.viewport::before{content:""; position:absolute; inset:0; pointer-events:none; z-index:3;
background:repeating-linear-gradient(0deg, rgba(0,0,0,.16) 0 1px, transparent 1px 3px); mix-blend-mode:multiply; opacity:.5;}
.viewport::after{content:""; position:absolute; inset:0; pointer-events:none; z-index:4;
box-shadow:inset 0 0 120px 6px rgba(0,0,0,.6); background:radial-gradient(120% 120% at 50% 40%, transparent 60%, rgba(0,0,0,.35));}
/* HUD corner brackets */
.bkt{position:absolute; width:20px; height:20px; border:2px solid var(--amber); opacity:.55; z-index:5; pointer-events:none}
.bkt.tl{top:9px;left:9px;border-right:0;border-bottom:0}.bkt.tr{top:9px;right:9px;border-left:0;border-bottom:0}
.bkt.bl{bottom:9px;left:9px;border-right:0;border-top:0}.bkt.br{bottom:9px;right:9px;border-left:0;border-top:0}
/* boot overlay */
.boot{position:absolute; inset:0; z-index:8; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:18px;
background:radial-gradient(120% 120% at 50% 30%, #10131a, #06070a); text-align:center; padding:24px}
.boot.hide{opacity:0; visibility:hidden; transition:opacity .5s ease, visibility .5s}
.boot .big{font-size:clamp(18px,3.5vw,26px); font-weight:600; letter-spacing:.06em}
.boot-claim{font-family:'Azeret Mono',monospace; font-size:12px; letter-spacing:.16em; text-transform:uppercase; color:var(--amber); text-align:center; opacity:.92; margin-top:-6px}
.boot .status{font-family:'Azeret Mono',monospace; font-size:12px; color:var(--dim); min-height:1.4em; letter-spacing:.05em}
.bar{width:min(70%,360px); height:6px; border:1px solid var(--line); border-radius:6px; overflow:hidden; background:#0a0c11}
.bar i{display:block; height:100%; width:0%; background:linear-gradient(90deg,var(--amber-hot),var(--amber)); box-shadow:0 0 14px var(--amber); transition:width .2s}
.ignite{font-family:'Chakra Petch'; font-weight:700; letter-spacing:.28em; text-transform:uppercase; font-size:15px;
color:#0a0a0a; background:linear-gradient(180deg,var(--amber),var(--amber-hot)); border:0; padding:14px 30px; border-radius:8px; cursor:pointer;
box-shadow:0 0 34px -6px var(--amber); animation:pulse 1.8s ease-in-out infinite; display:none}
.ignite:hover{filter:brightness(1.08)}
@keyframes pulse{0%,100%{box-shadow:0 0 20px -6px var(--amber)}50%{box-shadow:0 0 40px 2px var(--amber)}}
/* telemetry strip */
.telem{display:grid; grid-template-columns:repeat(4,1fr); gap:10px; margin-top:14px}
.cell{border:1px solid var(--line); border-radius:8px; padding:10px 12px; background:#0b0d12}
.cell .l{font-family:'Azeret Mono',monospace; font-size:9.5px; letter-spacing:.24em; color:var(--dim); text-transform:uppercase}
.cell .v{font-family:'Azeret Mono',monospace; font-size:19px; font-weight:600; color:var(--ink); margin-top:3px}
.cell .v small{font-size:11px; color:var(--dim); font-weight:400}
.cell .v.hot{color:var(--amber)} .cell .v.cool{color:var(--cyan)}
/* controls */
.controls{display:flex; align-items:center; justify-content:space-between; gap:18px; flex-wrap:wrap; margin-top:20px}
.keys{display:flex; gap:8px; align-items:center; flex-wrap:wrap}
.kc{font-family:'Azeret Mono',monospace; font-size:12px; min-width:34px; height:34px; padding:0 9px; display:flex; align-items:center; justify-content:center;
border:1px solid var(--line); border-bottom-width:3px; border-radius:7px; background:#0e1016; color:var(--dim); transition:all .08s}
.kc.on{background:var(--amber); color:#0a0a0a; border-color:var(--amber-hot); box-shadow:0 0 18px -3px var(--amber); transform:translateY(1px)}
.kc small{font-size:9px; opacity:.7; margin-left:5px}
.btns{display:flex; gap:10px}
.btn{font-family:'Chakra Petch'; font-weight:600; letter-spacing:.14em; text-transform:uppercase; font-size:12px; cursor:pointer;
border:1px solid var(--line); background:#0e1016; color:var(--ink); padding:11px 18px; border-radius:8px; transition:all .12s}
.btn:hover{border-color:var(--amber); color:var(--amber)}
.btn.ghost{color:var(--dim)}
footer{margin-top:26px; font-family:'Azeret Mono',monospace; font-size:11px; color:var(--dimmer); line-height:1.7}
footer a{color:var(--dim); text-decoration:none; border-bottom:1px solid var(--dimmer)}
.warn{color:var(--red)}
@media(max-width:620px){ .telem{grid-template-columns:repeat(2,1fr)} .spec{text-align:left} }
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="brand">
<h1>NEURAL<span class="slash">/</span><em>DRIVE</em></h1>
<p class="lede">A <a class="mlink" href="https://huggingface.co/codelion/neural-drive-model" target="_blank" rel="noopener">world model</a> trained automatically on SuperTuxKart gameplay videos, running entirely inside your browser.</p>
</div>
</header>
<div class="console">
<div class="viewport">
<canvas id="screen" width="384" height="192"></canvas>
<span class="bkt tl"></span><span class="bkt tr"></span><span class="bkt bl"></span><span class="bkt br"></span>
<div class="boot" id="boot">
<div class="big" id="bootBig">IGNITION SEQUENCE</div>
<div class="boot-claim">The world's first in-browser world model</div>
<div class="bar"><i id="bar"></i></div>
<div class="status" id="bootStatus">initializing…</div>
<button class="ignite" id="ignite">▶ Ignition</button>
</div>
</div>
<div class="telem">
<div class="cell"><div class="l">Framerate</div><div class="v hot" id="tFps"><small>fps</small></div></div>
<div class="cell"><div class="l">Frame time</div><div class="v" id="tMs"><small>ms</small></div></div>
<div class="cell"><div class="l">Compute</div><div class="v cool" id="tDev"></div></div>
<div class="cell"><div class="l">Inputs</div><div class="v" id="tAct"></div></div>
</div>
</div>
<div class="controls">
<div class="keys" id="keys">
<span class="kc" data-k="ArrowUp"><small>accel</small></span>
<span class="kc" data-k="ArrowLeft"><small>left</small></span>
<span class="kc" data-k="ArrowDown"><small>brake</small></span>
<span class="kc" data-k="ArrowRight"><small>right</small></span>
<span class="kc" data-k="KeyD">D<small>drift</small></span>
</div>
<div class="btns">
<button class="btn" id="respawn">⟲ Respawn</button>
<button class="btn ghost" id="pause">Ⅱ Pause</button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/ort.webgpu.min.js"></script>
<script>
const $=id=>document.getElementById(id);
const bar=$("bar"), bootStatus=$("bootStatus"), boot=$("boot"), bootBig=$("bootBig"), ignite=$("ignite");
let META, SEEDS, dit, vae, ctx=[], acts=[], running=false, DEV="—";
let N, ROW, K;
const key={ArrowUp:0,ArrowDown:0,ArrowLeft:0,ArrowRight:0,KeyD:0};
function setStatus(t){bootStatus.textContent=t}
function setBar(p){bar.style.width=(p*100).toFixed(0)+"%"}
async function fetchProgress(url, label){
const res=await fetch(url);
const total=+res.headers.get("content-length")||0;
const reader=res.body.getReader(); let recd=0; const chunks=[];
while(true){const{done,value}=await reader.read(); if(done)break; chunks.push(value); recd+=value.length;
if(total) setStatus(label+" "+(recd/1e6|0)+" / "+(total/1e6|0)+" MB"); }
const buf=new Uint8Array(recd); let o=0; for(const c of chunks){buf.set(c,o); o+=c.length;}
return buf;
}
// Box–Muller gaussian noise
function randn(n){const a=new Float32Array(n); for(let i=0;i<n;i+=2){
let u=0,v=0; while(u===0)u=Math.random(); while(v===0)v=Math.random();
const m=Math.sqrt(-2*Math.log(u)); a[i]=m*Math.cos(2*Math.PI*v); if(i+1<n)a[i+1]=m*Math.sin(2*Math.PI*v);} return a;}
async function boot_(){
try{
setStatus("loading manifest…");
META=await (await fetch("meta.json")).json();
N=META.z_ch*META.hz*META.wz; ROW=META.action_dim; K=META.k;
setStatus("loading seed states…");
const sb=await fetchProgress("seeds.bin","seed states");
SEEDS=new Float32Array(sb.buffer);
const hasGPU = !!navigator.gpu;
DEV = hasGPU ? "WebGPU" : "WASM";
ort.env.wasm.wasmPaths="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/";
if(!hasGPU) ort.env.wasm.numThreads = Math.min(4, navigator.hardwareConcurrency||4);
const eps = hasGPU ? ["webgpu","wasm"] : ["wasm"];
setBar(.05);
setStatus("downloading dynamics model…");
const ditBuf=await fetchProgress("genframe_fp16.onnx","dynamics"); setBar(.7);
setStatus("compiling dynamics on "+DEV+"…");
dit=await ort.InferenceSession.create(ditBuf,{executionProviders:eps, graphOptimizationLevel:"all"}); setBar(.85);
setStatus("downloading decoder…");
const vaeBuf=await fetchProgress("vae.onnx","decoder");
vae=await ort.InferenceSession.create(vaeBuf,{executionProviders:eps, graphOptimizationLevel:"all"}); setBar(1);
$("tDev").textContent=DEV;
spawn((Math.random()*META.seeds_shape[0])|0);
await warm();
bootBig.textContent = hasGPU ? "NEURAL ENGINE READY" : "READY (CPU — will be slow)";
if(!hasGPU) bootBig.classList.add("warn");
setStatus("arrow keys to drive · D drift");
ignite.style.display="inline-block";
}catch(e){
bootBig.textContent="INITIALIZATION FAILED"; bootBig.classList.add("warn");
setStatus(String(e).slice(0,140));
console.error(e);
}
}
function spawn(i){
const per=META.ctx*N; const base=i*per;
ctx=[]; acts=[];
for(let f=0;f<META.ctx;f++){ ctx.push(SEEDS.subarray(base+f*N, base+(f+1)*N).slice()); acts.push(new Float32Array(ROW)); }
}
function curAction(){
// [accel, brake, left, right, nitro, drift]
return Float32Array.from([key.ArrowUp,key.ArrowDown,key.ArrowLeft,key.ArrowRight,0,key.KeyD]);
}
async function genFrame(act){
// single graph does prefill(7 ctx frames) once + k decode steps internally (KV-cache)
const czbuf=new Float32Array(7*N);
for(let f=0;f<7;f++) czbuf.set(ctx[f], f*N);
const cabuf=new Float32Array(7*ROW);
for(let f=0;f<7;f++) cabuf.set(acts[f], f*ROW);
const out=await dit.run({
ctx_z:new ort.Tensor("float32",czbuf,[1,7,META.z_ch,META.hz,META.wz]),
ctx_a:new ort.Tensor("float32",cabuf,[1,7,ROW]),
cur_a:new ort.Tensor("float32",act,[1,ROW]),
noise:new ort.Tensor("float32",randn(N),[1,META.z_ch,META.hz,META.wz])
});
return out.cur.data;
}
const oc=$("screen").getContext("2d"); const IMG=oc.createImageData(384,192);
async function decode(lat){
const zT=new ort.Tensor("float32",lat,[1,META.z_ch,META.hz,META.wz]);
const out=await vae.run({zin:zT}); const p=out.img.data; // [1,3,192,384] CHW
const HW=192*384, d=IMG.data;
for(let i=0;i<HW;i++){
let r=p[i], g=p[HW+i], b=p[2*HW+i];
r=r>=0&&r<=1?r:(r<0?0:(r>1?1:0)); g=g>=0&&g<=1?g:(g<0?0:(g>1?1:0)); b=b>=0&&b<=1?b:(b<0?0:(b>1?1:0));
d[i*4]=r*255; d[i*4+1]=g*255; d[i*4+2]=b*255; d[i*4+3]=255;
}
oc.putImageData(IMG,0,0);
}
async function warm(){ const c=await genFrame(new Float32Array(ROW)); await decode(c); }
let ema=0;
async function loop(){
if(!running) return;
const t0=performance.now();
const act=curAction();
const cur=await genFrame(act);
ctx.push(cur); if(ctx.length>7)ctx.shift();
acts.push(act); if(acts.length>7)acts.shift();
await decode(cur);
const dt=performance.now()-t0; ema=ema?ema*.85+dt*.15:dt;
$("tMs").innerHTML=(ema|0)+" <small>ms</small>";
$("tFps").innerHTML=(1000/ema).toFixed(1)+" <small>fps</small>";
const names=["ACCEL","BRAKE","LEFT","RIGHT","NITRO","DRIFT"];
const on=[...act].map((v,i)=>v?names[i]:null).filter(Boolean);
$("tAct").textContent=on.length?on.join(" · "):"—";
requestAnimationFrame(loop);
}
function start(){ if(running)return; running=true; boot.classList.add("hide"); loop(); }
// keyboard
const codes=Object.keys(key);
addEventListener("keydown",e=>{ if(codes.includes(e.code)){key[e.code]=1; paint(); if(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(e.code))e.preventDefault();}});
addEventListener("keyup",e=>{ if(codes.includes(e.code)){key[e.code]=0; paint();}});
function paint(){ document.querySelectorAll(".kc").forEach(el=>el.classList.toggle("on", !!key[el.dataset.k])); }
// pointer support for the on-screen keycaps
document.querySelectorAll(".kc").forEach(el=>{
const k=el.dataset.k, dn=e=>{e.preventDefault();key[k]=1;paint()}, up=e=>{e.preventDefault();key[k]=0;paint()};
el.addEventListener("pointerdown",dn); el.addEventListener("pointerup",up); el.addEventListener("pointerleave",up);
});
$("ignite").onclick=start;
$("respawn").onclick=()=>{ spawn((Math.random()*META.seeds_shape[0])|0); if(!running){decode(ctx[6]);} };
$("pause").onclick=()=>{ running=!running; $("pause").textContent=running?"Ⅱ Pause":"▶ Resume"; if(running)loop(); };
boot_();
</script>
</body>
</html>