← Canyon Run results

Canyon Run

Mimo v2.6 Flash · full

◈Preview unavailable — try playing the result

Browser check: Preview unavailable or timed out. This is a technical check, not a quality review.

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 72,770 bytes · SHA-256 c7ab82adfd96
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>HELIOS CANYON — Procedural Sunset Racer</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=Rajdhani:wght@500;600;700&family=Orbitron:wght@600;800&display=swap" rel="stylesheet">
<style>
:root{
  --or:#ff7b2e; --or2:#ffb066; --mg:#ff3d81; --pu:#a24bff; --sil:#e6ecff;
  --glass:rgba(12,8,24,.5);
  --edge:rgba(255,178,110,.32);
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;width:100%;overflow:hidden;background:#06040c;color:#e9e2ff;
  font-family:'Rajdhani',"Segoe UI",system-ui,sans-serif;-webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;user-select:none;touch-action:none;cursor:crosshair}
canvas#view{position:fixed;inset:0;width:100%;height:100%;display:block;z-index:0}
#vig{position:fixed;inset:0;z-index:1;pointer-events:none;
  background:radial-gradient(ellipse at 50% 52%,rgba(0,0,0,0) 38%,rgba(6,3,14,.55) 82%,rgba(4,2,10,.9) 100%);}
#vig::after{content:"";position:absolute;inset:0;
  background:repeating-linear-gradient(to bottom,rgba(255,255,255,.022) 0 1px,rgba(0,0,0,0) 1px 3px);
  mix-blend-mode:overlay;opacity:.5}

/* ---------------- HUD ---------------- */
#hud{position:fixed;inset:0;z-index:3;pointer-events:none;opacity:0;transition:opacity .5s ease}
#hud.on{opacity:1}
.tnum{font-variant-numeric:tabular-nums}
.lbl{font-size:11px;letter-spacing:.34em;font-weight:600;color:rgba(233,226,255,.5);text-transform:uppercase}

#scoreBox{position:absolute;top:26px;left:30px;padding-left:14px;border-left:2px solid var(--or)}
#scoreBox .val{font-family:'Orbitron',sans-serif;font-weight:800;font-size:40px;line-height:1;
  letter-spacing:.04em;color:#fff;text-shadow:0 0 24px rgba(255,122,46,.55);margin-top:4px}
#mult{margin-top:6px;font-family:'Orbitron',sans-serif;font-weight:800;font-size:26px;line-height:1;
  color:var(--or2);text-shadow:0 0 18px rgba(255,140,60,.7);transform-origin:left center;letter-spacing:.02em}
#mult.hot{color:#fff;text-shadow:0 0 30px rgba(255,90,140,.95),0 0 60px rgba(255,140,60,.6)}
#mult.pulse{animation:mpulse .42s cubic-bezier(.2,1.6,.4,1)}
@keyframes mpulse{0%{transform:scale(1)}28%{transform:scale(1.45)}100%{transform:scale(1)}}

#topRight{position:absolute;top:26px;right:30px;text-align:right;display:flex;gap:26px;align-items:flex-start}
#topRight .cell .v{font-family:'Orbitron',sans-serif;font-weight:600;font-size:17px;color:var(--sil);
  letter-spacing:.06em;margin-top:3px;opacity:.9}
#snd{pointer-events:auto;cursor:pointer;background:var(--glass);border:1px solid var(--edge);
  color:var(--or2);font-family:inherit;font-size:10px;letter-spacing:.2em;padding:5px 9px;border-radius:2px;
  transition:.2s;opacity:.8}
#snd:hover{background:rgba(255,122,46,.18);opacity:1}
#snd.off{color:rgba(233,226,255,.35);border-color:rgba(255,255,255,.12)}

/* bottom left bars */
#bars{position:absolute;left:30px;bottom:30px;width:290px}
.barrow{display:flex;align-items:center;gap:10px;margin-top:9px}
.barrow .bl{width:52px;font-size:10px;letter-spacing:.2em;font-weight:600;color:rgba(233,226,255,.45)}
.bar{position:relative;flex:1;height:7px;background:rgba(255,255,255,.07);
  border:1px solid rgba(255,255,255,.09);overflow:hidden;transform:skewX(-18deg)}
