A creation shared by its maker. The page below runs in a sandbox that cannot read anything of yours or talk to any server. Read the source before you run it anywhere else.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ergod's Fly — Living Model (connectome-driven)</title>
<style>
:root{ --deep:#04121f; --ink:#7fb8d8; --glow:#5ff0d6; --hot:#e8fff8; --threat:#ff5d8f; --eye:#ff3d6e; }
*{ box-sizing:border-box; }
body{ margin:0; padding:0; background:radial-gradient(130% 90% at 50% 0%, #0a2a40 0%, #04121f 55%, #020a12 100%);
color:#cfeeff; font:14px/1.5 ui-monospace, Menlo, Consolas, monospace; min-height:100vh; }
.wrap{ max-width:1000px; margin:0 auto; padding:20px; }
h1{ font-size:18px; margin:0 0 2px; color:var(--hot); }
.sub{ color:var(--ink); font-size:12px; margin-bottom:14px; }
.stage{ position:relative; border:1px solid #18465e; border-radius:16px; overflow:hidden;
background:rgba(4,18,31,.55); }
#scene{ display:block; width:100%; height:460px; }
.hud{ position:absolute; top:12px; left:14px; font-size:12px; color:var(--ink);
background:rgba(4,18,31,.55); padding:10px 12px; border-radius:10px; border:1px solid #103848; min-width:190px; }
.hud .big{ font-size:22px; color:var(--hot); }
.meter{ height:10px; border-radius:6px; background:#06202f; border:1px solid #18465e; overflow:hidden; margin:5px 0 9px; }
#bar{ height:100%; width:0%; background:linear-gradient(90deg,#1e9bd1,var(--glow)); transition:width .04s linear; }
.row{ display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; }
button{ background:#0c3146; color:var(--glow); border:1px solid #2a6f8c; border-radius:8px;
padding:7px 12px; font:inherit; cursor:pointer; }
button:hover{ background:#114056; }
button.warn{ color:var(--threat); border-color:#7a2c47; }
input[type=range]{ width:130px; }
.escape{ position:absolute; top:14px; right:14px; color:var(--threat); font-weight:bold; font-size:15px;
background:rgba(40,8,20,.6); padding:8px 12px; border-radius:10px; border:1px solid #6a243c;
opacity:0; transition:opacity .15s; }
.escape.on{ opacity:1; }
.controls{ display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:12px; }
.controls .status{ color:var(--ink); font-size:12px; }
#trace{ display:block; width:100%; height:120px; margin-top:12px; border:1px solid #18465e; border-radius:12px; background:#04121f; }
.foot{ color:#5e8aa3; font-size:11px; margin-top:10px; }
code{ color:var(--glow); }
</style>
</head>
<body>
<div class="wrap">
<h1>🐙 Ergod's Fly — Living Model</h1>
<div class="sub">A Drosophila whose motion is driven by the REAL 166,700-neuron connectome run (flybrain v0.1.0).
Wing-beat, leg tremor and the looming escape jump all read from the recorded CNS firing.</div>
<div class="stage">
<canvas id="scene"></canvas>
<div class="hud">
<div>CNS firing rate</div>
<div class="big" id="rate">0</div>
<div style="font-size:11px;color:var(--ink)">spikes / 20 ms step</div>
<div class="meter"><div id="bar"></div></div>
<div id="state" style="color:var(--glow)">idle · wings buzzing</div>
<div style="margin-top:6px">step <span id="k">0/50</span> · t <span id="t">0.00s</span></div>
<div>total spikes <span id="tot" style="color:var(--hot)">0</span></div>
</div>
<div class="escape" id="esc">⚡ GIANT-FIBER ESCAPE</div>
</div>
<div class="controls">
<button id="play">▶ play</button>
<button id="restart">↺ restart</button>
<button id="loom" class="warn">☄ looming stimulus</button>
<span class="status">speed</span><input id="speed" type="range" min="1" max="24" value="9">
<span class="status" id="speedv">9× slow-mo</span>
</div>
<canvas id="trace"></canvas>
<div class="foot">Replay of recorded activity (data from <code>bring_fly_to_life.py</code>). The fly's escape at t=0.22 s is the real giant-fiber (DNp01) firing reproduced here as motion.
For the LIVE Python brain, run that script in this workspace.</div>
</div>
<script>
const ACT = [0,0,0,0,5,17,27,122,225,358,645,1035,1396,2014,2995,4146,6178,
10588,14025,15485,15029,14266,13021,12820,12500,12516,12664,12848,13023,13089,
12888,12741,13187,12968,12859,13765,13822,13561,13699,13776,12211,12252,12385,
12327,12791,13108,12463,12354,12457,12691];
const DT=0.020, LOOM_STEP=10, ESC_STEP=11, PEAK=Math.max(...ACT), BASE=75;
const scene=document.getElementById('scene'), sx=scene.getContext('2d');
const trace=document.getElementById('trace'), tx=trace.getContext('2d');
let W=0,H=0;
function resize(){
const dpr=devicePixelRatio||1;
scene.width=scene.clientWidth*dpr; scene.height=scene.clientHeight*dpr; sx.setTransform(dpr,0,0,dpr,0,0);
trace.width=trace.clientWidth*dpr; trace.height=trace.clientHeight*dpr; tx.setTransform(dpr,0,0,dpr,0,0);
W=scene.clientWidth; H=scene.clientHeight;
}
window.addEventListener('resize',resize);
// ---- state ----
let k=0, playing=false, total=0, cursor=0, wingPhase=0, legPhase=0, last=0;
let jumping=false, jumpT=0, escapedThisLoop=false, idleWing=0;
function reset(){ k=0; cursor=0; total=0; jumping=false; jumpT=0; escapedThisLoop=false; }
// ---- draw the Drosophila ----
function drawFly(cx,cy,rot,scale,wingAng,legJit,lit){
sx.save(); sx.translate(cx,cy); sx.rotate(rot); sx.scale(scale,scale);
// ABDOMEN — 6 tapering segments (A1..A6), behind thorax (+x)
for(let i=0;i<6;i++){
const x0=-6+i*8, h=Math.max(2.5, 16-i*2.0);
sx.beginPath(); sx.ellipse(x0,0,4.5,h/2,0,0,7);
const ag=sx.createLinearGradient(x0-4,0,x0+4,0);
ag.addColorStop(0,'#0a1c26'); ag.addColorStop(1,'#123845');
sx.fillStyle=ag; sx.fill();
sx.strokeStyle='rgba(95,240,214,'+(0.10+lit*0.32)+')'; sx.lineWidth=0.8; sx.stroke();
}
// THORAX — robust center; head at -x, abdomen at +x
sx.beginPath(); sx.ellipse(-25,0,20,16,0,0,7);
const th=sx.createRadialGradient(-28,-5,2,-25,0,22);
th.addColorStop(0,'#1c4a5c'); th.addColorStop(1,'#0a1c26');
sx.fillStyle=th; sx.fill();
sx.strokeStyle='rgba(95,240,214,'+(0.15+lit*0.5)+')'; sx.lineWidth=1; sx.stroke();
sx.strokeStyle='rgba(95,240,214,'+(0.10+lit*0.3)+')'; sx.lineWidth=0.8;
sx.beginPath(); sx.moveTo(-40,-6); sx.quadraticCurveTo(-25,8,-10,-6); sx.stroke();
// HEAD — forward at -x (neck to thorax)
sx.beginPath(); sx.ellipse(-50,-1,11,12,0,0,7); sx.fillStyle='#0c232e'; sx.fill();
sx.strokeStyle='rgba(95,240,214,'+(0.12+lit*0.4)+')'; sx.lineWidth=0.8; sx.stroke();
// compound eyes (glowing hemispheres)
for(const s of [-1,1]){
const g=sx.createRadialGradient(-50,s*7,1,-50,s*7,8);
g.addColorStop(0,'#ff7da3'); g.addColorStop(0.6,'#ff3d6e'); g.addColorStop(1,'#7a0f33');
sx.beginPath(); sx.ellipse(-51,s*7,6,7,0,0,7); sx.fillStyle=g; sx.fill();
}
// antennae with feathery arista
for(const s of [-1,1]){
sx.strokeStyle='#1c4a5c'; sx.lineWidth=1.4;
sx.beginPath(); sx.moveTo(-58,s*2); sx.lineTo(-64,s*7); sx.stroke();
sx.strokeStyle='rgba(160,245,255,0.5)'; sx.lineWidth=0.6;
for(let b=-3;b<=3;b++){ sx.beginPath(); sx.moveTo(-64,s*7); sx.lineTo(-64+b, s*7+b*1.4); sx.stroke(); }
}
// WINGS — one pair, membrane + veins, rooted at MESOTHORAX (mid-thorax)
for(const s of[-1,1]){
sx.save(); sx.translate(-25,s*5); sx.rotate(s*wingAng);
sx.beginPath(); sx.ellipse(42,0,44,15,0,0,7);
sx.fillStyle='rgba(120,230,255,'+(0.10+lit*0.18)+')'; sx.fill();
sx.strokeStyle='rgba(160,245,255,'+(0.25+lit*0.4)+')'; sx.lineWidth=1; sx.stroke();
sx.strokeStyle='rgba(160,245,255,'+(0.12+lit*0.2)+')'; sx.lineWidth=0.6;
for(let v=0;v<4;v++){ sx.beginPath(); sx.moveTo(2,0); sx.lineTo(84, s*(2+v*4)); sx.stroke(); }
sx.restore();
}
// HALTERES — one pair of drumstick balancers at METATHORAX (behind wing roots)
for(const s of[-1,1]){
sx.save(); sx.translate(-18,s*6);
sx.strokeStyle='#2a6076'; sx.lineWidth=1.6;
sx.beginPath(); sx.moveTo(0,0); sx.lineTo(0,s*5); sx.stroke();
sx.beginPath(); sx.ellipse(0,s*6,2.6,4.5,s*0.4,0,7); sx.fillStyle='#5ff0d6'; sx.fill();
sx.restore();
}
// LEGS — 3 pairs, each rooted at a distinct coxa (fore/mid/hind along thorax)
sx.strokeStyle='#2a6076'; sx.lineWidth=1.4;
const coxae=[[-40,1],[-28,1],[-16,1],[-40,-1],[-28,-1],[-16,-1]]; // fore/mid/hind x, side
coxae.forEach(([lx,side],i)=>{
const j=(Math.sin(legPhase*2+i)*legJit);
const cy=side*6;
const cx2=lx+(side*4), cy2=cy+side*9+j;
const fx=lx+(side*9), fy=cy+side*20+j*1.3;
const tx=lx+(side*6), ty=cy+side*30+j*1.7;
sx.beginPath(); sx.moveTo(lx,cy); sx.lineTo(cx2,cy2); sx.lineTo(fx,fy); sx.lineTo(tx,ty); sx.stroke();
});
sx.restore();
}
function frameRate(rate){ return 5 + rate/220; } // wing Hz-ish (visual)
function drawScene(rate,lit){
sx.clearRect(0,0,W,H);
// connectome glow behind
const g=sx.createRadialGradient(W/2,H*0.62,10,W/2,H*0.62,260);
g.addColorStop(0,'rgba(95,240,214,'+(0.05+lit*0.22)+')');
g.addColorStop(1,'rgba(4,18,31,0)');
sx.fillStyle=g; sx.fillRect(0,0,W,H);
// floor reflection line
sx.strokeStyle='rgba(40,90,110,0.4)'; sx.lineWidth=1;
sx.beginPath(); sx.moveTo(0,H*0.86); sx.lineTo(W,H*0.86); sx.stroke();
// fly position + jump
const baseY=H*0.6, baseX=W*0.5;
let px=baseX, py=baseY, rot=0, scale=1.05;
if(jumping){
const p=Math.min(1,jumpT/0.7);
const arc=Math.sin(p*Math.PI);
py=baseY - arc*150; // launch arc
px=baseX + Math.sin(p*Math.PI)*40; // little drift
rot=Math.sin(p*Math.PI*2)*0.5; // tumble
scale=1.05 + arc*0.25;
}
const wingAng=Math.sin(wingPhase)*(0.35+lit*0.55) + (jumping?0.6:0);
const legJit=Math.min(3, rate/2500);
drawFly(px,py,rot,scale,wingAng,legJit,lit);
// motion trail during escape
if(jumping){
for(let i=1;i<=5;i++){
sx.globalAlpha=0.10*(1-i/6);
drawFly(px, py+i*14, rot, scale*(1-i*0.02), wingAng, legJit, lit*0.5);
}
sx.globalAlpha=1;
}
}
function drawTrace(){
const w=trace.clientWidth, h=trace.clientHeight;
tx.clearRect(0,0,w,h);
const lx=(LOOM_STEP/ACT.length)*w;
tx.fillStyle='rgba(255,93,143,0.07)'; tx.fillRect(lx,0,w-lx,h);
tx.strokeStyle='#103848'; for(let gy=0;gy<=4;gy++){ const y=h*gy/4; tx.beginPath(); tx.moveTo(0,y); tx.lineTo(w,y); tx.stroke(); }
tx.beginPath(); tx.strokeStyle='#5ff0d6'; tx.lineWidth=1.6;
for(let i=0;i<=Math.floor(cursor);i++){ const x=(i/ACT.length)*w, y=h-(ACT[i]/PEAK)*h; i?tx.lineTo(x,y):tx.moveTo(x,y); }
tx.stroke();
const hx=(cursor/ACT.length)*w; tx.strokeStyle='#e8fff8'; tx.beginPath(); tx.moveTo(hx,0); tx.lineTo(hx,h); tx.stroke();
}
// ---- loop ----
function tick(now){
if(!last) last=now; const dt=Math.min(0.05,(now-last)/1000); last=now;
const rate = ACT[Math.floor(cursor)]||0;
const lit = Math.min(1, rate/PEAK);
// wing + leg phases advance with activity
wingPhase += frameRate(rate)*6.28*dt*(playing?1:0.35);
legPhase += dt*(playing?6:2);
if(playing){
cursor += (parseInt(speed.value)/10)*dt; // steps per real second scaled by speed
if(cursor>=ACT.length){ cursor=0; total=0; escapedThisLoop=false; }
k=Math.floor(cursor);
total += (cursor - (k>0?k-1:0))>0 ? rate : 0;
if(k===ESC_STEP && !escapedThisLoop){ jumping=true; jumpT=0; escapedThisLoop=true; document.getElementById('esc').classList.add('on'); }
}
if(jumping){ jumpT+=dt; if(jumpT>0.7){ jumping=false; document.getElementById('esc').classList.remove('on'); } }
drawScene(rate,lit); drawTrace();
// HUD
document.getElementById('rate').textContent=rate;
document.getElementById('bar').style.width=(rate/PEAK*100)+'%';
document.getElementById('k').textContent=k+'/'+ACT.length;
document.getElementById('t').textContent=(k*DT).toFixed(2)+'s';
document.getElementById('tot').textContent=total.toLocaleString();
let st='idle · wings buzzing';
if(k>=LOOM_STEP) st='⚠ looming detected · tensing';
if(jumping) st='⚡ ESCAPE JUMP!';
else if(rate>3000) st='CNS firing · legs jittering';
document.getElementById('state').textContent=st;
requestAnimationFrame(tick);
}
const speed=document.getElementById('speed');
document.getElementById('play').onclick=function(){
playing=!playing; this.textContent=playing?'⏸ pause':'▶ play';
};
document.getElementById('restart').onclick=function(){ reset(); cursor=0; playing=true; document.getElementById('play').textContent='⏸ pause'; };
document.getElementById('loom').onclick=function(){ if(cursor<LOOM_STEP){ total=ACT.slice(0,LOOM_STEP).reduce((a,b)=>a+b,0); cursor=LOOM_STEP; } };
speed.oninput=function(){ document.getElementById('speedv').textContent=this.value+'× slow-mo'; };
resize(); reset(); cursor=0; playing=true;
document.getElementById('play').textContent='⏸ pause';
requestAnimationFrame(tick);
</script>
</body>
</html>