.bar i{position:absolute;inset:0 auto 0 0;width:50%;display:block;
  background:linear-gradient(90deg,#ffd166,#ff7b2e 42%,#ff3d81);transition:width .09s linear;
  box-shadow:0 0 14px rgba(255,110,60,.85)}
.bar.t{height:5px}
.bar.t i{background:linear-gradient(90deg,#7ef9ff,#49b6ff 55%,#a24bff);box-shadow:0 0 14px rgba(90,190,255,.85)}
.bar.low i{background:linear-gradient(90deg,#ff3d81,#ff2b2b);animation:lowblink .55s steps(2) infinite}
@keyframes lowblink{50%{opacity:.35}}

/* bottom right speedo */
#speedo{position:absolute;right:26px;bottom:22px;width:214px;height:132px}
#speedo svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
#spdNum{position:absolute;left:0;right:0;bottom:26px;text-align:center;font-family:'Orbitron',sans-serif;
  font-weight:800;font-size:38px;line-height:1;color:#fff;text-shadow:0 0 22px rgba(120,200,255,.55)}
#spdUnit{position:absolute;left:0;right:0;bottom:8px;text-align:center;font-size:10px;letter-spacing:.4em;
  color:rgba(233,226,255,.42)}
#boostTag{position:absolute;left:0;right:0;bottom:112px;text-align:center;font-size:11px;letter-spacing:.36em;
  color:#7ef9ff;opacity:0;transition:opacity .15s;font-weight:700}
#boostTag.on{opacity:1;animation:bst .5s linear infinite}
@keyframes bst{50%{opacity:.35}}

/* centre popups + alerts */
#pops{position:absolute;inset:0;overflow:hidden}
.pop{position:absolute;transform:translate(-50%,-50%);font-family:'Orbitron',sans-serif;font-weight:800;
  font-size:17px;color:#fff;text-shadow:0 0 16px rgba(255,120,60,.9);white-space:nowrap;
  animation:pop .95s cubic-bezier(.15,.85,.3,1) forwards}
.pop.nm{font-size:13px;letter-spacing:.28em;color:var(--or2);text-shadow:0 0 14px rgba(255,140,60,.9)}
@keyframes pop{0%{opacity:0;transform:translate(-50%,-50%) scale(.5)}
  16%{opacity:1;transform:translate(-50%,-50%) scale(1.22)}
  100%{opacity:0;transform:translate(-50%,-140%) scale(1)}}
#alert{position:absolute;left:0;right:0;top:19%;text-align:center;font-family:'Orbitron',sans-serif;
  font-weight:800;font-size:clamp(20px,3.4vw,40px);letter-spacing:.3em;opacity:0;transition:opacity .25s;
  color:#fff;text-shadow:0 0 30px rgba(255,80,120,.8)}
#alert.on{opacity:1}
#alert.warn{color:#ff5c7a;text-shadow:0 0 34px rgba(255,60,90,.9)}
#alert.good{color:#7ef9ff;text-shadow:0 0 34px rgba(90,220,255,.9)}
#hit{position:absolute;inset:0;background:radial-gradient(ellipse at 50% 50%,rgba(255,40,60,0) 45%,rgba(255,30,50,.55) 100%);
  opacity:0;transition:opacity .5s;pointer-events:none}
#hit.on{opacity:1;transition:opacity .05s}

/* ---------------- OVERLAYS ---------------- */
#ov{position:fixed;inset:0;z-index:5;display:flex;align-items:center;justify-content:center;
  background:radial-gradient(ellipse at 50% 45%,rgba(24,10,44,.42),rgba(4,2,10,.9));
  backdrop-filter:blur(3px);opacity:0;visibility:hidden;transition:opacity .45s ease,visibility .45s}
#ov.show{opacity:1;visibility:visible}
.panel{display:none;flex-direction:column;align-items:center;text-align:center;padding:38px 44px;max-width:640px}
#ov[data-mode="menu"] .p-menu{display:flex}
#ov[data-mode="over"] .p-over{display:flex}
#ov[data-mode="pause"] .p-pause{display:flex}
.kicker{font-size:11px;letter-spacing:.55em;color:var(--or2);font-weight:600;margin-bottom:16px;opacity:.85}
.kicker.red{color:#ff5c7a}
h1{font-family:'Orbitron',sans-serif;font-weight:800;font-size:clamp(38px,7.4vw,84px);line-height:.94;
  letter-spacing:.04em;color:#fff;text-shadow:0 0 44px rgba(255,110,50,.55),0 0 90px rgba(162,75,255,.35)}
h1 span{display:block;color:transparent;-webkit-text-stroke:1.6px var(--or2);text-shadow:0 0 34px rgba(255,140,60,.5)}
.tag{margin-top:16px;font-size:16px;letter-spacing:.16em;color:rgba(233,226,255,.6);font-weight:500}
.btn{margin-top:30px;pointer-events:auto;cursor:pointer;font-family:'Orbitron',sans-serif;font-weight:800;
  font-size:15px;letter-spacing:.3em;color:#0b0714;background:linear-gradient(100deg,#ffd166,#ff7b2e 60%,#ff3d81);
  border:none;padding:15px 42px;position:relative;transition:.22s;clip-path:polygon(12px 0,100% 0,calc(100% - 12px) 100%,0 100%)}
.btn:hover{filter:brightness(1.15);transform:translateY(-2px);box-shadow:0 12px 40px rgba(255,110,50,.4)}
.btn:active{transform:translateY(0)}
.btn.ghost{background:transparent;color:var(--or2);border:1px solid var(--edge);box-shadow:none}
.btn.ghost:hover{background:rgba(255,122,46,.14)}
.hints{margin-top:26px;display:flex;gap:24px;flex-wrap:wrap;justify-content:center}
.hints div{font-size:11.5px;letter-spacing:.16em;color:rgba(233,226,255,.42)}
.hints b{color:var(--or2);font-weight:700;letter-spacing:.2em}
.bestline{margin-top:22px;font-size:11px;letter-spacing:.4em;color:rgba(233,226,255,.4)}
.bestline span{color:#fff;font-family:'Orbitron',sans-serif;letter-spacing:.06em;margin-left:8px}

.final{margin-top:6px;display:flex;flex-direction:column;gap:4px}
.final .lbl{letter-spacing:.42em}
.final .num{font-family:'Orbitron',sans-serif;font-weight:800;font-size:clamp(46px,9vw,88px);line-height:1;
  color:#fff;text-shadow:0 0 40px rgba(255,110,50,.65)}
.stats{margin-top:26px;display:flex;gap:14px;flex-wrap:wrap;justify-content:center}
.stat{min-width:140px;padding:14px 20px;background:rgba(255,255,255,.045);border:1px solid rgba(255,255,255,.09);
  display:flex;flex-direction:column;gap:6px}
.stat span{font-size:10px;letter-spacing:.26em;color:rgba(233,226,255,.45)}
.stat b{font-family:'Orbitron',sans-serif;font-weight:800;font-size:22px;color:var(--or2);letter-spacing:.04em}

#fatal{position:fixed;inset:0;z-index:9;display:none;align-items:center;justify-content:center;background:#06040c}
#fatal.on{display:flex}
#fatal .\boxed{max-width:520px;padding:36px 40px;border:1px solid rgba(255,120,60,.3);background:rgba(255,120,60,.05);text-align:center}
#fatal h2{font-family:'Orbitron',sans-serif;font-size:20px;letter-spacing:.24em;color:var(--or2);margin-bottom:14px}
#fatal p{font-size:14px;line-height:1.7;color:rgba(233,226,255,.6);letter-spacing:.06em}

#hint{position:absolute;left:50%;bottom:96px;transform:translateX(-50%);font-size:11px;letter-spacing:.34em;
  color:rgba(233,226,255,.4);transition:opacity .6s;opacity:0}
#hint.on{opacity:1}

@media (max-width:760px){
  #scoreBox{top:16px;left:16px}#scoreBox .val{font-size:30px}#mult{font-size:20px}
  #topRight{top:16px;right:16px;gap:14px}
  #bars{left:16px;bottom:18px;width:200px}
  #speedo{right:12px;bottom:12px;width:158px;height:104px}
  #spdNum{font-size:28px;bottom:22px}#spdUnit{bottom:6px;font-size:9px}#boostTag{bottom:88px;font-size:9px}
}
</style>
</head>
<body>
<canvas id="view"></canvas>
<div id="vig"></div>

<div id="hud">
  <div id="scoreBox">
    <div class="lbl">Score</div>
    <div class="val tnum" id="score">0</div>
    <div id="mult">×1.0</div>
  </div>

  <div id="topRight">
    <div class="cell"><div class="lbl">Distance</div><div class="v tnum" id="dist">0 m</div></div>
    <div class="cell"><div class="lbl">Best</div><div class="v tnum" id="best">0</div></div>
    <button id="snd" title="toggle sound (M)">SND</button>
  </div>

  <div id="bars">
    <div class="barrow"><span class="bl">Energy</span><div class="bar" id="eBar"><i id="eFill" style="width:100%"></i></div></div>
    <div class="barrow"><span class="bl">Turbo</span><div class="bar t"><i id="tFill" style="width:0%"></i></div></div>
  </div>

  <div id="speedo">
    <svg viewBox="0 0 214 132">
      <defs>
        <linearGradient id="gs" x1="0" y1="1" x2="1" y2="0">
          <stop offset="0" stop-color="#49b6ff"/><stop offset=".55" stop-color="#7ef9ff"/>
          <stop offset="1" stop-color="#ff7b2e"/>
        </linearGradient>
      </defs>
      <path d="M 22 112 A 85 85 0 0 1 192 112" fill="none" stroke="rgba(255,255,255,.1)" stroke-width="4" stroke-linecap="round"/>
      <path id="spdArc" d="M 22 112 A 85 85 0 0 1 192 112" fill="none" stroke="url(#gs)" stroke-width="4"
            stroke-linecap="round" stroke-dasharray="267" stroke-dashoffset="267"/>
      <g id="ticks" stroke="rgba(255,255,255,.28)" stroke-width="1.4"></g>
    </svg>
    <div id="boostTag">TURBO</div>
    <div id="spdNum" class="tnum">0</div>
    <div id="spdUnit">KM / H</div>
  </div>

  <div id="pops"></div>
  <div id="alert"></div>
  <div id="hit"></div>
  <div id="hint"></div>
</div>

<div id="ov" class="show" data-mode="menu">
  <div class="panel p-menu">
    <div class="kicker">Procedural Sunset Run</div>
    <h1>HELIOS<span>CANYON</span></h1>
    <p class="tag">Skim the rock. Steal the light. Outrun the dark.</p>
    <button class="btn" id="startBtn">Begin Run</button>
    <div class="hints">
      <div><b>MOUSE / ← → ↑ ↓</b> bank &amp; pitch</div>
      <div><b>SPACE</b> turbo</div>
      <div><b>P</b> pause</div>
      <div><b>M</b> sound</div>
    </div>
    <div class="bestline">BEST <span id="menuBest">0</span></div>
  </div>

  <div class="panel p-over">
    <div class="kicker red">Run Terminated</div>
    <div class="final"><span class="lbl">Final Score</span><span class="num tnum" id="finalScore">0</span></div>
    <div class="stats">
      <div class="stat"><span>MAX SPEED</span><b class="tnum" id="finalSpeed">0</b></div>
      <div class="stat"><span>DISTANCE</span><b class="tnum" id="finalDist">0</b></div>
      <div class="stat"><span>NEAR MISSES</span><b class="tnum" id="finalNM">0</b></div>
    </div>
    <button class="btn" id="retryBtn">Restart</button>
    <div class="bestline">BEST <span id="overBest">0</span></div>
  </div>

  <div class="panel p-pause">
    <div class="kicker">System Hold</div>
    <h1>PAUSED</h1>
    <button class="btn ghost" id="resumeBtn">Resume</button>
    <button class="btn ghost" id="quitBtn" style="margin-top:14px">Abandon Run</button>
  </div>
</div>

<div id="fatal"><div class="box">
  <h2>RENDERER OFFLINE</h2>
  <p>The Three.js library could not be fetched from the CDN. This build streams all geometry
     through Three.js — connect to the network and reload to enter the canyon.</p>
</div></div>

<script>
window.__THREE_SRC = [
  "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js",
  "https://unpkg.com/three@0.128.0/build/three.min.js",
  "https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js"
];
</script>
<script>
"use strict";
/* ============================================================================
   HELIOS CANYON — high-speed procedural canyon racer
   Single file. Three.js (CDN) + Web Audio + custom post-processing chain.
   ========================================================================== */

/* ------------------------------ CONSTANTS ------------------------------- */
const CH_LEN = 216;              // chunk length (world units)
const CH_SEG = 18;               // longitudinal samples per chunk
const CH_STEP = CH_LEN / CH_SEG; // 12
const CH_RING = 5;               // vertical colour strata per wall
const CH_SHELF = 17;             // wall rim thickness
const AHEAD = 6, BEHIND = 2;     // chunk residency
const P_R = 2.05;                // craft collision radius
const NEAR_WALL = 7.0;           // near-miss distance for canyon walls
const NEAR_OBS = 5.2;            // near-miss distance for monoliths
const ORB_R = 4.6;               // orb pickup radius

const COL = {
  band: [[0.125,0.088,0.170],[0.215,0.135,0.230],[0.335,0.198,0.262],
         [0.475,0.300,0.282],[0.615,0.438,0.340]],
  shelf:[0.780,0.598,0.440],
  floorA:[0.098,0.068,0.140], floorB:[0.168,0.110,0.205],
  obsSide:[0.255,0.245,0.360], obsSide2:[0.345,0.325,0.455],
  obsTop:[0.740,0.575,0.430], obsEnd:[0.300,0.282,0.405], obsBot:[0.062,0.050,0.100],
  rim:[1.0,0.56,0.20],
  orb:[1.0,0.72,0.36], orbTurbo:[0.42,0.95,1.0]
};

/* -------------------------------- UTILS --------------------------------- */
const clamp=(v,a,b)=>v<a?a:(v>b?b:v);
const lerp=(a,b,t)=>a+(b-a)*t;
const sm=(c,t,r,dt)=>lerp(c,t,1-Math.exp(-r*dt));
function mulberry32(a){return function(){a|=0;a=a+0x6D2B79F5|0;let t=Math.imul(a^a>>>15,1|a);
  t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296;};}
function hashInt(n){n=(n^61)^(n>>>16);n=n+(n<<3);n^=n>>>4;n=Math.imul(n,0x27d4eb2d);n^=n>>>15;return n>>>0;}
const fmt=n=>Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g,",");
const $=id=>document.getElementById(id);

function loadThree(i){
  if(window.THREE) return Promise.resolve();
  if(i>=window.__THREE_SRC.length) return Promise.reject(new Error("cdn"));
  return new Promise((res,rej)=>{
    const s=document.createElement('script');
    s.src=window.__THREE_SRC[i];
    s.onload=()=>{window.THREE?res():rej(new Error("bad"));};
    s.onerror=()=>rej(new Error("fail"));
    document.head.appendChild(s);
  }).catch(()=>loadThree(i+1));
}

/* --------------------------------- AUDIO -------------------------------- */
const AU = {
  ctx:null, muted:false, ready:false,
  init(){
    if(this.ctx){ if(this.ctx.state==='suspended') this.ctx.resume(); return; }
    const AC = window.AudioContext||window.webkitAudioContext; if(!AC) return;
    try{
      const ctx=this.ctx=new AC();
      const master=this.master=ctx.createGain(); master.gain.value=0.5; master.connect(ctx.destination);

      const len=Math.floor(ctx.sampleRate*2), buf=ctx.createBuffer(1,len,ctx.sampleRate);
      const dat=buf.getChannelData(0);
      for(let i=0;i<len;i++) dat[i]=Math.random()*2-1;
      this.nbuf=buf;

      /* engine: saw + sub through resonant lowpass */
      const engG=this.engG=ctx.createGain(); engG.gain.value=0.0;
      const lp=this.engLP=ctx.createBiquadFilter(); lp.type='lowpass'; lp.frequency.value=420; lp.Q.value=5;
      const o1=this.o1=ctx.createOscillator(); o1.type='sawtooth'; o1.frequency.value=58;
      const o2=this.o2=ctx.createOscillator(); o2.type='sine'; o2.frequency.value=29;
      const o2g=ctx.createGain(); o2g.gain.value=0.7;
      o1.connect(lp); o2.connect(o2g); o2g.connect(lp); lp.connect(engG); engG.connect(master);
      o1.start(); o2.start();

      /* wind: looped noise through bandpass */
      const ns=this.wind=ctx.createBufferSource(); ns.buffer=buf; ns.loop=true;
      const bp=this.windBP=ctx.createBiquadFilter(); bp.type='bandpass'; bp.frequency.value=760; bp.Q.value=0.6;
      const wg=this.windG=ctx.createGain(); wg.gain.value=0;
      ns.connect(bp); bp.connect(wg); wg.connect(master); ns.start();

      this.ready=true;
    }catch(e){ this.ready=false; }
  },
  setMuted(m){ this.muted=m; if(this.master) this.master.gain.value = m?0:0.5; },
  update(sn,boost,active){
    if(!this.ready) return; const t=this.ctx.currentTime, T=(p,v,c)=>p.setTargetAtTime(v,t,c||0.09);
    if(!active){ T(this.engG.gain,0,0.2); T(this.windG.gain,0,0.2); return; }
    T(this.o1.frequency, 46+sn*96+(boost?34:0),0.08);
    T(this.o2.frequency, 23+sn*48+(boost?17:0),0.08);
    T(this.engLP.frequency, 320+sn*1500+(boost?900:0),0.1);
    T(this.engG.gain, 0.10+sn*0.13+(boost?0.05:0),0.12);
    T(this.windG.gain, 0.02+sn*0.17+(boost?0.08:0),0.15);
    T(this.windBP.frequency, 500+sn*1400,0.15);
  },
  env(g,t,a,peak,dur){ g.gain.setValueAtTime(0.0001,t); g.gain.exponentialRampToValueAtTime(peak,t+a);
    g.gain.exponentialRampToValueAtTime(0.0001,t+dur); },
  ping(kind){
    if(!this.ready||this.muted) return; const ctx=this.ctx,t=ctx.currentTime;
    const base = kind==='turbo' ? 660 : 880;
    const g=ctx.createGain(); g.connect(this.master); this.env(g,t,0.012,0.24,0.5);
    const o=ctx.createOscillator(); o.type='sine'; o.frequency.setValueAtTime(base,t);
    o.frequency.exponentialRampToValueAtTime(base*2.0,t+0.16);
    const o2=ctx.createOscillator(); o2.type='triangle'; o2.frequency.setValueAtTime(base*1.5,t);
    const g2=ctx.createGain(); g2.gain.value=0.35; o2.connect(g2); g2.connect(g);
    o.connect(g); o.start(t); o2.start(t); o.stop(t+0.55); o2.stop(t+0.55);
  },
  whoosh(pitch){
    if(!this.ready||this.muted) return; const ctx=this.ctx,t=ctx.currentTime;
    const s=ctx.createBufferSource(); s.buffer=this.nbuf;
    const bp=ctx.createBiquadFilter(); bp.type='bandpass'; bp.Q.value=1.5;
    const p=pitch||1;
    bp.frequency.setValueAtTime(380*p,t);
    bp.frequency.exponentialRampToValueAtTime(3200*p,t+0.16);
    bp.frequency.exponentialRampToValueAtTime(620*p,t+0.44);
    const g=ctx.createGain(); this.env(g,t,0.05,0.30,0.46);
    s.connect(bp); bp.connect(g); g.connect(this.master); s.start(t); s.stop(t+0.5);
  },
  thud(){
    if(!this.ready||this.muted) return; const ctx=this.ctx,t=ctx.currentTime;
    const s=ctx.createBufferSource(); s.buffer=this.nbuf;
    const lp=ctx.createBiquadFilter(); lp.type='lowpass'; lp.frequency.setValueAtTime(900,t);
    lp.frequency.exponentialRampToValueAtTime(120,t+0.35);
    const g=ctx.createGain(); this.env(g,t,0.008,0.5,0.42);
    s.connect(lp); lp.connect(g); g.connect(this.master); s.start(t); s.stop(t+0.45);
    const o=ctx.createOscillator(); o.type='sine';
    o.frequency.setValueAtTime(120,t); o.frequency.exponentialRampToValueAtTime(34,t+0.3);
    const g2=ctx.createGain(); this.env(g2,t,0.006,0.55,0.36);
    o.connect(g2); g2.connect(this.master); o.start(t); o.stop(t+0.4);
  },
  sweep(up){
    if(!this.ready||this.muted) return; const ctx=this.ctx,t=ctx.currentTime;
    const o=ctx.createOscillator(); o.type='sawtooth';
    const f=up?180:900; o.frequency.setValueAtTime(f,t);
    o.frequency.exponentialRampToValueAtTime(up?980:150,t+0.55);
    const lp=ctx.createBiquadFilter(); lp.type='lowpass'; lp.frequency.value=2400;
    const g=ctx.createGain(); this.env(g,t,0.03,0.26,0.6);
    o.connect(lp); lp.connect(g); g.connect(this.master); o.start(t); o.stop(t+0.65);
  }
};

/* --------------------------------- INPUT -------------------------------- */
const IN = { bank:0, pitch:0, boost:false, keys:Object.create(null),
             mouse:{x:0,y:0,active:false}, touch:null, anyKey:false };

function initInput(){
  const kd=e=>{
    const k=e.key.toLowerCase();
    if(['arrowleft','arrowright','arrowup','arrowdown',' ','spacebar'].includes(k)) e.preventDefault();
    IN.keys[k]=true; IN.anyKey=true; AU.init();
    if(k==='m'){ AU.setMuted(!AU.muted); $('snd').classList.toggle('off',AU.muted); }
    if(k==='p'||k==='escape'){ togglePause(); }
    if(k===' '||k==='shift') IN.boost=true;
  };
  const ku=e=>{ const k=e.key.toLowerCase(); IN.keys[k]=false;
    if(k===' '||k==='shift') IN.boost=false; };
  window.addEventListener('keydown',kd,{passive:false});
  window.addEventListener('keyup',ku);
  window.addEventListener('blur',()=>{ IN.keys=Object.create(null); IN.boost=false; });

  const cv=$('view');
  cv.addEventListener('mousemove',e=>{
    IN.mouse.x=(e.clientX/window.innerWidth)*2-1;
    IN.mouse.y=(e.clientY/window.innerHeight)*2-1;
    IN.mouse.active=true; AU.init();
  });
  cv.addEventListener('mouseleave',()=>{ IN.mouse.active=false; });
  const tl=e=>{ const t=e.touches[0];
    if(t) IN.touch={x:(t.clientX/window.innerWidth)*2-1, y:(t.clientY/window.innerHeight)*2-1};
    IN.boost=e.touches.length>=2;
    AU.init(); e.preventDefault(); };
  const te=e=>{ if(!e.touches.length){ IN.touch=null; IN.boost=false; }
    else { const t=e.touches[0];
      IN.touch={x:(t.clientX/window.innerWidth)*2-1, y:(t.clientY/window.innerHeight)*2-1};
      IN.boost=e.touches.length>=2; } };
  window.addEventListener('touchstart',tl,{passive:false});
  window.addEventListener('touchmove',tl,{passive:false});
  window.addEventListener('touchend',te);
  window.addEventListener('mousedown',()=>AU.init());
}

function readInput(){
  let b=0,p=0;
  if(IN.keys['arrowleft']||IN.keys['a']) b-=1;
  if(IN.keys['arrowright']||IN.keys['d']) b+=1;
  if(IN.keys['arrowup']||IN.keys['w']) p+=1;
  if(IN.keys['arrowdown']||IN.keys['s']) p-=1;
  let src=b, srcP=p;
  if(IN.touch){ src=IN.touch.x; srcP=-IN.touch.y; }
  else{
    if(Math.abs(IN.mouse.x)>Math.abs(b)) src=IN.mouse.x;
    if(Math.abs(-IN.mouse.y)>Math.abs(p)) srcP=-IN.mouse.y;
  }
  IN.bank=clamp(src,-1,1);
  IN.pitch=clamp(srcP,-1,1);
  IN.boost=!!(IN.boost||IN.keys[' ']||IN.keys['shift']);
}

/* ---------------------------------- HUD --------------------------------- */
const HUD = {
  score:null, mult:null, eFill:null, tFill:null, eBar:null, spdNum:null,
  arc:null, dist:null, best:null, pops:null, alert:null, hit:null, boostTag:null,
  lastAlert:0, flashT:0,
  init(){
    this.score=$('score'); this.mult=$('mult'); this.eFill=$('eFill'); this.tFill=$('tFill');
    this.eBar=$('eBar'); this.spdNum=$('spdNum'); this.arc=$('spdArc'); this.dist=$('dist');
    this.best=$('best'); this.pops=$('pops'); this.alert=$('alert'); this.hit=$('hit');
    this.boostTag=$('boostTag');
    // speedo ticks
    const g=$('ticks'); let s='';
    for(let i=0;i<=10;i++){
      const a=Math.PI-(i/10)*Math.PI;
      const r1=i%5===0?66:72, r2=79;
      const cx=107, cy=112;
      s+=`<line x1="${(cx+Math.cos(a)*r1).toFixed(1)}" y1="${(cy-Math.sin(a)*r1).toFixed(1)}"
           x2="${(cx+Math.cos(a)*r2).toFixed(1)}" y2="${(cy-Math.sin(a)*r2).toFixed(1)}"
           opacity="${i%5===0?.85:.4}"/>`;
    }
    g.innerHTML=s;
    this.arcLen=this.arc.getTotalLength?this.arc.getTotalLength():267;
    this.arc.style.strokeDasharray=this.arcLen;
  },
  update(){
    const S=window.__S, P=window.__P;
    this.score.textContent=fmt(S.score);
    const m='×'+S.mult.toFixed(1);
    if(this.mult.textContent!==m) this.mult.textContent=m;
    this.mult.classList.toggle('hot',S.mult>=3);
    this.eFill.style.width=clamp(S.energy,0,100)+'%';
    this.eBar.classList.toggle('low',S.energy<26);
    this.tFill.style.width=clamp(S.turbo,0,100)+'%';
    const kmh=Math.round(S.speed*3);
    this.spdNum.textContent=kmh;
    const f=clamp(S.speed/Math.max(1,S.vMax),0,1);
    this.arc.style.strokeDashoffset=this.arcLen*(1-f);
    this.dist.textContent = P.z>=1000 ? (P.z/1000).toFixed(2)+' km' : Math.round(P.z)+' m';
    this.best.textContent = fmt(Math.max(S.best||0,S.score));
    this.boostTag.classList.toggle('on',S.boosting);
    if(S.flashHit>0){ this.hit.classList.add('on'); } else this.hit.classList.remove('on');
  },
  pulseMult(){
    const el=this.mult; el.classList.remove('pulse'); void el.offsetWidth; el.classList.add('pulse');
  },
  pop(txt,x,y,cls){
    const d=document.createElement('div');
    d.className='pop'+(cls?' '+cls:''); d.textContent=txt;
    d.style.left=x+'px'; d.style.top=y+'px';
    this.pops.appendChild(d);
    setTimeout(()=>{ d.remove(); },980);
  },
  say(txt,kind,hold){
    const a=this.alert; a.textContent=txt; a.className='on'+(kind?' '+kind:'');
    clearTimeout(this._at);
    this._at=setTimeout(()=>{ a.className=''; }, hold||1400);
  }
};
</script>
<script>
"use strict";
/* ============================ PROCEDURAL WORLD =========================== */

let SEED = 1337;   // per-run seed: shifts terrain phases + obstacle layout

function dOf(z){ return clamp(z/17000,0,1); }          // difficulty 0..1

/* --- analytic terrain fields (pure functions of z, shared by all chunks) --- */
function cX(z){ const s=SEED*0.017;
  return Math.sin(z*0.0040+s)*40 + Math.sin(z*0.0107+1.7+s*1.3)*16 + Math.sin(z*0.0019+4.1+s*0.7)*26; }
function halfW(z){ const d=dOf(z);
  return (48-17*d) + Math.sin(z*0.0061+0.6+SEED*0.011)*8 + Math.sin(z*0.0167+2.2)*4; }
function wallH(z){ const d=dOf(z);
  return clamp((118 + 40*Math.sin(z*0.0034+2.0+SEED*0.009) + 22*Math.sin(z*0.0091+0.4)) *
               (0.95+0.22*d), 92, 190); }
function ceilY(z){ return Math.min(wallH(z)*0.62, 76); }
function corrX(z){ const w=halfW(z);
  let x = cX(z) + Math.sin(z*0.0073+1.1+SEED*0.02)*w*0.45 + Math.sin(z*0.0031+2.6)*w*0.25;
  return clamp(x, cX(z)-w+9, cX(z)+w-9); }
function corrY(z){ const c=ceilY(z);
  return clamp(21 + Math.sin(z*0.0058+0.4+SEED*0.013)*12 + Math.sin(z*0.0131+2.0)*6, 8, c-7); }

/* Linear-interpolated evaluation on the exact sample grid used by the meshes,
   so collision matches the rendered surface to the fraction of a unit. */
function terrainAt(z){
  let i=Math.floor(z/CH_STEP); if(i<0) i=0;
  const za=i*CH_STEP, zb=za+CH_STEP;
  let t=(z-za)/CH_STEP; t=clamp(t,0,1);
  const ax=cX(za), bx=cX(zb), ah=halfW(za), bh=halfW(zb);
  const ha=wallH(za), hb=wallH(zb);
  return { x:lerp(ax,bx,t), hw:lerp(ah,bh,t), h:lerp(ha,hb,t),
           ceil:Math.min(lerp(ha,hb,t)*0.62,76) };
}

/* ------------------------------ geometry helpers ------------------------- */
function triPush(P,C,a,b,c,col){
  P.push(a[0],a[1],a[2],b[0],b[1],b[2],c[0],c[1],c[2]);
  for(let i=0;i<3;i++) C.push(col[0],col[1],col[2]);
}
function quadPush(P,C,a,b,c,d,col){ triPush(P,C,a,b,c,col); triPush(P,C,a,c,d,col); }
function jit(col,k,r){ const m=1+(r()-0.5)*k; return [clamp(col[0]*m,0,1),clamp(col[1]*m,0,1),clamp(col[2]*m,0,1)]; }

function pushBox(P,C,x0,x1,y0,y1,z0,z1,cs,ct,ce,cb){
  const S=cs,T=ct,E=ce||cs,B=cb||COL.obsBot;
  quadPush(P,C,[x0,y1,z0],[x1,y1,z0],[x1,y1,z1],[x0,y1,z1],T);      // top
  quadPush(P,C,[x0,y0,z1],[x1,y0,z1],[x1,y0,z0],[x0,y0,z0],B);      // bottom
  quadPush(P,C,[x1,y0,z0],[x1,y0,z1],[x1,y1,z1],[x1,y1,z0],S);      // +x
  quadPush(P,C,[x0,y0,z1],[x0,y0,z0],[x0,y1,z0],[x0,y1,z1],S);      // -x
  quadPush(P,C,[x0,y0,z1],[x1,y0,z1],[x1,y1,z1],[x0,y1,z1],E);      // +z
  quadPush(P,C,[x1,y0,z0],[x0,y0,z0],[x0,y1,z0],[x1,y1,z0],E);      // -z
}

/* is the intended flight corridor clear of this box (with padding)? */
function corridorClear(x0,x1,y0,y1,z0,z1,pad){
  const zA=Math.floor(z0/6)*6;
  for(let z=zA; z<=z1; z+=6){
    if(z<z0-6) continue;
    const cx=corrX(z), cy=corrY(z);
    if(cx>x0-pad && cx<x1+pad && cy>y0-pad && cy<y1+pad) return false;
  }
  return true;
}

/* ------------------------------ chunk builder ---------------------------- */
function buildChunkData(idx){
  const z0=idx*CH_LEN, z1=z0+CH_LEN;
  const rnd=mulberry32(hashInt(Math.imul(idx,2654435761)^SEED));
  const wP=[],wC=[],fP=[],fC=[],gP=[],gC=[];
  const obstacles=[], orbs=[];

  /* --- canyon walls + floor --- */
  for(let i=0;i<CH_SEG;i++){
    const za=z0+i*CH_STEP, zb=za+CH_STEP;
    const ta=terrainAt(za), tb=terrainAt(zb);
    const mid=(i%2===0)?COL.floorA:COL.floorB;
    quadPush(fP,fC,
      [ta.x-ta.hw,0,za],[ta.x+ta.hw,0,za],
      [tb.x+tb.hw,0,zb],[tb.x-tb.hw,0,zb], jit(mid,0.10,rnd));

    for(const s of [-1,1]){
      const ax=ta.x+s*ta.hw, bx=tb.x+s*tb.hw;
      for(let r=0;r<CH_RING;r++){
        const ya0=ta.h*r/CH_RING, ya1=ta.h*(r+1)/CH_RING;
        const yb0=tb.h*r/CH_RING, yb1=tb.h*(r+1)/CH_RING;
        quadPush(wP,wC,[ax,ya0,za],[ax,ya1,za],[bx,yb1,zb],[bx,yb0,zb],
                 jit(COL.band[r],0.20,rnd));
      }
      const axo=ax+s*CH_SHELF, bxo=bx+s*CH_SHELF;
      quadPush(wP,wC,[ax,ta.h,za],[axo,ta.h*1.05,za],[bxo,tb.h*1.05,zb],[bx,tb.h,zb],
               jit(COL.shelf,0.14,rnd));
      quadPush(wP,wC,[axo,ta.h*1.05,za],[axo+s*22,ta.h*1.05-46,za],
               [bxo+s*22,tb.h*1.05-46,zb],[bxo,tb.h*1.05,zb],[0.07,0.05,0.11]);
    }
  }

  /* --- difficulty-scaled obstacle clusters --- */
  const d=dOf(z0);
  const nCl=Math.max(1,Math.round(1+rnd()*1.6+d*2.1));
  let lastZ=-1e9;
  for(let k=0;k<nCl;k++){
    const tz=z0+26+rnd()*(CH_LEN-52);
    if(tz-lastZ<44) continue;
    lastZ=tz;
    const r=rnd(); let type='monolith';
    if(d>0.10 && r<0.20+0.22*d) type='window';
    else if(r<0.56) type='pillar';
    else if(r<0.79) type='overhead';

    const t=terrainAt(tz), cy=ceilY(tz), w=t.hw;

    if(type==='window'){                              /* TIGHT SQUEEZE */
      const hw2=30-13*d, hh=17-6*d, dp=6;
      const gxMin=t.x-w+hw2/2+1, gxMax=t.x+w-hw2/2-1;
      const gyMin=hh/2+1, gyMax=Math.max(cy-hh/2-1,gyMin+2);
      let gx=clamp(corrX(tz),gxMin,gxMax);
      let gy=clamp(corrY(tz),gyMin,gyMax);
      const zA=tz-dp, zB=tz+dp;
      const L=t.x-w-9, R=t.x+w+9, top=t.h+5;
      const push=(a,b,c,dd)=>{ pushBox(wP,wC,a,b,c,dd,zA,zB,COL.obsSide,COL.obsTop,COL.obsEnd);
                               obstacles.push({x0:a,x1:b,y0:c,y1:dd,z0:zA,z1:zB,nm:false}); };
      push(L, gx-hw2/2, 0, top);
      push(gx+hw2/2, R, 0, top);
      push(gx-hw2/2, gx+hw2/2, 0, gy-hh/2);
      push(gx-hw2/2, gx+hw2/2, gy+hh/2, top);
      /* glowing rim marks the gap without encroaching on it */
      const th=1.2;
      pushBox(gP,gC, gx-hw2/2, gx+hw2/2, gy+hh/2, gy+hh/2+th, zA-0.6, zB+0.6, COL.rim, COL.rim, COL.rim, COL.rim);
      pushBox(gP,gC, gx-hw2/2, gx+hw2/2, gy-hh/2-th, gy-hh/2, zA-0.6, zB+0.6, COL.rim, COL.rim, COL.rim, COL.rim);
      pushBox(gP,gC, gx-hw2/2-th, gx-hw2/2, gy-hh/2, gy+hh/2, zA-0.6, zB+0.6, COL.rim, COL.rim, COL.rim, COL.rim);
      pushBox(gP,gC, gx+hw2/2, gx+hw2/2+th, gy-hh/2, gy+hh/2, zA-0.6, zB+0.6, COL.rim, COL.rim, COL.rim, COL.rim);

    } else {
      let bw, bh, bz0, bz1, bx, by0, by1;
      if(type==='pillar'){                            /* grounded monolith */
        bw=6+rnd()*11; bh=12+rnd()*40; bz0=tz-(6+rnd()*9)/2; bz1=tz+(bz0-tz)*-1;
        bz1=tz+(6+rnd()*9)/2;
        const side=rnd()<0.5?-1:1;
        bx=t.x+side*(w-bw/2-3-rnd()*9); by0=0; by1=bh;
      } else if(type==='overhead'){                   /* ceiling fang */
        bw=11+rnd()*15; const dp=7+rnd()*9; bz0=tz-dp/2; bz1=tz+dp/2;
        const side=rnd()<0.5?-1:1;
        bx=t.x+side*(w-bw/2-2);
        by1=t.h+4; by0=cy*(0.30+rnd()*0.34);
      } else {                                        /* floating monolith */
        bw=7+rnd()*13; const dp=7+rnd()*13; bz0=tz-dp/2; bz1=tz+dp/2;
        bx=t.x+(rnd()*2-1)*(w-bw/2-7);
        bh=8+rnd()*22; by0=8+rnd()*Math.max(4,(cy-26)); by1=by0+bh;
      }
      let x0=bx-bw/2, x1=bx+bw/2;
      if(!corridorClear(x0,x1,by0,by1,bz0,bz1,8)){
        const cg=corrX(tz), dir=(bx>=cg)?1:-1;
        const nx0=(dir>0? cg+8 : x0-0), nx1=(dir>0? x1 : cg-8);
        if(dir>0){ x0=Math.max(x0, cg+8); } else { x1=Math.min(x1, cg-8); }
        if(x1-x0<3){ // shift whole box away from corridor instead
          const need=(cg>x1)? (cg-8-x1) : (x0-(cg+8));
          x0+= (cg>x1? need : -need); x1+=(cg>x1? need : -need);
        }
        const inside=clamp(x0, t.x-w+2, t.x+w-2);
        x1+=inside-x0; x0=inside;
        if(!corridorClear(x0,x1,by0,by1,bz0,bz1,7)){
          by1=Math.min(by1, corrY(tz)-9);
          if(by1-by0<5) by0=Math.max(0,by1-6);
          if(!corridorClear(x0,x1,by0,by1,bz0,bz1,6)) continue;
        }
      }
      pushBox(wP,wC,x0,x1,by0,by1,bz0,bz1,COL.obsSide,COL.obsTop,COL.obsEnd);
      obstacles.push({x0,x1,y0:by0,y1:by1,z0:bz0,z1:bz1,nm:false});
    }
  }

  /* --- energy orbs along the corridor (risk = they hug the walls) --- */
  const step=40-8*d;
  for(let z=z0+8; z<z1-4; z+=step+rnd()*14){
    const t=terrainAt(z), cy=ceilY(z);
    let ox=corrX(z)+(rnd()*2-1)*4, oy=corrY(z)+(rnd()*2-1)*5, kind='energy';
    if(rnd()<0.24){ const side=rnd()<0.5?-1:1;
      ox=t.x+side*(t.hw-7.5); oy=6+rnd()*Math.max(6,cy-14); }
    if(rnd()<0.10) kind='turbo';
    ox=clamp(ox, t.x-t.hw+4, t.x+t.hw-4);
    oy=clamp(oy, 4.5, cy-4);
    orbs.push({x:ox,y:oy,z:z,kind:kind,taken:false,ph:rnd()*6.283});
  }

  return { idx,z0,z1,
    wP:new Float32Array(wP), wC:new Float32Array(wC),
    fP:new Float32Array(fP), fC:new Float32Array(fC),
    gP:new Float32Array(gP), gC:new Float32Array(gC),
    obstacles, orbs };
}

/* --------------------------- collision primitives ------------------------ */
function boxDist(px,py,pz,o){
  const dx=Math.max(o.x0-px,0,px-o.x1);
  const dy=Math.max(o.y0-py,0,py-o.y1);
  const dz=Math.max(o.z0-pz,0,pz-o.z1);
  return Math.sqrt(dx*dx+dy*dy+dz*dz);
}
function boxInside(px,py,pz,o){
  return px>o.x0&&px<o.x1&&py>o.y0&&py<o.y1&&pz>o.z0&&pz<o.z1;
}
</script>
<script>
"use strict";
/* ============================== RENDER LAYER ============================= */

let renderer, scene, camera, canvas;
let matWorld, matFloor, matGlow, matCraft;
let chunkMeshes=[];              // flat list of live meshes for disposal
const chunks=new Map();          // idx -> {data, meshes:[]}
let sky, skyMat, sunLight, sunSprite, ground, engineLight, engineSprite;
let craft, craftGlow;
let ptGeo, ptMat, orbGeo, orbMat;
let rtScene, rtPrev, rtCur, rtB1, rtB2, fsScene, fsCam, fsQuad;
let matBright, matBlur, matGrade, matScreen;
let sceneW=1, sceneH=1, bloomW=1, bloomH=1;
let QSCALE=1;

/* ------------------------------ state ----------------------------------- */
const S = {
  mode:'menu', prevMode:'menu', time:0, seedTime:0,
  score:0, mult:1, nmTimer:0, nmCount:0,
  energy:100, turbo:0, boosting:false, boostHeld:false,
  speed:44, vMax:100, dist:0, maxSpeed:0,
  bankIn:0, pitchIn:0, bankVis:0, pitchVis:0, yaw:0,
  shake:0, flashHit:0, invuln:0, turb:0, flash:1, flashCol:[0,0,0],
  auto:true, overT:0, armed:[true,true], fbHold:0, best:0, muted:false
};
const P = { x:0, y:20, z:0, vx:0, vy:0, r:P_R };
window.__S=S; window.__P=P;

/* ------------------------------ textures -------------------------------- */
function glowTexture(){
  const c=document.createElement('canvas'); c.width=c.height=64;
  const g=c.getContext('2d');
  const rg=g.createRadialGradient(32,32,0,32,32,32);
  rg.addColorStop(0,'rgba(255,255,255,1)');
  rg.addColorStop(0.16,'rgba(255,240,220,0.92)');
  rg.addColorStop(0.42,'rgba(255,170,90,0.42)');
  rg.addColorStop(1,'rgba(255,120,40,0)');
  g.fillStyle=rg; g.fillRect(0,0,64,64);
  const t=new THREE.CanvasTexture(c); t.needsUpdate=true; return t;
}

/* ------------------------------ the craft ------------------------------- */
function extrudeY(P,C,pts,y0,y1,col,capT,capB){
  const n=pts.length;
  for(let i=0;i<n;i++){ const j=(i+1)%n;
    quadPush(P,C,[pts[i][0],y1,pts[i][1]],[pts[j][0],y1,pts[j][1]],
                 [pts[j][0],y0,pts[j][1]],[pts[i][0],y0,pts[i][1]],col); }
  for(let i=1;i<n-1;i++){
    triPush(P,C,[pts[0][0],y1,pts[0][1]],[pts[i][0],y1,pts[i][1]],[pts[i+1][0],y1,pts[i+1][1]],capT||col);
    triPush(P,C,[pts[0][0],y0,pts[0][1]],[pts[i+1][0],y0,pts[i+1][1]],[pts[i][0],y0,pts[i][1]],capB||col);
  }
}
function extrudeX(P,C,pts,x0,x1,col,capT,capB){
  const n=pts.length;
  for(let i=0;i<n;i++){ const j=(i+1)%n;
    quadPush(P,C,[x1,pts[i][1],pts[i][0]],[x1,pts[j][1],pts[j][0]],
                 [x0,pts[j][1],pts[j][0]],[x0,pts[i][1],pts[i][0]],col); }
  for(let i=1;i<n-1;i++){
    triPush(P,C,[x1,pts[0][1],pts[0][0]],[x1,pts[i][1],pts[i][0]],[x1,pts[i+1][1],pts[i+1][0]],capT||col);
    triPush(P,C,[x0,pts[0][1],pts[0][0]],[x0,pts[i+1][1],pts[i+1][0]],[x0,pts[i][1],pts[i][0]],capB||col);
  }
}
function buildCraftGeometry(){
  const P=[],C=[];
  const body=[0.40,0.44,0.56], bodyD=[0.17,0.19,0.28], wing=[0.30,0.33,0.44];
  const tip=[1.0,0.50,0.15], fin=[1.0,0.44,0.14], eng=[0.12,0.11,0.19];
  const nose=[0,0.06,6.7], tail=[0,0.12,-5.0];
  const rA=[[1.18,0,1.0],[0,0.92,1.0],[-1.18,0,1.0],[0,-0.82,1.0]];
  const rB=[[0.74,0,-3.3],[0,0.56,-3.3],[-0.74,0,-3.3],[0,-0.5,-3.3]];
  for(let i=0;i<4;i++){ const j=(i+1)%4;
    triPush(P,C,nose,rA[i],rA[j], body);
    triPush(P,C,rA[i],rB[i],rB[j], body);
    triPush(P,C,rA[i],rB[j],rA[j], i===1?bodyD:body);
    triPush(P,C,rB[i],tail,rB[j], bodyD);
  }
  extrudeY(P,C,[[-0.95,2.3],[-0.95,-2.7],[-6.5,-4.0],[-5.0,0.7]],-0.15,0.15,wing, body, bodyD);
  extrudeY(P,C,[[0.95,2.3],[5.0,0.7],[6.5,-4.0],[0.95,-2.7]],-0.15,0.15,wing, body, bodyD);
  extrudeY(P,C,[[-6.6,-3.15],[-6.6,-4.05],[-4.9,-3.75],[-4.9,-3.15]],-0.17,0.17,tip, tip, tip);
  extrudeY(P,C,[[6.6,-3.15],[4.9,-3.15],[4.9,-3.75],[6.6,-4.05]],-0.17,0.17,tip, tip, tip);
  extrudeX(P,C,[[-3.3,0.55],[-4.7,0.6],[-5.0,3.1],[-3.5,2.7]],-0.14,0.14,fin, fin, fin);
  pushBox(P,C,-0.78,0.78,-0.66,0.66,-5.1,-3.6, eng, eng, eng, eng);
  pushBox(P,C,-0.5,0.5,-0.44,0.44,2.6,4.4, [0.30,0.34,0.46],[0.9,0.55,0.25],[0.30,0.34,0.46]);
  const g=new THREE.BufferGeometry();
  g.setAttribute('position',new THREE.BufferAttribute(new Float32Array(P),3));
  g.setAttribute('color',new THREE.BufferAttribute(new Float32Array(C),3));
  g.computeVertexNormals();
  return g;
}

/* ------------------------------ particle system -------------------------- */
const PN=1500, DN=520;
const pa={ px:new Float32Array(PN),py:new Float32Array(PN),pz:new Float32Array(PN),
  vx:new Float32Array(PN),vy:new Float32Array(PN),vz:new Float32Array(PN),
  life:new Float32Array(PN),max:new Float32Array(PN),bs:new Float32Array(PN),
  dr:new Float32Array(PN),dg:new Float32Array(PN),db:new Float32Array(PN),
  bsx:new Float32Array(PN),
  pos:new Float32Array(PN*3), col:new Float32Array(PN*3),
  siz:new Float32Array(PN), alp:new Float32Array(PN) };
let pHead=DN;

function spawnPart(x,y,z,vx,vy,vz,life,size,r,g,b,drag){
  const i=pHead; pHead=(pHead+1)%PN; if(pHead<DN) pHead=DN;
  pa.px[i]=x;pa.py[i]=y;pa.pz[i]=z;pa.vx[i]=vx;pa.vy[i]=vy;pa.vz[i]=vz;
  pa.life[i]=life;pa.max[i]=life;pa.bs[i]=size;pa.dr[i]=r;pa.dg[i]=g;pa.db[i]=b;
  pa.bsx[i]=drag||1;
}
function respawnDust(i,initial){
  const z = initial ? P.z-70+Math.random()*470 : P.z+160+Math.random()*280;
  const t=terrainAt(z);
  pa.px[i]=t.x+(Math.random()*2-1)*(t.hw+8);
  pa.py[i]=1+Math.random()*Math.max(6,ceilY(z)+6);
  pa.pz[i]=z;
  pa.vx[i]=0; pa.vy[i]=0; pa.vz[i]=-2-Math.random()*4;
  pa.life[i]=1e9; pa.max[i]=1e9;
  pa.bs[i]=0.16+Math.random()*0.30;
  const w=0.55+Math.random()*0.45;
  pa.dr[i]=w; pa.dg[i]=w*0.82; pa.db[i]=w*0.72;
  pa.bsx[i]=2;
}
function initParticles(){
  for(let i=0;i<DN;i++){ pa.life[i]=0; respawnDust(i,true); }
  for(let i=DN;i<PN;i++) pa.life[i]=0;
  pHead=DN;
}
function clearParticles(){
  for(let i=DN;i<PN;i++) pa.life[i]=0;
  pHead=DN;
}
function updateParticles(dt){
  for(let i=0;i<DN;i++){
    const z=pa.pz[i];
    if(z<P.z-70||z>P.z+430){ respawnDust(i,false); }
    else { pa.px[i]+=pa.vx[i]*dt; pa.py[i]+=pa.vy[i]*dt; pa.pz[i]+=pa.vz[i]*dt; }
    const d=pa.pz[i]-P.z;
    pa.alp[i]=0.55*clamp((d-7)/9,0,1)*clamp((440-d)/120,0,1);
    pa.siz[i]=pa.bs[i];
    pa.pos[i*3]=pa.px[i]; pa.pos[i*3+1]=pa.py[i]; pa.pos[i*3+2]=pa.pz[i];
    pa.col[i*3]=pa.dr[i]; pa.col[i*3+1]=pa.dg[i]; pa.col[i*3+2]=pa.db[i];
  }
  for(let i=DN;i<PN;i++){
    if(pa.life[i]<=0){ pa.alp[i]=0; continue; }
    pa.life[i]-=dt;
    if(pa.life[i]<=0){ pa.alp[i]=0; continue; }
    const k=pa.bsx[i];
    pa.vx[i]*=(1-2.4*dt*k); pa.vy[i]*=(1-2.4*dt*k); pa.vz[i]*=(1-2.4*dt*k);
    pa.vy[i]-=6*dt*k*0.35;
    pa.px[i]+=pa.vx[i]*dt; pa.py[i]+=pa.vy[i]*dt; pa.pz[i]+=pa.vz[i]*dt;
    const t=pa.life[i]/pa.max[i];
    pa.alp[i]=t*t;
    pa.siz[i]=pa.bs[i]*(0.35+0.65*t);
    pa.pos[i*3]=pa.px[i]; pa.pos[i*3+1]=pa.py[i]; pa.pos[i*3+2]=pa.pz[i];
    const f=0.35+0.65*t;
    pa.col[i*3]=pa.dr[i]*f+0.25*(1-t); pa.col[i*3+1]=pa.dg[i]*f*f; pa.col[i*3+2]=pa.db[i]*f*t;
  }
  ptGeo.attributes.position.needsUpdate=true;
  ptGeo.attributes.aColor.needsUpdate=true;
  ptGeo.attributes.aSize.needsUpdate=true;
  ptGeo.attributes.aAlpha.needsUpdate=true;
}
function spawnSparks(x,y,z,n,r,g,b,spd){
  for(let i=0;i<n;i++){
    const a=Math.random()*6.283, b2=Math.acos(Math.random()*2-1), s=(0.4+Math.random())*(spd||26);
    spawnPart(x,y,z,Math.sin(b2)*Math.cos(a)*s, Math.cos(b2)*s*0.7, Math.sin(b2)*Math.sin(a)*s,
      0.4+Math.random()*0.5, 0.55+Math.random()*0.7, r,g,b, 1.6);
  }
}

/* ------------------------------ scene objects ---------------------------- */
function initScene(){
  scene=new THREE.Scene();
  scene.fog=new THREE.Fog(0xff9a63,140,820);

  camera=new THREE.PerspectiveCamera(70,innerWidth/innerHeight,0.5,6000);
  camera.rotation.order='YXZ';

  /* sky dome */
  skyMat=new THREE.ShaderMaterial({
    side:THREE.BackSide, depthWrite:false, fog:false,
    uniforms:{ uTop:{value:new THREE.Color(0x2b1b52)}, uMid:{value:new THREE.Color(0x8a3f7a)},
               uBot:{value:new THREE.Color(0xff8a4a)}, uGlow:{value:new THREE.Color(0xffd9a0)},
               uSun:{value:new THREE.Vector3(-0.78,0.30,0.55)} },
    vertexShader:`varying vec3 vD; void main(){ vD=normalize(position);
      gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0); }`,
    fragmentShader:`varying vec3 vD; uniform vec3 uTop,uMid,uBot,uGlow,uSun;
      void main(){ float h=vD.y;
        vec3 c=mix(uBot,uMid,smoothstep(-0.12,0.16,h));
        c=mix(c,uTop,smoothstep(0.08,0.62,h));
        float s=max(dot(normalize(vD),normalize(uSun)),0.0);
        c+=uGlow*pow(s,7.0)*0.60;
        c+=uGlow*pow(s,150.0)*1.35;
        gl_FragColor=vec4(c,1.0); }`
  });
  sky=new THREE.Mesh(new THREE.SphereGeometry(3200,24,16), skyMat);
  sky.renderOrder=-1000; sky.frustumCulled=false;
  scene.add(sky);

  /* lights */
  sunLight=new THREE.DirectionalLight(0xffc490,1.35);
  sunLight.castShadow=true;
  const sm2=sunLight.shadow;
  sm2.mapSize.set(2048,2048);
  sm2.camera.left=-135; sm2.camera.right=135; sm2.camera.top=150; sm2.camera.bottom=-150;
  sm2.camera.near=1; sm2.camera.far=760;
  sm2.bias=-0.0008; sm2.normalBias=0.9; sm2.radius=1.6;
  sm2.camera.updateProjectionMatrix();
  scene.add(sunLight); scene.add(sunLight.target);

  const hemi=new THREE.HemisphereLight(0xa070ff,0x59291c,0.70); scene.add(hemi);
  const amb=new THREE.AmbientLight(0x43306a,0.52); scene.add(amb);
  window.__hemi=hemi; window.__amb=amb;

  /* distant ground so no gaps show through */
  const gg=new THREE.PlaneGeometry(7000,7000);
  ground=new THREE.Mesh(gg,new THREE.MeshLambertMaterial({color:0x120c20,fog:true}));
  ground.rotation.x=-Math.PI/2; ground.position.y=-1.4; ground.receiveShadow=true;
  scene.add(ground);

  /* materials */
  matWorld=new THREE.MeshLambertMaterial({vertexColors:true,side:THREE.DoubleSide,fog:true});
  matWorld.shadowSide=THREE.DoubleSide;
  matFloor=new THREE.MeshLambertMaterial({vertexColors:true,side:THREE.DoubleSide,fog:true});
  matGlow=new THREE.MeshBasicMaterial({vertexColors:true,side:THREE.DoubleSide,fog:true});
  matCraft=new THREE.MeshLambertMaterial({vertexColors:true,side:THREE.DoubleSide,fog:true});

  /* craft */
  craft=new THREE.Mesh(buildCraftGeometry(),matCraft);
  craft.castShadow=true; craft.rotation.order='YXZ';
  scene.add(craft);
  const tex=glowTexture();
  engineSprite=new THREE.Sprite(new THREE.SpriteMaterial({map:tex,color:0xffa040,
    blending:THREE.AdditiveBlending,depthWrite:false,transparent:true,opacity:0.95}));
  engineSprite.position.set(0,0,-5.4); engineSprite.scale.set(6,6,1);
  craft.add(engineSprite);
  engineLight=new THREE.PointLight(0xff8a3c,1.6,70,2);
  engineLight.position.set(0,0,-5); craft.add(engineLight);

  sunSprite=new THREE.Sprite(new THREE.SpriteMaterial({map:tex,color:0xffe6bc,
    blending:THREE.AdditiveBlending,depthWrite:false,transparent:true,opacity:0.95,fog:false}));
  sunSprite.scale.set(340,340,1); sunSprite.renderOrder=-900;
  scene.add(sunSprite);

  /* particles */
  ptGeo=new THREE.BufferGeometry();
  ptGeo.setAttribute('position',new THREE.BufferAttribute(pa.pos,3));
  ptGeo.setAttribute('aColor',new THREE.BufferAttribute(pa.col,3));
  ptGeo.setAttribute('aSize',new THREE.BufferAttribute(pa.siz,1));
  ptGeo.setAttribute('aAlpha',new THREE.BufferAttribute(pa.alp,1));
  ptMat=new THREE.ShaderMaterial({
    transparent:true, depthWrite:false, depthTest:true, blending:THREE.AdditiveBlending,
    uniforms:{ uMap:{value:tex}, uScale:{value:600}, uFogNear:{value:140}, uFogFar:{value:820},
               uFogCol:{value:new THREE.Color(0xff9a63)} },
    vertexShader:`attribute float aSize; attribute vec3 aColor; attribute float aAlpha;
      uniform float uScale,uFogNear,uFogFar; varying vec3 vC; varying float vA,vF;
      void main(){ vC=aColor; vA=aAlpha;
        vec4 mv=modelViewMatrix*vec4(position,1.0); float d=max(-mv.z,1.0);
        vF=smoothstep(uFogNear,uFogFar,d);
        gl_PointSize=clamp(aSize*uScale/d,1.0,140.0);
        gl_Position=projectionMatrix*mv; }`,
    fragmentShader:`uniform sampler2D uMap; uniform vec3 uFogCol; varying vec3 vC; varying float vA,vF;
      void main(){ float m=texture2D(uMap,gl_PointCoord).a;
        float a=vA*m*(1.0-vF); if(a<0.004) discard;
        gl_FragColor=vec4(vC,a); }`
  });
  const pts=new THREE.Points(ptGeo,ptMat); pts.frustumCulled=false; scene.add(pts);

  /* orbs */
  const ON=320;
  orbGeo=new THREE.BufferGeometry();
  orbGeo.setAttribute('position',new THREE.BufferAttribute(new Float32Array(ON*3),3));
  orbGeo.setAttribute('aColor',new THREE.BufferAttribute(new Float32Array(ON*3),3));
  orbGeo.setAttribute('aSize',new THREE.BufferAttribute(new Float32Array(ON),1));
  orbGeo.setAttribute('aAlpha',new THREE.BufferAttribute(new Float32Array(ON),1));
  orbGeo.setDrawRange(0,0);
  orbMat=ptMat.clone(); orbMat.uniforms=THREE.UniformsUtils.clone(ptMat.uniforms);
  orbMat.uniforms.uMap.value=tex;
  orbMat.uniforms.uScale.value=560;
  const opts=new THREE.Points(orbGeo,orbMat); opts.frustumCulled=false; scene.add(opts);

  initParticles();
}

/* ------------------------------ chunk management ------------------------- */
function makeMesh(pos,col,mat,cast,recv){
  if(!pos.length) return null;
  const g=new THREE.BufferGeometry();
  g.setAttribute('position',new THREE.BufferAttribute(pos,3));
  g.setAttribute('color',new THREE.BufferAttribute(col,3));
  g.computeVertexNormals();
  const m=new THREE.Mesh(g,mat);
  m.castShadow=cast; m.receiveShadow=recv;
  scene.add(m); chunkMeshes.push(m);
  return m;
}
function spawnChunk(i){
  if(chunks.has(i)) return;
  const d=buildChunkData(i);
  const meshes=[ makeMesh(d.wP,d.wC,matWorld,true,false),
                 makeMesh(d.fP,d.fC,matFloor,false,true),
                 makeMesh(d.gP,d.gC,matGlow,false,false) ].filter(Boolean);
  chunks.set(i,{data:d,meshes});
}
function dropChunk(i){
  const c=chunks.get(i); if(!c) return;
  c.meshes.forEach(m=>{ scene.remove(m); const k=chunkMeshes.indexOf(m); if(k>=0) chunkMeshes.splice(k,1); m.geometry.dispose(); });
  chunks.delete(i);
}
function resetChunks(){
  [...chunks.keys()].forEach(dropChunk);
  for(let i=0;i<=AHEAD;i++) spawnChunk(i);
}
function streamChunks(budget){
  const ci=Math.floor(P.z/CH_LEN);
  for(let i=Math.max(0,ci-BEHIND);i<=ci+AHEAD;i++){
    if(!chunks.has(i)){ if(budget<=0) return; spawnChunk(i); budget--; }
  }
  for(const k of chunks.keys()) if(k<ci-BEHIND-1||k>ci+AHEAD+1) dropChunk(k);
}

/* --------------------------- post-processing ---------------------------- */
const VS_PASS=`varying vec2 vUv; void main(){ vUv=uv; gl_Position=vec4(position.xy,0.0,1.0); }`;
function initPost(){
  matBright=new THREE.ShaderMaterial({uniforms:{tDiffuse:{value:null},uDir:{value:new THREE.Vector2(1,0)},
    uTexel:{value:new THREE.Vector2()},uThresh:{value:0.74},uThrOn:{value:1}},
    vertexShader:VS_PASS,
    fragmentShader:`uniform sampler2D tDiffuse;uniform vec2 uDir,uTexel;uniform float uThresh,uThrOn;
      varying vec2 vUv;
      vec3 th(vec3 c){float l=max(max(c.r,c.g),c.b);float k=smoothstep(uThresh,uThresh+0.42,l);return mix(c,c*k,uThrOn);}
      void main(){vec3 s=th(texture2D(tDiffuse,vUv).rgb);float w=1.0;
        for(int i=1;i<=4;i++){float fi=float(i);vec2 o=uDir*uTexel*(fi*1.35);float d=1.0/(1.0+fi*0.85);
          s+=th(texture2D(tDiffuse,vUv+o).rgb)*d; s+=th(texture2D(tDiffuse,vUv-o).rgb)*d; w+=2.0*d;}
        gl_FragColor=vec4(s/w,1.0);}`});
  matBlur=matBright.clone(); matBlur.uniforms=THREE.UniformsUtils.clone(matBright.uniforms);
  matBlur.uniforms.uThrOn.value=0;

  matGrade=new THREE.ShaderMaterial({uniforms:{
    tScene:{value:null},tBloom:{value:null},tPrev:{value:null},
    uBloom:{value:0.85},uFeedback:{value:0.0},uRadial:{value:0.0},uCA:{value:1.0},
    uVig:{value:0.42},uGrain:{value:0.032},uTime:{value:0},uFlash:{value:0},uFlashCol:{value:new THREE.Color(0,0,0)}},
    vertexShader:VS_PASS,
    fragmentShader:`uniform sampler2D tScene,tBloom,tPrev;
      uniform float uBloom,uFeedback,uRadial,uCA,uVig,uGrain,uTime,uFlash; uniform vec3 uFlashCol;
      varying vec2 vUv;
      void main(){ vec2 uv=vUv; vec2 d=uv-0.5; float r2=dot(d,d);
        float ca=uCA*(0.0012+r2*0.0075);
        vec3 col;
        col.r=texture2D(tScene,uv-d*ca).r;
        col.g=texture2D(tScene,uv).g;
        col.b=texture2D(tScene,uv+d*ca).b;
        float amt=uRadial*smoothstep(0.004,0.22,r2);
        vec3 acc=col; float w=1.0;
        for(int i=1;i<=5;i++){ float f=float(i)*0.22;
          vec2 o=d*amt*f;
          acc+=texture2D(tScene,uv-o).rgb; acc+=texture2D(tScene,uv+o*0.4).rgb; w+=2.0; }
        col=acc/w;
        vec3 pv=texture2D(tPrev,uv).rgb;
        col=mix(col,pv,uFeedback);
        col+=texture2D(tBloom,uv).rgb*uBloom;
        col*=1.06;
        col=pow(max(col,vec3(0.0)),vec3(0.90));
        col=(col-0.5)*1.07+0.5;
        float l=dot(col,vec3(0.299,0.587,0.114));
        col=mix(vec3(l),col,1.16);
        col*=1.0-uVig*smoothstep(0.05,0.44,r2);
        col=mix(col,uFlashCol,uFlash);
        float g=fract(sin(dot(uv+vec2(uTime*1.7,uTime*0.9),vec2(12.9898,78.233)))*43758.5453);
        col+=(g-0.5)*uGrain;
        gl_FragColor=vec4(clamp(col,0.0,1.0),1.0); }`});

  matScreen=new THREE.ShaderMaterial({uniforms:{tDiffuse:{value:null}},vertexShader:VS_PASS,
    fragmentShader:`uniform sampler2D tDiffuse; varying vec2 vUv;
      void main(){ gl_FragColor=vec4(texture2D(tDiffuse,vUv).rgb,1.0); }`});

  fsScene=new THREE.Scene();
  fsCam=new THREE.OrthographicCamera(-1,1,1,-1,0,1);
  fsQuad=new THREE.Mesh(new THREE.PlaneGeometry(2,2),matScreen);
  fsQuad.frustumCulled=false; fsScene.add(fsQuad);
}
function makeRT(w,h,depth){
  return new THREE.WebGLRenderTarget(Math.max(2,w),Math.max(2,h),{
    minFilter:THREE.LinearFilter,magFilter:THREE.LinearFilter,format:THREE.RGBAFormat,
    type:THREE.UnsignedByteType,depthBuffer:!!depth,stencilBuffer:false,generateMipmaps:false});
}
function resize(){
  const w=innerWidth,h=innerHeight;
  const basePr=Math.min(window.devicePixelRatio||1,1.5);
  const pr=Math.max(0.55,basePr*QSCALE);
  renderer.setPixelRatio(pr); renderer.setSize(w,h,false);
  camera.aspect=w/h; camera.updateProjectionMatrix();
  const bw=Math.max(2,Math.floor(w*pr)), bh=Math.max(2,Math.floor(h*pr));
  const SS=basePr<1.5?1.3:1.0;
  let sw=Math.min(Math.floor(bw*SS),2200);
  let sh=Math.max(2,Math.round(sw*h/w));
  sceneW=sw; sceneH=sh;
  bloomW=Math.max(2,Math.ceil(sw/4)); bloomH=Math.max(2,Math.ceil(sh/4));
  [rtScene,rtPrev,rtCur].forEach(rt=>rt&&rt.setSize(bw,bh));
  rtScene.setSize(sw,sh);
  [rtB1,rtB2].forEach(rt=>rt&&rt.setSize(bloomW,bloomH));
}
function initRTs(){
  const w=2,h=2;
  rtScene=makeRT(w,h,true); rtPrev=makeRT(w,h,false); rtCur=makeRT(w,h,false);
  rtB1=makeRT(w,h,false); rtB2=makeRT(w,h,false);
}
function pass(mat,target){
  fsQuad.material=mat;
  renderer.setRenderTarget(target||null);
  renderer.render(fsScene,fsCam);
}
function renderFrame(){
  /* 1. scene */
  renderer.setRenderTarget(rtScene);
  renderer.render(scene,camera);
  /* 2. bloom (downsample + threshold + 4 blur passes) */
  matBright.uniforms.tDiffuse.value=rtScene.texture;
  matBright.uniforms.uDir.value.set(1,0);
  matBright.uniforms.uTexel.value.set(1/bloomW,1/bloomH);
  pass(matBright,rtB1);
  matBlur.uniforms.uDir.value.set(0,1); matBlur.uniforms.uTexel.value.set(1/bloomW,1/bloomH);
  matBlur.uniforms.tDiffuse.value=rtB1.texture; pass(matBlur,rtB2);
  matBlur.uniforms.tDiffuse.value=rtB2.texture; matBlur.uniforms.uDir.value.set(1,0); pass(matBlur,rtB1);
  matBlur.uniforms.tDiffuse.value=rtB1.texture; matBlur.uniforms.uDir.value.set(0,1); pass(matBlur,rtB2);
  /* 3. grade + motion blur feedback */
  const g=matGrade.uniforms;
  g.tScene.value=rtScene.texture; g.tBloom.value=rtB2.texture; g.tPrev.value=rtPrev.texture;
  g.uTime.value=S.time;
  g.uFeedback.value = S.fbHold>0 ? 0 : clamp(0.10+(S.speed/Math.max(60,S.vMax))*0.17+(S.boosting?0.11:0),0,0.36);
  g.uRadial.value = clamp((S.speed/Math.max(60,S.vMax))*0.030+(S.boosting?0.035:0),0,0.075);
  g.uCA.value = 1.0+(S.boosting?3.2:0)+ (S.speed/Math.max(60,S.vMax))*0.8;
  g.uFlash.value = S.flash;
  g.uFlashCol.value.setRGB(S.flashCol[0],S.flashCol[1],S.flashCol[2]);
  pass(matGrade,rtCur);
  /* 4. to screen */
  matScreen.uniforms.tDiffuse.value=rtCur.texture;
  pass(matScreen,null);
  /* swap feedback buffers */
  const t=rtPrev; rtPrev=rtCur; rtCur=t;
}
</script>
<script>
"use strict";
/* ============================== GAME LOGIC =============================== */

let camPos=null, camLook=null, _v=null, _dir=null, _c1=null, _c2=null;
const PAL={};

/* ------------------------------- helpers -------------------------------- */
function project(x,y,z){
  if(!_v||!camera) return null;
  _v.set(x,y,z); _v.project(camera);
  if(_v.z>1) return null;
  return { x:(_v.x*0.5+0.5)*innerWidth, y:(-_v.y*0.5+0.5)*innerHeight };
}
function autoPilot(){
  const look=P.z+80+S.speed*0.30;
  const tx=corrX(look), ty=corrY(look);
  return [ clamp((tx-P.x)*0.024-P.vx*0.050,-1,1),
           clamp((ty-P.y)*0.032-P.vy*0.050,-1,1) ];
}
function pushOut(o){
  const cx=clamp(P.x,o.x0,o.x1), cy=clamp(P.y,o.y0,o.y1), cz=clamp(P.z,o.z0,o.z1);
  let nx=P.x-cx, ny=P.y-cy, nz=P.z-cz;
  const l=Math.sqrt(nx*nx+ny*ny+nz*nz);
  if(l>1e-4){ P.x=cx+nx/l*P_R; P.y=cy+ny/l*P_R; P.z=cz+nz/l*P_R; return; }
  const p1=P.x-o.x0, p2=o.x1-P.x, q1=P.y-o.y0, q2=o.y1-P.y;
  const m=Math.min(p1,p2,q1,q2);
  if(m===p1) P.x=o.x0-P_R; else if(m===p2) P.x=o.x1+P_R;
  else if(m===q1) P.y=o.y0-P_R; else P.y=o.y1+P_R;
}

/* --------------------------------- hit ---------------------------------- */
function hit(amt,x,y,z,r,g,b){
  spawnSparks(x,y,z,26,r,g,b,34);
  S.shake=Math.max(S.shake,1);
  S.invuln=0.75;
  if(S.mode!=='playing'){ S.shake=Math.max(S.shake,0.45); return; }
  S.speed*=0.55; S.mult=1; S.nmTimer=3; S.energy-=amt; S.flashHit=0.65;
  AU.thud(); HUD.say('IMPACT','warn',650);
  if(S.energy<=0){ S.energy=0; gameOver(); }
}
function nearMiss(label,gap){
  S.mult=Math.min(9,S.mult+0.30);
  S.nmTimer=0; S.nmCount++;
  const pts=Math.round((130+S.speed*0.55)*S.mult);
  S.score+=pts;
  S.energy=Math.min(100,S.energy+1.6);
  S.turbo=Math.min(100,S.turbo+7);
  S.shake=Math.min(1,S.shake+0.30);
  HUD.pulseMult();
  const sp=project(P.x,P.y,P.z);
  if(sp) HUD.pop('+'+pts, sp.x+(Math.random()*70-35), sp.y);
  if(gap<2.4&&sp) HUD.pop(label, sp.x, sp.y-36, 'nm');
  AU.whoosh(1+clamp(0.6-gap*0.10,0,0.6));
}

/* ------------------------------ collision -------------------------------- */
function collide(){
  if(S.invuln>0) return;
  const t=terrainAt(P.z), rel=P.x-t.x;
  if(Math.abs(rel)+P_R>t.hw){
    const s=rel>=0?1:-1;
    P.x=t.x+s*(t.hw-P_R); P.vx=-P.vx*0.2;
    hit(21,P.x,P.y,P.z,1,0.25,0.10); return;
  }
  if(P.y<P_R+0.2){
    P.y=P_R+0.2; P.vy=Math.abs(P.vy)*0.4; S.speed*=0.985;
    hit(9,P.x,1.2,P.z,1,0.6,0.2); return;
  }
  const cy=ceilY(P.z);
  if(P.y>cy){ P.y=cy; P.vy=Math.min(P.vy,-2); S.turb=0.35; }
  const ci=Math.floor(P.z/CH_LEN);
  for(let i=ci-1;i<=ci+1;i++){
    const c=chunks.get(i); if(!c) continue;
    for(const o of c.data.obstacles){
      if(o.z1<P.z-8||o.z0>P.z+8) continue;
      if(boxDist(P.x,P.y,P.z,o)<P_R){ pushOut(o); hit(21,P.x,P.y,P.z,1,0.4,0.15); return; }
    }
  }
}
function wallScan(){
  const t=terrainAt(P.z), rel=P.x-t.x;
  const gap=t.hw-Math.abs(rel)-P_R;
  const side=rel<0?0:1;
  if(gap<NEAR_WALL){ if(S.armed[side]){ S.armed[side]=false; nearMiss(gap<3.0?'SKIM':'NEAR MISS',gap); } }
  else if(gap>NEAR_WALL*1.7) S.armed[side]=true;
}
function obsScan(){
  const ci=Math.floor(P.z/CH_LEN);
  for(let i=ci-1;i<=ci+1;i++){
    const c=chunks.get(i); if(!c) continue;
    for(const o of c.data.obstacles){
      if(o.nm||o.z1<P.z-8||o.z0>P.z+10) continue;
      const d=boxDist(P.x,P.y,P.z,o);
      if(d>0&&d<NEAR_OBS){ o.nm=true; nearMiss('NEAR MISS',d); }
    }
  }
}
function orbsUpdate(playing){
  const ci=Math.floor(P.z/CH_LEN);
  for(let i=ci-1;i<=ci+2;i++){
    const c=chunks.get(i); if(!c) continue;
    for(const o of c.data.orbs){
      if(!playing||o.taken||o.z<P.z-25) continue;
      const dx=o.x-P.x, dy=o.y-P.y, dz=o.z-P.z;
      if(dx*dx+dy*dy+dz*dz>=ORB_R*ORB_R) continue;
      o.taken=true;
      const turboKind=o.kind==='turbo';
      if(turboKind){ S.turbo=Math.min(100,S.turbo+34); S.energy=Math.min(100,S.energy+7);
        if(playing) HUD.say('TURBO CHARGE','good',800); }
      else { S.energy=Math.min(100,S.energy+8); S.turbo=Math.min(100,S.turbo+5); }
      S.score+=Math.round(70*S.mult);
      S.mult=Math.min(9,S.mult+0.10);
      spawnSparks(o.x,o.y,o.z,16,turboKind?0.4:1.0,turboKind?0.95:0.7,turboKind?1.0:0.35,30);
      AU.ping(o.kind); HUD.pulseMult();
      const sp=project(o.x,o.y,o.z);
      if(sp) HUD.pop('+'+Math.round(70*S.mult),sp.x,sp.y);
    }
  }
}
function updateOrbPoints(){
  const at=orbGeo.attributes, pos=at.position.array, col=at.aColor.array,
        siz=at.aSize.array, alp=at.aAlpha.array, N=siz.length;
  let n=0;
  const ci=Math.floor(P.z/CH_LEN);
  for(let i=ci-1;i<=ci+AHEAD;i++){
    const c=chunks.get(i); if(!c) continue;
    for(const o of c.data.orbs){
      if(o.taken||o.z<P.z-30||o.z>P.z+680||n>=N) continue;
      pos[n*3]=o.x; pos[n*3+1]=o.y; pos[n*3+2]=o.z;
      const C=o.kind==='turbo'?COL.orbTurbo:COL.orb;
      col[n*3]=C[0]; col[n*3+1]=C[1]; col[n*3+2]=C[2];
      siz[n]=4.4+Math.sin(S.time*4.2+o.ph)*0.7;
      alp[n]=0.95;
      n++;
    }
  }
  orbGeo.setDrawRange(0,n);
  at.position.needsUpdate=true; at.aColor.needsUpdate=true;
  at.aSize.needsUpdate=true; at.aAlpha.needsUpdate=true;
}

/* -------------------------------- flight --------------------------------- */
function normalFlight(dt){
  readInput();
  let inB=IN.bank, inP=IN.pitch;
  if(S.auto){ const a=autoPilot(); inB=a[0]; inP=a[1]; }
  const playing=S.mode==='playing';
  S.bankIn=sm(S.bankIn,inB,playing?8:5,dt);
  S.pitchIn=sm(S.pitchIn,inP,playing?7:4.5,dt);

  S.vMax=96+54*dOf(P.z);

  const wantBoost=playing&&!S.auto&&IN.boost&&S.turbo>0.5;
  if(wantBoost&&!S.boosting){ AU.sweep(true); S.shake=Math.min(1,S.shake+0.35); HUD.say('TURBO','good',900); }
  if(S.boosting&&!wantBoost) AU.sweep(false);
  S.boosting=wantBoost;

  if(S.boosting) S.turbo-=34*dt;
  else S.turbo+=(S.speed>S.vMax*0.93?3.6:-1.7)*dt;
  S.turbo=clamp(S.turbo,0,100);

  let target=S.vMax;
  target*=1+0.22*clamp(-S.pitchIn,0,1);
  target*=1-0.32*clamp(S.pitchIn,0,1);
  if(S.boosting) target*=1.78;
  S.speed=sm(S.speed,target,S.speed<target?1.25:2.4,dt);
  S.maxSpeed=Math.max(S.maxSpeed,S.speed);

  P.vx=sm(P.vx,S.bankIn*S.speed*0.62,2.7,dt);
  P.x+=P.vx*dt;
  P.vy=sm(P.vy,S.pitchIn*S.speed*0.50-7.0,3.2,dt);
  P.y+=P.vy*dt;
  P.z+=S.speed*dt;

  S.yaw=sm(S.yaw,Math.atan2(P.vx,Math.max(35,S.speed*0.8)),3.6,dt);
  S.bankVis=sm(S.bankVis,clamp(S.bankIn*0.95+P.vx/Math.max(45,S.speed)*0.5,-1.2,1.2),6,dt);
  S.pitchVis=sm(S.pitchVis,-S.pitchIn*0.5-clamp(P.vy/Math.max(45,S.speed),-0.5,0.5)*0.35,7,dt);
}
function overUpdate(dt){
  S.speed=Math.max(12,S.speed-dt*52);
  P.vx=sm(P.vx,0,1.2,dt);
  P.x+=P.vx*dt; P.z+=S.speed*dt;
  P.vy-=26*dt; P.y+=P.vy*dt;
  if(P.y<2){ P.y=2; P.vy=Math.abs(P.vy)*0.3; }
  S.yaw+=dt*0.7; S.bankVis+=dt*2.6; S.pitchVis=Math.sin(S.time*3.4)*0.4;
}
function scoring(dt){
  S.energy-=(4.0+6.5*clamp(S.speed/S.vMax,0,1.5))*dt;
  if(S.turb>0){ S.energy-=7*dt; S.shake=Math.max(S.shake,0.5); }
  S.score+=S.speed*dt*0.11*S.mult;
  S.nmTimer+=dt;
  if(S.nmTimer>2.6) S.mult=Math.max(1,S.mult-0.6*dt);
  if(S.energy<=0){ S.energy=0; gameOver(); }
}
function timers(dt){
  S.shake=Math.max(0,S.shake-dt*2.0);
  S.flashHit=Math.max(0,S.flashHit-dt*2.2);
  S.invuln-=dt;
  S.turb=Math.max(0,S.turb-dt);
  S.flash=Math.max(0,S.flash-dt*2.6);
  S.fbHold-=dt;
}
function trail(dt){
  const sn=clamp(S.speed/Math.max(60,S.vMax),0,1.5);
  const sy=Math.sin(S.yaw), cz=Math.cos(S.yaw);
  const ex=P.x-sy*5.2, ey=P.y-0.1, ez=P.z-cz*5.2;
  const n=Math.floor(1+sn*4+(S.boosting?5:0));
  for(let i=0;i<n;i++){
    const j=0.35+Math.random()*0.65, spd=S.speed*0.10*j;
    if(S.boosting) spawnPart(ex,ey,ez,
      -sy*spd*0.6+(Math.random()-0.5)*10,(Math.random()-0.5)*9,-cz*spd*0.6-S.speed*0.05+(Math.random()-0.5)*10,
      0.28+Math.random()*0.4,0.95+Math.random()*1.1,0.45,0.9,1.0,1.0,1.0);
    else spawnPart(ex,ey,ez,
      -sy*spd*0.5+(Math.random()-0.5)*7,(Math.random()-0.5)*6,-cz*spd*0.5-S.speed*0.04+(Math.random()-0.5)*7,
      0.30+Math.random()*0.45,0.7+Math.random()*0.9,1.0,0.55,0.22,1.0,1.0);
  }
}

/* ------------------------------- atmosphere ------------------------------ */
function initPalette(){
  PAL.topA=new THREE.Color(0x301d5c); PAL.topB=new THREE.Color(0x09061e);
  PAL.midA=new THREE.Color(0x8d3f7d); PAL.midB=new THREE.Color(0x2a1450);
  PAL.botA=new THREE.Color(0xff8f4c); PAL.botB=new THREE.Color(0x99334e);
  PAL.glowA=new THREE.Color(0xffdca6); PAL.glowB=new THREE.Color(0xff9d8e);
  PAL.fogA=new THREE.Color(0xff9a63); PAL.fogB=new THREE.Color(0x7a2f55);
  PAL.sunA=new THREE.Color(0xffc490); PAL.sunB=new THREE.Color(0x8fa0e6);
}
function applyAtmosphere(d){
  skyMat.uniforms.uTop.value.copy(PAL.topA).lerp(PAL.topB,d);
  skyMat.uniforms.uMid.value.copy(PAL.midA).lerp(PAL.midB,d);
  skyMat.uniforms.uBot.value.copy(PAL.botA).lerp(PAL.botB,d);
  skyMat.uniforms.uGlow.value.copy(PAL.glowA).lerp(PAL.glowB,d);
  scene.fog.color.copy(PAL.fogA).lerp(PAL.fogB,d);
  scene.fog.near=lerp(155,112,d); scene.fog.far=lerp(880,600,d);
  ptMat.uniforms.uFogCol.value.copy(scene.fog.color);
  orbMat.uniforms.uFogCol.value.copy(scene.fog.color);
  ptMat.uniforms.uFogNear.value=scene.fog.near; ptMat.uniforms.uFogFar.value=scene.fog.far;
  orbMat.uniforms.uFogNear.value=scene.fog.near; orbMat.uniforms.uFogFar.value=scene.fog.far;
  sunLight.color.copy(PAL.sunA).lerp(PAL.sunB,d);
  sunLight.intensity=lerp(1.42,0.86,d);
}

/* --------------------------------- camera -------------------------------- */
function updateCamera(dt,snap){
  const sn=clamp(S.speed/Math.max(60,S.vMax),0,1.5);
  const back=15.5+sn*4.5+(S.boosting?3.5:0);
  const sy=Math.sin(S.yaw), cz=Math.cos(S.yaw);
  const tz=P.z-cz*back;
  const t=terrainAt(tz);
  let tx=P.x-sy*back;
  tx=clamp(tx,t.x-t.hw+4.5,t.x+t.hw+4.5);
  const ty=clamp(P.y+4.6-S.pitchVis*2.2,1.4,ceilY(tz)+2.5);
  if(snap) camPos.set(tx,ty,tz);
  else camPos.set(sm(camPos.x,tx,9,dt),sm(camPos.y,ty,9,dt),sm(camPos.z,tz,11,dt));
  camLook.set(sm(camLook.x,P.x+sy*20,7,dt),
              sm(camLook.y,P.y+3.6-S.pitchVis*7,7,dt),
              sm(camLook.z,P.z+cz*20,9,dt));
  camera.position.copy(camPos);
  camera.lookAt(camLook);
  camera.rotateZ(-S.bankVis*0.20);
  if(S.shake>0.001){
    const a=S.shake*S.shake*1.7+0.015*S.shake;
    camera.position.x+=(Math.random()-0.5)*a*1.7;
    camera.position.y+=(Math.random()-0.5)*a*1.5;
    camera.position.z+=(Math.random()-0.5)*a*1.3;
    camera.rotateZ((Math.random()-0.5)*a*0.11);
    camera.rotateX((Math.random()-0.5)*a*0.07);
  }
  const fov=66+sn*10+(S.boosting?17:0);
  const nf=sm(camera.fov,fov,4.5,dt);
  if(Math.abs(nf-camera.fov)>0.005){ camera.fov=nf; camera.updateProjectionMatrix(); }
  const sc=sceneH*0.5/Math.tan(camera.fov*Math.PI/360);
  ptMat.uniforms.uScale.value=sc;
  orbMat.uniforms.uScale.value=sc*1.05;
  sky.position.copy(camera.position);
  ground.position.set(P.x,-1.4,P.z);
  sunLight.position.set(P.x+_dir.x*300,P.y+_dir.y*300+45,P.z+_dir.z*300);
  sunLight.target.position.set(P.x,P.y,P.z);
  sunLight.target.updateMatrixWorld();
  sunSprite.position.set(camera.position.x+_dir.x*2600,
                         camera.position.y+_dir.y*2600,
                         camera.position.z+_dir.z*2600);
}
function craftUpdate(dt){
  const sn=clamp(S.speed/Math.max(60,S.vMax),0,1.5);
  craft.position.set(P.x,P.y,P.z);
  craft.rotation.set(S.pitchVis,S.yaw,-S.bankVis);
  const es=2.6+sn*2.4+(S.boosting?4.5:0)+Math.sin(S.time*26)*0.25;
  engineSprite.scale.set(es,es,1);
  engineSprite.material.opacity=S.mode==='over'?0.3:0.72;
  engineLight.intensity=S.mode==='over'?0.3:1.1+sn*1.7+(S.boosting?3.2:0);
}

/* ------------------------------- run control ----------------------------- */
function resetRun(newSeed){
  SEED=(newSeed>>>0)||1;
  clearParticles();
  resetChunks();
  P.x=corrX(0); P.y=corrY(0); P.z=0; P.vx=0; P.vy=0;
  S.score=0; S.mult=1; S.nmTimer=0; S.nmCount=0;
  S.energy=100; S.turbo=0; S.boosting=false;
  S.speed=46; S.maxSpeed=0; S.vMax=96;
  S.bankIn=0; S.pitchIn=0; S.bankVis=0; S.pitchVis=0; S.yaw=0;
  S.shake=0; S.flashHit=0; S.invuln=0; S.turb=0;
  S.armed=[true,true]; S.fbHold=0.4; S.overT=0;
  S.flash=1; S.flashCol=[0,0,0];
  craft.position.set(P.x,P.y,P.z);
  camPos.set(corrX(-30),corrY(-30)+4.6,-30);
  camLook.set(P.x,P.y+2,P.z+40);
  updateCamera(0.016,true);
  applyAtmosphere(0);
}
function startGame(){
  AU.init();
  resetRun((Math.random()*1e9)>>>0);
  S.mode='playing'; S.auto=false;
  $('ov').classList.remove('show');
  $('ov').dataset.mode='menu';
  $('hud').classList.add('on');
  S.flash=1; S.flashCol=[1,1,1];
  HUD.say('RUN LIVE','',1100);
  const h=$('hint');
  h.textContent='ORBS REFUEL · CLOSE PASSES MULTIPLY';
  h.classList.add('on');
  clearTimeout(startGame._t);
  startGame._t=setTimeout(()=>h.classList.remove('on'),4600);
}
function gameOver(){
  if(S.mode!=='playing') return;
  S.mode='over'; S.overT=0; S.auto=false; S.energy=0;
  S.flash=1; S.flashCol=[0.6,0.07,0.13]; S.shake=1.2;
  spawnSparks(P.x,P.y,P.z,90,1.0,0.55,0.25,62);
  spawnSparks(P.x,P.y,P.z,55,0.5,0.8,1.0,46);
  AU.sweep(false); AU.thud();
  S.best=Math.max(S.best,Math.round(S.score));
  try{ localStorage.setItem('heliosbest_v2',String(S.best)); }catch(e){}
  $('finalScore').textContent=fmt(S.score);
  $('finalSpeed').textContent=Math.round(S.maxSpeed*3)+' km/h';
  $('finalDist').textContent=(P.z>=1000?(P.z/1000).toFixed(2)+' km':Math.round(P.z)+' m');
  $('finalNM').textContent=S.nmCount;
  $('overBest').textContent=fmt(S.best);
  $('menuBest').textContent=fmt(S.best);
  setTimeout(()=>{ if(S.mode==='over'){ $('ov').dataset.mode='over'; $('ov').classList.add('show'); } },950);
}
function toMenu(){
  S.mode='menu'; S.auto=true;
  $('ov').classList.remove('show');
  resetRun((Math.random()*1e9)>>>0);
}
function togglePause(){
  if(S.mode==='playing'){
    S.mode='pause'; $('ov').dataset.mode='pause'; $('ov').classList.add('show');
    AU.update(0,0,false);
  } else if(S.mode==='pause'){
    S.mode='playing'; $('ov').classList.remove('show'); lastT=performance.now();
  }
}

/* --------------------------------- update -------------------------------- */
function update(dt){
  S.time+=dt;
  if(S.mode==='pause'){ updateCamera(dt,false); craftUpdate(dt); return; }
  if(S.mode==='over') overUpdate(dt); else normalFlight(dt);
  streamChunks(3);
  collide();
  if(S.mode==='playing'){ wallScan(); obsScan(); scoring(dt); }
  orbsUpdate(S.mode==='playing');
  updateParticles(dt);
  trail(dt);
  updateOrbPoints();
  timers(dt);
  applyAtmosphere(dOf(P.z));
  craftUpdate(dt);
  updateCamera(dt,false);
}

/* ---------------------------------- loop --------------------------------- */
let lastT=0, booted=false;
let _ftAcc=0, _ftN=0;
function loop(now){
  requestAnimationFrame(loop);
  let dt=(now-lastT)/1000; lastT=now;
  if(!(dt>0)) dt=0.016;
  /* adaptive resolution: keep the frame budget on slower GPUs */
  _ftAcc+=dt; _ftN++;
  if(_ftN>=50){
    const avg=_ftAcc/_ftN; _ftAcc=0; _ftN=0;
    let nq=QSCALE;
    if(avg>0.030) nq=Math.max(0.55,QSCALE-0.18);
    else if(avg<0.0155&&QSCALE<1) nq=Math.min(1,QSCALE+0.10);
    if(Math.abs(nq-QSCALE)>0.01){ QSCALE=nq; resize(); }
  }
  dt=Math.min(dt,0.05);
  update(dt);
  HUD.update();
  AU.update(clamp(S.speed/Math.max(60,S.vMax),0,1.3),S.boosting,S.mode!=='pause');
  renderFrame();
}

/* ---------------------------------- boot --------------------------------- */
function boot(){
  if(booted) return; booted=true;
  canvas=$('view');
  renderer=new THREE.WebGLRenderer({canvas:canvas,antialias:false,alpha:false,
    powerPreference:'high-performance'});
  renderer.setClearColor(0x07040f,1);
  renderer.shadowMap.enabled=true;
  renderer.shadowMap.type=THREE.PCFSoftShadowMap;
  renderer.autoClear=true;

  initScene();
  initPost();
  initRTs();
  initPalette();

  camPos=new THREE.Vector3(); camLook=new THREE.Vector3();
  _v=new THREE.Vector3(); _dir=new THREE.Vector3(-0.16,0.28,0.95).normalize();
  _c1=new THREE.Color(); _c2=new THREE.Color();

  HUD.init(); initInput();
  try{ S.best=parseInt(localStorage.getItem('heliosbest_v2')||'0',10)||0; }catch(e){ S.best=0; }
  $('menuBest').textContent=fmt(S.best);
  $('best').textContent=fmt(S.best);

  resize();
  window.addEventListener('resize',resize);
  resetRun((Math.random()*1e9)>>>0);
  S.mode='menu'; S.auto=true;

  $('startBtn').addEventListener('click',startGame);
  $('retryBtn').addEventListener('click',startGame);
  $('resumeBtn').addEventListener('click',togglePause);
  $('quitBtn').addEventListener('click',toMenu);
  $('snd').addEventListener('click',()=>{ AU.init(); const m=!AU.muted; AU.setMuted(m);
    $('snd').classList.toggle('off',m); });

  document.addEventListener('visibilitychange',()=>{
    if(document.hidden&&S.mode==='playing') togglePause();
  });

  $('hud').classList.add('on');
  lastT=performance.now();
  requestAnimationFrame(loop);
}

/* test hooks (pure logic, no WebGL required) */
window.__T={ buildChunkData, terrainAt, corrX, corrY, ceilY, cX, halfW, wallH, boxDist,
  boxInside, NEAR_WALL, NEAR_OBS, CH_LEN, S, P, chunks,
  chunkCount:()=>chunks.size, meshCount:()=>chunkMeshes.length,
  setSeed:(s)=>{ SEED=s>>>0||1; } };

loadThree(0).then(boot).catch(()=>{ $('fatal').classList.add('on'); });
</script>
</body>
</html>
<!-- agent-meta {"model":"xiaomi/mimo-v2.6-flash","provider":"openrouter","persona":"full","sessionId":"e9fec70b-3bf5-402b-bcd6-8ee2a9b1b1d1","tokensIn":9381682,"tokensOut":153089,"tokensTotal":9534771,"cost":0.15562258880000002,"turns":41,"toolCalls":65,"failedToolCalls":3,"timestamp":"2026-09-27T16:35:52.549Z"} -->