← Canyon Run results

Canyon Run

Qwen 3.8 · typescript

Initial view of Canyon Run

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 65,574 bytes · SHA-256 65b4c6d059c9
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SUNRIFT — High-Speed Procedural Canyon Racer</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.147.0/three.min.js"></script>
<style>
/* ── css: style ── */
:root{
  --ink:#ffe8d0; --dim:rgba(255,220,190,.55); --accent:#ffb35c;
  --cyan:#59e6ff; --bad:#ff5a64;
}
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:100%;height:100%;overflow:hidden;background:#120718}
#app{position:fixed;inset:0;font-family:ui-monospace,'SF Mono','Cascadia Code','Roboto Mono',Menlo,Consolas,monospace;color:var(--ink);cursor:crosshair;user-select:none;-webkit-user-select:none}
#game{position:absolute;inset:0;width:100%;height:100%;display:block}
/* ---------- HUD ---------- */
#hud{position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity .5s}
#hud.on{opacity:1}
.hud-top{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:flex-start;padding:18px 26px}
.hud-block .label{font-size:10px;letter-spacing:.32em;color:var(--dim)}
.hud-block .big{font-size:26px;font-weight:700;letter-spacing:.08em;margin-top:2px}
.hud-block .sub{font-size:11px;color:var(--dim);margin-top:1px}
.hud-block.center{text-align:center}
#mult{font-size:44px;font-weight:800;letter-spacing:.02em;color:var(--accent);text-shadow:0 0 18px rgba(255,170,80,.55);transform-origin:center;transition:color .2s}
#mult.near{color:var(--cyan);text-shadow:0 0 22px rgba(90,230,255,.8)}
#mult.pulse{animation:mpulse .28s ease-out}
@keyframes mpulse{0%{transform:scale(1)}35%{transform:scale(1.35)}100%{transform:scale(1)}}
.hud-bottom{position:absolute;bottom:0;left:0;right:0;display:flex;justify-content:space-between;align-items:flex-end;padding:18px 26px;gap:20px}
#speedWrap{width:200px}
.speedline{height:4px;background:rgba(255,230,200,.14);margin-top:8px;overflow:hidden}
#speedBar{height:100%;width:0%;background:linear-gradient(90deg,#ffd9a0,#ff9d45,#ff5a64);box-shadow:0 0 10px rgba(255,150,70,.8)}
#energyWrap{width:220px;height:16px;border:1px solid rgba(255,190,120,.4);padding:2px;transition:border-color .3s,box-shadow .3s}
#energyFill{height:100%;width:100%;background:linear-gradient(90deg,#3de0c8,#7df0ff);box-shadow:0 0 12px rgba(80,230,255,.75);transition:width .12s linear}
#energyWrap.low{border-color:rgba(255,90,100,.85);animation:elow .5s infinite}
#energyWrap.low #energyFill{background:linear-gradient(90deg,#ff5a64,#ff9d45);box-shadow:0 0 14px rgba(255,90,100,.85)}
@keyframes elow{0%,100%{box-shadow:0 0 0 rgba(255,90,100,0)}50%{box-shadow:0 0 14px rgba(255,90,100,.7)}}
#turboOuter{width:200px;text-align:right}
#turboOuter .label{font-size:10px;letter-spacing:.32em;color:var(--dim);margin-top:6px}
#turboWrap{height:9px;border:1px solid rgba(255,230,200,.25);padding:1px}
#turboFill{height:100%;width:0%;background:linear-gradient(90deg,#ff9d45,#fff1d6);box-shadow:0 0 10px rgba(255,200,120,.9)}
#boostHint{font-size:10px;letter-spacing:.28em;color:var(--cyan);margin-bottom:5px;opacity:0;transition:opacity .3s}
#boostHint.on{opacity:1;animation:hin 1.2s infinite}
@keyframes hin{0%,100%{opacity:.45}50%{opacity:1}}
/* ---------- flashes ---------- */
#flash{position:absolute;inset:0;background:#ff3b30;opacity:0;pointer-events:none;mix-blend-mode:screen}
#flashCyan{position:absolute;inset:0;background:#4fe3ff;opacity:0;pointer-events:none;mix-blend-mode:screen}
/* ---------- overlays ---------- */
#overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:radial-gradient(ellipse at 50% 60%,rgba(30,10,30,.55),rgba(10,4,14,.82));z-index:5}
#overlay.off{display:none}
.screen{max-width:640px;width:88%;text-align:center;padding:40px 30px;border:1px solid rgba(255,190,120,.28);background:rgba(16,7,20,.6);backdrop-filter:blur(6px)}
.title{font-size:64px;font-weight:900;letter-spacing:.34em;text-indent:.34em;background:linear-gradient(180deg,#ffe9c8 0%,#ffb35c 45%,#ff5a64 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;filter:drop-shadow(0 0 24px rgba(255,150,70,.45))}
.tag{font-size:11px;letter-spacing:.5em;color:var(--dim);margin-top:8px}
.controls{margin:26px auto 6px;font-size:12px;letter-spacing:.18em;line-height:2.1;color:rgba(255,230,200,.8)}
.tip{font-size:10px;letter-spacing:.24em;color:var(--dim);margin-bottom:26px}
.btn{pointer-events:auto;cursor:pointer;font-family:inherit;font-size:15px;font-weight:700;letter-spacing:.4em;text-indent:.4em;color:#180a06;background:linear-gradient(180deg,#ffd9a0,#ff9d45);border:1px solid #ffcf90;padding:14px 44px;box-shadow:0 0 24px rgba(255,160,80,.4);transition:transform .12s,box-shadow .12s}
.btn:hover{transform:translateY(-2px) scale(1.03);box-shadow:0 0 34px rgba(255,180,90,.7)}
.btn:active{transform:translateY(0) scale(.99)}
.pauseTitle{font-size:34px;letter-spacing:.5em;text-indent:.5em;color:var(--accent)}
.overTitle{font-size:38px;font-weight:900;letter-spacing:.22em;color:var(--bad);text-shadow:0 0 22px rgba(255,90,100,.55)}
#overReason{font-size:11px;letter-spacing:.4em;margin:12px 0 24px}
.stats{display:grid;grid-template-columns:1fr 1fr;gap:16px 30px;margin:0 auto 30px;max-width:420px;text-align:left}
.stats>div{display:flex;flex-direction:column;border-bottom:1px solid rgba(255,190,120,.22);padding-bottom:8px}
.stats .label{font-size:9px;letter-spacing:.3em;color:var(--dim)}
.stats .big{font-size:22px;font-weight:700;margin-top:2px}
.hidden{display:none !important}
</style>
</head>
<body>
<div id="app"><canvas id="game"></canvas>
<div id="hud">
  <div class="hud-top">
    <div class="hud-block">
      <div class="label">SCORE</div>
      <div class="big" id="score">0</div>
      <div class="sub" id="dist">0 m</div>
    </div>
    <div class="hud-block center">
      <div id="mult">×1</div>
      <div class="label">MULTIPLIER</div>
    </div>
    <div class="hud-block right">
      <div class="label">SPEED</div>
      <div class="big" id="speed">0</div>
      <div class="sub">KM/H</div>
    </div>
  </div>
  <div class="hud-bottom">
    <div id="speedWrap">
      <div class="label">VELOCITY</div>
      <div class="speedline"><div id="speedBar"></div></div>
    </div>
    <div id="energyWrap" class="bar-outer">
      <div class="label">ENERGY</div>
      <div id="energyFill"></div>
    </div>
    <div id="turboOuter">
      <div id="boostHint">BOOST READY — HOLD SHIFT</div>
      <div id="turboWrap" class="bar-outer"><div id="turboFill"></div></div>
      <div class="label">TURBO</div>
    </div>
  </div>
</div>
<div id="flash"></div>
<div id="flashCyan"></div>
<div id="overlay">
  <div class="screen" id="scrStart">
    <h1 class="title">SUNRIFT</h1>
    <p class="tag">HIGH-SPEED PROCEDURAL CANYON RACER</p>
    <div class="controls">
      <div>MOUSE / ← → &nbsp;·&nbsp; STEER &amp; BANK</div>
      <div>↑ ↓ / W S &nbsp;·&nbsp; PITCH — CLIMB · DIVE</div>
      <div>SHIFT / SPACE &nbsp;·&nbsp; BOOST (WHEN TURBO IS FULL)</div>
      <div>P &nbsp;·&nbsp; PAUSE</div>
    </div>
    <p class="tip">GRAZE THE ROCK FOR MULTIPLIERS · GRAB CYAN ORBS TO REFUEL</p>
    <button class="btn" id="btnStart">LAUNCH</button>
    <div class="err hidden" id="loadErr">THREE.JS FAILED TO LOAD — CHECK CONNECTION AND RELOAD</div>
  </div>
  <div class="screen hidden" id="scrPause">
    <h2 class="pauseTitle">PAUSED</h2>
    <p class="sub">THE CANYON WAITS FOR NO ONE</p>
    <button class="btn" id="btnResume">RESUME</button>
  </div>
  <div class="screen hidden" id="scrOver">
    <h2 class="overTitle">RUN TERMINATED</h2>
    <div class="sub" id="overReason">CRASHED</div>
    <div class="stats">
      <div><span class="label">FINAL SCORE</span><span class="big" id="finScore">0</span></div>
      <div><span class="label">MAX SPEED</span><span class="big" id="finSpeed">0</span></div>
      <div><span class="label">DISTANCE</span><span class="big" id="finDist">0</span></div>
      <div><span class="label">TOP MULT</span><span class="big" id="finMult">×1</span></div>
    </div>
    <button class="btn" id="btnRestart">RESTART</button>
  </div>
</div></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./state":"src/state.ts","./input":"src/input.ts","./audio":"src/audio.ts","./world":"src/world.ts","./player":"src/player.ts","./post":"src/post.ts","./particles":"src/particles.ts","./hud":"src/hud.ts"},"src/world.ts":{"./rng":"src/rng.ts","./state":"src/state.ts"},"src/player.ts":{"./state":"src/state.ts","./world":"src/world.ts"},"src/hud.ts":{"./state":"src/state.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const state_1 = require("./state");
const input_1 = require("./input");
const audio_1 = require("./audio");
const world_1 = require("./world");
const player_1 = require("./player");
const post_1 = require("./post");
const particles_1 = require("./particles");
const hud_1 = require("./hud");
const MAX_DT = 1 / 30;
class Game {
    constructor() {
        this.state = null;
        this.phase = 'menu';
        this.elapsed = 0;
        this.camFov = 78;
        this.last = performance.now();
        this.pr = 1;
        this.tick = (now) => {
            requestAnimationFrame(this.tick);
            let dt = (now - this.last) / 1000;
            this.last = now;
            dt = Math.min(MAX_DT, Math.max(0.0001, dt));
            this.elapsed += dt;
            const s = this.state;
            if (!s)
                return;
            const running = this.phase === 'run';
            const menu = this.phase === 'menu';
            if (running) {
                s.time += dt;
                // ---- energy / turbo / speed ----
                const drain = s.boosting ? 5.2 : 3.1 + Math.min(2.5, s.dist * 0.00004);
                s.energy -= drain * dt;
                s.speed += ((s.boosting ? 185 : 66) - s.speed) * Math.min(1, dt * (s.boosting ? 3.2 : 1.1));
                if (s.boosting) {
                    s.turbo -= 34 * dt;
                    if (s.turbo <= 0) {
                        s.turbo = 0;
                        s.boosting = false;
                    }
                }
                else if (s.boost && s.turbo >= 100) {
                    s.boosting = true;
                }
                // turbo charge: straightness + speed
                const steer = this.input.steer;
                const straight = Math.abs(steer) < 0.18 && Math.abs(s.vx) < 14;
                if (straight)
                    s.turbo += 13 * dt;
                if (s.speed > 120)
                    s.turbo += 8 * dt;
                s.turbo = Math.min(100, s.turbo);
                // energy zero -> forced slow (tension)
                if (s.energy <= 0) {
                    s.energy = 0;
                    s.speed += (46 - s.speed) * Math.min(1, dt * 2.2);
                    s.lowEnergyT += dt;
                    if (s.lowEnergyT > 7) {
                        this.gameOver('ENERGY DEPLETED — THE CRAFT DRIFTED OUT');
                        return;
                    }
                }
                else {
                    s.lowEnergyT = 0;
                }
                s.boost = !!this.input.boost;
                // ---- player physics ----
                let stIn = this.input.steer;
                let ptIn = this.input.pitch;
                if (window.__assist) {
                    const ac = this.world.chunkAt();
                    if (ac) {
                        const col = this.world.columnAt(ac, this.world.playerZ());
                        stIn = stIn * 0.55 + (col.x - s.px) * 0.09;
                        ptIn = ptIn * 0.55 + (col.y - 3 - s.py) * 0.07;
                    }
                }
                const hit = this.player.update(dt, s, stIn, ptIn, this.world);
                s.dist += s.speed * dt;
                s.score += s.speed * s.mult * dt * 0.1;
                s.maxSpeed = Math.max(s.maxSpeed, s.speed);
                this.audio.setEngine(Math.min(1, s.speed / 185), s.boosting);
                // ---- world ----
                this.world.update(dt, this.elapsed, s.speed, s.dist);
                // ---- near-miss scoring ----
                s.nearCd -= dt;
                const chunk = this.world.chunkAt();
                const pz = this.world.playerZ();
                let near = false;
                if (chunk) {
                    const wc = this.world.wallClearance(chunk, pz, s.px);
                    const mc = this.world.monoClearance(chunk, pz, s.px);
                    const gap = Math.min(wc, mc) - world_1.PLAYER_R;
                    const prox = (d) => d < 2.6 ? 1 : d < 6 ? 0.55 : d < 11 ? 0.3 : 0;
                    near = gap < 2.6;
                    const p = prox(gap);
                    if (p > 0) {
                        s.mult += p * dt * (s.boosting ? 2.2 : 1.15);
                        s.maxMult = Math.max(s.maxMult, s.mult);
                    }
                    else {
                        s.mult = Math.max(1, s.mult - dt * 1.4);
                    }
                    if (near && s.nearCd <= 0) {
                        s.nearCd = 0.8;
                        const bonus = 90 * s.mult;
                        s.score += bonus;
                        s.orbFlash = Math.min(1, s.orbFlash + 0.9);
                        this.hudMultPing();
                        this.audio.whoosh(Math.min(1, 1.4 - gap / 3));
                        this.post.shakeAmp = Math.max(this.post.shakeAmp, 0.9 + (2.6 - gap) * 0.4);
                        s.shake = 0.5;
                    }
                }
                // ---- orbs ----
                if (chunk) {
                    for (const o of chunk.orbs) {
                        if (!o.alive || !o.mesh)
                            continue;
                        const dz = pz - o.z;
                        if (dz > -6 && dz < 9) {
                            const dx = s.px - o.x, dy = s.py - o.y;
                            if (dx * dx + dy * dy < 5.2 * 5.2) {
                                o.alive = false;
                                o.mesh.visible = false;
                                s.energy = Math.min(100, s.energy + 15);
                                s.turbo = Math.min(100, s.turbo + 12);
                                s.score += 50 * s.mult;
                                s.orbFlash = 1;
                                this.audio.orb();
                            }
                        }
                    }
                }
                // ---- camera (flight feel) ----
                const fovT = s.boosting ? 96 : 78 + (s.speed / 185) * 9;
                this.camFov += (fovT - this.camFov) * Math.min(1, dt * 4);
                if (Math.abs(this.camFov - this.camera.fov) > 0.05) {
                    this.camera.fov = this.camFov;
                    this.camera.updateProjectionMatrix();
                }
                const cp = this.player.group.position;
                const lx = s.px * 0.55 + steer * 3.2;
                const ly = s.py + 3.4 + this.input.pitch * -1.6;
                const lz = 13.5;
                this.camera.position.x += (lx - this.camera.position.x) * Math.min(1, dt * 6);
                this.camera.position.y += (ly - this.camera.position.y) * Math.min(1, dt * 6);
                this.camera.position.z += (lz - this.camera.position.z) * Math.min(1, dt * 6);
                this.camera.lookAt(cp.x * 0.75, cp.y + 1.2, -16);
                this.sun.position.set(this.camera.position.x + 18, 26, this.camera.position.z - 40);
                this.sun.target.position.set(this.camera.position.x, 0, -20);
                this.sun.target.updateMatrixWorld();
                // ---- particles ----
                this.dust.spawn(cp.x, cp.y, 0, s.vx, s.vy, s.boosting);
                const dn = s.boosting ? 9 : Math.max(1, Math.floor(s.speed / 16));
                for (let i = 0; i < dn; i++)
                    this.dust.spawnDust(s.px, s.py, -8, s.speed);
                this.dust.update(dt, 0);
                // shake decay + boost rumble
                if (s.boosting)
                    this.post.shakeAmp = Math.max(this.post.shakeAmp, 0.16);
                this.post.flash.r *= 0.9;
                this.post.flash.g *= 0.9;
                this.post.flash.b *= 0.9;
                this.hud.update(s, near);
                if (hit) {
                    this.gameOver(hit.reason);
                }
            }
            else if (menu) {
                // slow autopilot cruise for the menu backdrop
                s.time += dt;
                s.speed = 42;
                const chunk = this.world.chunkAt();
                const mz = this.world.playerZ();
                const steerAuto = chunk ? (this.world.columnAt(chunk, mz).x - s.px) * 0.06 : 0;
                const pitchAuto = chunk ? (this.world.columnAt(chunk, mz).y - 2 - s.py) * 0.05 : 0;
                this.player.update(dt, s, Math.max(-0.6, Math.min(0.6, steerAuto)), Math.max(-0.5, Math.min(0.5, pitchAuto)), this.world);
                this.world.update(dt, this.elapsed, s.speed, 0);
                s.dist += s.speed * dt;
                const cp = this.player.group.position;
                this.camera.position.x += (s.px * 0.55 - this.camera.position.x) * Math.min(1, dt * 3);
                this.camera.position.y += (s.py + 3.4 - this.camera.position.y) * Math.min(1, dt * 3);
                this.camera.position.z += (13.5 - this.camera.position.z) * Math.min(1, dt * 3);
                this.camera.lookAt(cp.x * 0.75, cp.y + 1.2, -16);
                this.dust.update(dt, 0);
                this.post.flash.r *= 0.9;
                this.post.flash.g *= 0.9;
                this.post.flash.b *= 0.9;
            }
            else if (this.phase === 'over') {
                // debris drift
                this.post.flash.r *= 0.93;
                this.post.flash.g *= 0.93;
                this.post.flash.b *= 0.93;
                this.post.shakeAmp *= 0.9;
            }
            // ---- render ----
            this.post.render(this.scene, this.camera, this.mainRT);
        };
        const canvas = document.getElementById('game');
        if (!canvas)
            throw new Error('canvas missing');
        if (typeof THREE === 'undefined') {
            document.getElementById('loadErr')?.classList.remove('hidden');
            return;
        }
        this.renderer = new THREE.WebGLRenderer({ canvas, antialias: false, powerPreference: 'high-performance' });
        this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.5));
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.scene = new THREE.Scene();
        this.scene.fog = new THREE.FogExp2(0x3b1a3f, 0.0052);
        const sky = this.buildSky();
        this.scene.add(sky);
        this.sun = new THREE.DirectionalLight(0xffc37a, 1.55);
        this.sun.position.set(18, 26, -40);
        this.scene.add(this.sun);
        this.scene.add(new THREE.AmbientLight(0x53304f, 0.85));
        const hemi = new THREE.HemisphereLight(0xff9a5e, 0x33122c, 0.5);
        this.scene.add(hemi);
        this.camera = new THREE.PerspectiveCamera(78, window.innerWidth / window.innerHeight, 0.1, 900);
        this.camera.position.set(0, 12, 14);
        this.world = new world_1.World(this.scene);
        this.player = new player_1.Player(this.scene);
        this.post = new post_1.PostFX(this.renderer);
        this.dust = new particles_1.Particles(this.scene);
        const rtOpts = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, depthBuffer: true };
        this.mainRT = new THREE.WebGLRenderTarget(2, 2, rtOpts);
        this.resize();
        this.input = new input_1.Input(document.getElementById('app'));
        this.hud = new hud_1.HUD();
        this.audio = new audio_1.AudioBus();
        this.input.onP = () => this.togglePause();
        document.getElementById('btnStart').addEventListener('click', () => this.start());
        document.getElementById('btnRestart').addEventListener('click', () => this.start());
        document.getElementById('btnResume').addEventListener('click', () => this.togglePause());
        window.addEventListener('resize', () => this.resize());
        // also start with Enter/Space on start screen
        window.addEventListener('keydown', e => {
            if (this.phase === 'menu' || this.phase === 'over') {
                if (e.code === 'Enter')
                    this.start();
            }
        });
        this.beginMenuRun();
        this.last = performance.now();
        requestAnimationFrame(this.tick);
    }
    buildSky() {
        const mat = new THREE.ShaderMaterial({
            side: THREE.BackSide,
            depthWrite: false,
            uniforms: {},
            vertexShader: `varying vec3 vP; void main(){ vP = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }`,
            fragmentShader: `
        varying vec3 vP;
        void main(){
          float h = normalize(vP).y;
          vec3 top = vec3(0.24, 0.10, 0.34);
          vec3 mid = vec3(0.82, 0.33, 0.28);
          vec3 low = vec3(1.0, 0.62, 0.30);
          vec3 col = mix(low, mid, smoothstep(0.02, 0.28, h));
          col = mix(col, top, smoothstep(0.25, 0.75, h));
          col = mix(vec3(0.10, 0.05, 0.12), col, smoothstep(-0.2, 0.06, h));
          // sun glow
          vec3 sd = normalize(vec3(0.32, 0.22, -1.0));
          float g = pow(max(dot(normalize(vP), sd), 0.0), 14.0);
          col += vec3(1.0, 0.72, 0.42) * g * 0.9;
          gl_FragColor = vec4(col, 1.0);
        }`,
        });
        // big inverted sphere (camera follows, so it tracks)
        const geo = new THREE.IcosahedronGeometry(500, 1);
        const mesh = new THREE.Mesh(geo, mat);
        mesh.position.z = -200;
        return mesh;
    }
    resize() {
        const w = window.innerWidth, h = window.innerHeight;
        this.renderer.setSize(w, h);
        this.camera.aspect = w / h;
        this.camera.updateProjectionMatrix();
        this.pr = Math.min(window.devicePixelRatio || 1, 1.5);
        this.mainRT.setSize(w * this.pr, h * this.pr);
        this.post.setSize(w * this.pr, h * this.pr);
    }
    beginMenuRun() {
        this.state = (0, state_1.newRun)((Math.random() * 1e9) | 0);
        this.state.phase = 'menu';
        this.world.runSeed = this.state.seed;
        this.world.offset = 0;
        this.world.clearAll();
        this.dust.reset();
        this.player.reset();
        this.hud.showStart();
    }
    start() {
        this.audio.init();
        this.audio.ui();
        this.state = (0, state_1.newRun)((Math.random() * 1e9) | 0);
        this.world.runSeed = this.state.seed;
        this.world.offset = 0;
        this.world.clearAll();
        this.dust.reset();
        this.player.reset();
        this.state.px = 0;
        this.state.py = 12;
        this.phase = 'run';
        this.hud.hideOverlays();
        this.hud.show(true);
        this.audio.startEngine();
    }
    togglePause() {
        if (this.phase === 'run') {
            this.phase = 'pause';
            this.hud.showPause();
            this.audio.setEngine(0, false);
        }
        else if (this.phase === 'pause') {
            this.phase = 'run';
            this.hud.hideOverlays();
            this.last = performance.now();
        }
    }
    gameOver(reason) {
        const s = this.state;
        this.phase = 'over';
        s.reason = reason;
        this.audio.crash();
        this.audio.stopEngine();
        this.post.flash.r = 0.85;
        this.post.flash.g = 0.1;
        this.post.flash.b = 0.1;
        this.post.shakeAmp = 2.4;
        this.hud.showOver(s, reason);
    }
    hudMultPing() {
        const el = document.getElementById('mult');
        if (el) {
            el.classList.remove('pulse');
            void el.offsetWidth;
            el.classList.add('pulse');
        }
    }
}
const game = new Game();
window.__game = {
    get state() { return game.state; },
    get phase() { return game.phase; },
    get chunks() { return game.world.chunks.size; },
    get chunksFirst() { const m = game.world.chunks; let min = 999; for (const k of m.keys())
        min = Math.min(min, k); return min; },
    get world() { return game.world; },
    get fov() { return game.camera.fov; },
    start: () => game.start(),
};
window.addEventListener('error', (e) => {
    const el = document.getElementById('loadErr');
    if (el) {
        el.classList.remove('hidden');
        el.textContent = 'RUNTIME ERROR — ' + e.message;
    }
});
};

// ── types: globals.d.ts ──
__mods["globals.d.ts"] = function (exports) {};

// ── module: src/rng.ts ──
__mods["src/rng.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RNG = void 0;
// Seeded RNG (mulberry32) for deterministic procedural generation.
class RNG {
    constructor(seed) { this.s = seed >>> 0; }
    next() {
        this.s = (this.s + 0x6D2B79F5) >>> 0;
        let t = this.s;
        t = Math.imul(t ^ (t >>> 15), t | 1);
        t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
        return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
    }
    range(a, b) { return a + (b - a) * this.next(); }
    pick(arr) { return arr[Math.floor(this.next() * arr.length)]; }
}
exports.RNG = RNG;
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.diff = exports.PLAYER_Z = exports.CEIL = exports.FLOOR = exports.BOUND = exports.REACH = exports.CHUNK = void 0;
exports.newRun = newRun;
// Shared game state + tuning constants.
exports.CHUNK = 150; // length of one generated segment
exports.REACH = 8; // chunks kept alive ahead of the player
exports.BOUND = 42; // canyon half-width
exports.FLOOR = -9;
exports.CEIL = 30;
exports.PLAYER_Z = 0;
function newRun(seed) {
    return {
        phase: 'run', seed,
        score: 0, mult: 1, maxMult: 1,
        energy: 100, turbo: 0, boosting: false, boost: false,
        speed: 55, maxSpeed: 0, dist: 0,
        px: 0, py: 10, vx: 0, vy: 0, roll: 0,
        nearCd: 0, lowEnergyT: 0, shake: 0, time: 0,
        reason: '', nearFlash: 0, orbFlash: 0,
    };
}
// Difficulty scales with distance traveled.
exports.diff = {
    targetSpeed(d) { return 58 + Math.min(120, d * 0.006); },
    orbDensity(d) { return 1.0 + Math.min(0.8, d * 0.0002); },
    wallAmp(d) { return 1 + Math.min(0.7, d * 0.00012); },
};
};

// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Input = void 0;
// Keyboard + mouse steering input.
class Input {
    constructor(el) {
        this.keys = new Set();
        this.mouseX = 0; // -1..1 target steer
        this.onP = null;
        this.el = el;
        window.addEventListener('keydown', e => {
            this.keys.add(e.code);
            if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.code))
                e.preventDefault();
            if (e.code === 'KeyP')
                this.onP?.();
        });
        window.addEventListener('keyup', e => this.keys.delete(e.code));
        window.addEventListener('blur', () => this.keys.clear());
        el.addEventListener('mousemove', e => {
            const r = el.getBoundingClientRect();
            this.mouseX = Math.max(-1, Math.min(1, ((e.clientX - r.left) / r.width) * 2 - 1));
        });
        el.addEventListener('mouseleave', () => this.mouseX *= 0.5);
    }
    get steer() {
        let s = this.mouseX;
        if (this.keys.has('ArrowLeft') || this.keys.has('KeyA'))
            s = -1;
        if (this.keys.has('ArrowRight') || this.keys.has('KeyD'))
            s = 1;
        return s;
    }
    get pitch() {
        let p = 0;
        if (this.keys.has('ArrowUp') || this.keys.has('KeyW'))
            p += 1;
        if (this.keys.has('ArrowDown') || this.keys.has('KeyS'))
            p -= 1;
        return p;
    }
    get boost() {
        return this.keys.has('ShiftLeft') || this.keys.has('ShiftRight') || this.keys.has('Space');
    }
}
exports.Input = Input;
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioBus = void 0;
// Procedural sound: engine hum, orb ping, near-miss whoosh, crash, UI.
class AudioBus {
    constructor() {
        this.ctx = null;
        this.master = null;
        this.engineOsc = null;
        this.engineOsc2 = null;
        this.engineGain = null;
        this.engineFilter = null;
    }
    init() {
        if (this.ctx) {
            this.ctx.resume();
            return;
        }
        const AC = window.AudioContext || window.webkitAudioContext;
        if (!AC)
            return;
        this.ctx = new AC();
        this.master = this.ctx.createGain();
        this.master.gain.value = 0.5;
        this.master.connect(this.ctx.destination);
    }
    // continuous engine layer
    startEngine() {
        if (!this.ctx || !this.master || this.engineOsc)
            return;
        const c = this.ctx;
        this.engineGain = c.createGain();
        this.engineGain.gain.value = 0;
        this.engineFilter = c.createBiquadFilter();
        this.engineFilter.type = 'lowpass';
        this.engineFilter.frequency.value = 220;
        this.engineOsc = c.createOscillator();
        this.engineOsc.type = 'sawtooth';
        this.engineOsc.frequency.value = 42;
        this.engineOsc2 = c.createOscillator();
        this.engineOsc2.type = 'triangle';
        this.engineOsc2.frequency.value = 84.5;
        this.engineOsc.connect(this.engineFilter);
        this.engineOsc2.connect(this.engineFilter);
        this.engineFilter.connect(this.engineGain);
        this.engineGain.connect(this.master);
        this.engineOsc.start();
        this.engineOsc2.start();
    }
    // speed 0..1, boost bool
    setEngine(speedN, boosting) {
        if (!this.ctx || !this.engineGain || !this.engineOsc || !this.engineOsc2 || !this.engineFilter)
            return;
        const t = this.ctx.currentTime;
        const f = 38 + speedN * 70 + (boosting ? 26 : 0);
        this.engineOsc.frequency.setTargetAtTime(f, t, 0.12);
        this.engineOsc2.frequency.setTargetAtTime(f * 2.03, t, 0.12);
        this.engineFilter.frequency.setTargetAtTime(160 + speedN * 900 + (boosting ? 700 : 0), t, 0.15);
        this.engineGain.gain.setTargetAtTime(0.05 + speedN * 0.075 + (boosting ? 0.05 : 0), t, 0.1);
    }
    stopEngine() {
        if (!this.ctx)
            return;
        const t = this.ctx.currentTime;
        if (this.engineGain)
            this.engineGain.gain.setTargetAtTime(0, t, 0.15);
        const o = this.engineOsc, o2 = this.engineOsc2;
        if (o)
            o.stop(t + 0.6);
        if (o2)
            o2.stop(t + 0.6);
        this.engineOsc = this.engineOsc2 = null;
    }
    blip(freq, dur, type, vol, slide = 0) {
        if (!this.ctx || !this.master)
            return;
        const c = this.ctx, t = c.currentTime;
        const o = c.createOscillator(), g = c.createGain();
        o.type = type;
        o.frequency.value = freq;
        if (slide)
            o.frequency.exponentialRampToValueAtTime(Math.max(30, freq + slide), t + dur);
        g.gain.value = vol;
        g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
        o.connect(g);
        g.connect(this.master);
        o.start(t);
        o.stop(t + dur + 0.02);
    }
    orb() { this.blip(880, 0.22, 'sine', 0.22, 440); this.blip(1320, 0.3, 'sine', 0.12, 560); }
    // filtered-noise whoosh for near-miss
    whoosh(intensity) {
        if (!this.ctx || !this.master)
            return;
        const c = this.ctx, t = c.currentTime;
        const len = 0.35;
        const buf = c.createBuffer(1, c.sampleRate * len, c.sampleRate);
        const d = buf.getChannelData(0);
        for (let i = 0; i < d.length; i++)
            d[i] = (Math.random() * 2 - 1) * (1 - i / d.length);
        const src = c.createBufferSource();
        src.buffer = buf;
        const f = c.createBiquadFilter();
        f.type = 'bandpass';
        f.frequency.setValueAtTime(300, t);
        f.frequency.exponentialRampToValueAtTime(2400, t + len * 0.8);
        f.Q.value = 1.2;
        const g = c.createGain();
        g.gain.setValueAtTime(0.28 * intensity, t);
        g.gain.exponentialRampToValueAtTime(0.0001, t + len);
        src.connect(f);
        f.connect(g);
        g.connect(this.master);
        src.start(t);
    }
    crash() {
        if (!this.ctx || !this.master)
            return;
        const c = this.ctx, t = c.currentTime;
        const len = 0.9;
        const buf = c.createBuffer(1, c.sampleRate * len, c.sampleRate);
        const d = buf.getChannelData(0);
        for (let i = 0; i < d.length; i++)
            d[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / d.length, 1.6);
        const src = c.createBufferSource();
        src.buffer = buf;
        const f = c.createBiquadFilter();
        f.type = 'lowpass';
        f.frequency.setValueAtTime(1400, t);
        f.frequency.exponentialRampToValueAtTime(80, t + len);
        const g = c.createGain();
        g.gain.value = 0.5;
        src.connect(f);
        f.connect(g);
        g.connect(this.master);
        src.start(t);
        this.blip(120, 0.7, 'sawtooth', 0.25, -90);
    }
    ui() { this.blip(660, 0.12, 'square', 0.08, 220); }
    multUp() { this.blip(1180, 0.14, 'sine', 0.14, 300); }
    mute() {
        if (!this.ctx || !this.master)
            return false;
        const v = this.master.gain.value > 0.01 ? 0 : 0.5;
        this.master.gain.value = v;
        return v === 0;
    }
}
exports.AudioBus = AudioBus;
};

// ── module: src/world.ts ──
__mods["src/world.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.World = exports.PLAYER_R = void 0;
const rng_1 = require("./rng");
const state_1 = require("./state");
exports.PLAYER_R = 2.1;
// palettes
const C_ROCK_L = 0x9c4f2e, C_ROCK_R = 0x8a3d5a, C_MONO = 0x7a5480, C_BOLT = 0x472442, C_GLOW = 0xffb35c;
const COLS = 6;
const VSEG = 4;
class World {
    constructor(scene) {
        this.chunks = new Map();
        this.nextIndex = 0;
        this.runSeed = 1;
        // world scrolls: offset grows, chunk groups translate by offset.
        this.offset = 0;
        this.scene = scene;
        this.orbGeo = new THREE.IcosahedronGeometry(1.35, 0);
        this.orbMat = new THREE.MeshStandardMaterial({ color: 0x04222e, emissive: 0x59e6ff, emissiveIntensity: 2.4, roughness: 0.35 });
        this.boltGeo = new THREE.BoxGeometry(1, 1, 1);
        this.boltMat = new THREE.MeshStandardMaterial({ color: C_BOLT, flatShading: true, roughness: 0.9 });
        this.glowMat = new THREE.MeshStandardMaterial({ color: 0x140a06, emissive: C_GLOW, emissiveIntensity: 1.9, roughness: 0.5 });
    }
    seedFor(i) {
        let h = (i + 1) * 0x9E3779B1;
        h ^= this.runSeed;
        h = Math.imul(h ^ (h >>> 13), 0x5bd1e995);
        return (h ^ (h >>> 15)) >>> 0;
    }
    playerZ() { return -this.offset; } // player's local z
    genAhead(d) {
        const out = [];
        const limit = (this.offset + state_1.CHUNK * 2.5) / state_1.CHUNK; // chunks needed
        while (this.nextIndex < limit) {
            out.push(this.genChunk(this.nextIndex, d));
            this.nextIndex++;
        }
        return out;
    }
    genChunk(i, d) {
        const rng = new rng_1.RNG(this.seedFor(i));
        const z = -i * state_1.CHUNK;
        const prev = i === 0 ? { x: 0, y: 13, w: 52 } : (() => { const p = this.chunks.get(i - 1); return p ? { x: p.endX, y: p.endY, w: p.endW } : { x: 0, y: 13, w: 52 }; })();
        const sway = rng.range(2, 5), phase = rng.range(0, 6.28);
        const amp = rng.range(6, 13) * (1 + Math.min(0.6, d * 0.00012));
        const endX = Math.max(-30, Math.min(30, prev.x + rng.range(-22, 22)));
        const endY = Math.max(2, Math.min(state_1.CEIL - 14, prev.y + rng.range(-8, 8)));
        const endW = Math.max(20, prev.w + rng.range(-5, 1.5) - d * 0.0005);
        const step = state_1.CHUNK / COLS;
        const cols = [];
        for (let c = 0; c <= COLS; c++) {
            const t = c / COLS;
            const e = t * t * (3 - 2 * t); // smoothstep
            cols.push({
                x: prev.x + (endX - prev.x) * e + Math.sin(t * sway + phase) * amp * Math.sin(t * Math.PI) * 0.4,
                y: prev.y + (endY - prev.y) * e,
                w: prev.w + (endW - prev.w) * e,
                z: z - c * step,
            });
        }
        const chunk = { i, z, group: new THREE.Group(), cols, endX, endY, endW, monos: [], bolts: [], orbs: [], ownGeos: [], ownMats: [], done: false };
        this.buildWalls(chunk, rng, -1, C_ROCK_L);
        this.buildWalls(chunk, rng, 1, C_ROCK_R);
        this.buildMonos(chunk, rng, d);
        if (i > 2 && rng.next() < 0.24 + Math.min(0.22, d * 0.00005))
            this.buildSqueeze(chunk, rng);
        this.buildOrbs(chunk, rng);
        this.scene.add(chunk.group);
        this.chunks.set(i, chunk);
        return chunk;
    }
    buildWalls(chunk, rng, side, color) {
        const pos = [];
        const idx = [];
        for (let c = 0; c <= COLS; c++) {
            const col = chunk.cols[c];
            for (let v = 0; v <= VSEG; v++) {
                const t = v / VSEG;
                const off = 9 + t * (20 + rng.range(0, 9)) + rng.range(0, 3.5);
                const x = col.x + side * (col.w / 2 + off);
                const y = col.y + 7 - t * 56 - rng.range(0, 2);
                pos.push(x, y, col.z);
            }
        }
        for (let c = 0; c < COLS; c++) {
            for (let v = 0; v < VSEG; v++) {
                const a = c * (VSEG + 1) + v;
                const b = a + 1, cc = a + VSEG + 1, dd = cc + 1;
                if (side > 0)
                    idx.push(a, cc, dd, a, dd, b);
                else
                    idx.push(a, b, dd, a, dd, cc);
            }
        }
        const geo = new THREE.BufferGeometry();
        geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(pos), 3));
        geo.setIndex(idx);
        geo.computeVertexNormals();
        const mat = new THREE.MeshStandardMaterial({ color, flatShading: true, roughness: 0.95, metalness: 0.04 });
        const mesh = new THREE.Mesh(geo, mat);
        chunk.group.add(mesh);
        chunk.ownGeos.push(geo);
        chunk.ownMats.push(mat);
    }
    buildMonos(chunk, rng, d) {
        const chance = 0.55 + Math.min(0.35, d * 0.00004);
        const n = rng.next() < chance * 0.5 ? 2 : (rng.next() < chance ? 1 : 0);
        for (let k = 0; k < n; k++) {
            const side = rng.pick([-1, 1]);
            const x = chunk.endX + side * (rng.range(chunk.endW * 0.25, chunk.endW * 0.62));
            const z = chunk.z - rng.range(25, state_1.CHUNK - 25);
            const r = rng.range(3.2, 6.2);
            const h = rng.range(11, 30);
            const geo = new THREE.CylinderGeometry(r * 0.6, r, h, 5, 1);
            const mat = new THREE.MeshStandardMaterial({ color: C_MONO, flatShading: true, roughness: 0.85, metalness: 0.08 });
            const mesh = new THREE.Mesh(geo, mat);
            const y = state_1.FLOOR + 2 + h / 2 - 2;
            mesh.position.set(x, y, z);
            mesh.rotation.y = rng.range(0, Math.PI);
            mesh.userData.spin = (rng.next() < 0.5 ? -1 : 1) * rng.range(0.08, 0.3);
            mesh.userData.baseY = y;
            mesh.userData.t0 = rng.range(0, 10);
            chunk.group.add(mesh);
            chunk.monos.push({ x, z, r, h, y, mesh });
            chunk.ownGeos.push(geo);
            chunk.ownMats.push(mat);
        }
    }
    bolt(chunk, x, y, z, sx, sy, sz, glow) {
        const m = new THREE.Mesh(this.boltGeo, glow ? this.glowMat : this.boltMat);
        m.scale.set(sx, sy, sz);
        m.position.set(x, y, z);
        chunk.group.add(m);
        chunk.bolts.push({ x, y, z, hx: sx / 2, hy: sy / 2, hz: sz / 2 });
    }
    buildSqueeze(chunk, rng) {
        const c = chunk.cols[Math.floor(rng.range(2, COLS - 1))];
        const z = c.z - 4;
        const slotH = 5.6 + rng.range(-0.8, 1.4);
        const slotW = 8.2 + rng.range(-1.5, 1.5);
        const side = rng.pick([-1, 1]);
        const cy = c.y;
        const big = 60;
        this.bolt(chunk, c.x, cy + slotH / 2 + 13, z, big, 26, 9, false); // above slot
        this.bolt(chunk, c.x, cy - slotH / 2 - 15, z, big, 30, 9, false); // below slot
        this.bolt(chunk, c.x + side * (slotW / 2 + 16), cy, z, 32, slotH, 9, false);
        this.bolt(chunk, c.x - side * (slotW / 2 + 16), cy, z, 32, slotH, 9, false);
        // glowing slot frame
        this.bolt(chunk, c.x + side * slotW / 2, cy, z, 0.8, slotH + 0.8, 9.6, true);
        this.bolt(chunk, c.x + side * (slotW / 2 + 16) - side * slotW, cy, z, 0.8, slotH + 0.8, 9.6, true);
    }
    buildOrbs(chunk, rng) {
        const n = 3 + Math.floor(rng.next() * 3);
        for (let k = 0; k < n; k++) {
            const t = (k + 0.5) / n;
            const col = chunk.cols[Math.min(COLS, Math.floor(t * COLS))];
            const risky = rng.next() < 0.35;
            const x = risky ? col.x + rng.pick([-1, 1]) * (col.w / 2 - 4.5) : col.x + (rng.next() - 0.5) * col.w * 0.55;
            const y = col.y + (rng.next() - 0.5) * 9;
            const mesh = new THREE.Mesh(this.orbGeo, this.orbMat);
            mesh.position.set(x, y, col.z);
            chunk.group.add(mesh);
            chunk.orbs.push({ x, y, z: col.z, alive: true, mesh });
        }
    }
    // ---- per-frame ----
    update(dt, t, speed, d) {
        this.offset += speed * dt;
        this.genAhead(d);
        // translate + cull
        const pz = this.playerZ();
        for (const [i, c] of this.chunks) {
            c.group.position.z = this.offset;
            if (this.offset > i * state_1.CHUNK + state_1.CHUNK + 30)
                this.disposeChunk(i, c);
        }
        // animate monos + orbs
        const nearIdx = Math.floor(-pz / state_1.CHUNK);
        for (let i = nearIdx - 1; i <= nearIdx + 2; i++) {
            const c = this.chunks.get(i);
            if (!c)
                continue;
            for (const m of c.monos) {
                if (!m.mesh)
                    continue;
                m.mesh.rotation.y += m.mesh.userData.spin * dt;
                m.mesh.position.y = m.mesh.userData.baseY + Math.sin(t * 0.6 + m.mesh.userData.t0) * 1.4;
            }
            for (const o of c.orbs) {
                if (!o.alive || !o.mesh)
                    continue;
                o.mesh.rotation.y += dt * 1.8;
                o.mesh.rotation.x += dt * 0.7;
            }
        }
    }
    chunkAt() {
        return this.chunks.get(Math.floor(-this.playerZ() / state_1.CHUNK)) ?? null;
    }
    // wall clearance at z: returns min positive clearance to either wall (and col used)
    wallClearance(c, pz, px) {
        const f = (-pz - c.z) / state_1.CHUNK; // 0..1 within chunk
        const i = Math.max(0, Math.min(COLS, Math.round(f * COLS)));
        const col = c.cols[i];
        const clL = px - (col.x - col.w / 2);
        const clR = (col.x + col.w / 2) - px;
        return Math.min(clL, clR);
    }
    columnAt(c, pz) {
        const f = (-pz - c.z) / state_1.CHUNK;
        const i = Math.max(0, Math.min(COLS, Math.round(f * COLS)));
        return c.cols[i];
    }
    monoClearance(c, pz, px) {
        let best = 999;
        for (const m of c.monos) {
            const dz = Math.abs(pz - m.z);
            if (dz < m.r + 18) {
                const dc = Math.abs(px - m.x) - m.r;
                if (dc < best)
                    best = dc;
            }
        }
        return best;
    }
    disposeChunk(i, c) {
        if (c.done)
            return;
        c.done = true;
        this.scene.remove(c.group);
        for (const g of c.ownGeos)
            g.dispose();
        for (const m of c.ownMats)
            m.dispose();
        this.chunks.delete(i);
    }
    clearAll() {
        for (const [i, c] of this.chunks)
            this.disposeChunk(i, c);
        this.nextIndex = 0;
    }
}
exports.World = World;
};

// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Player = void 0;
const state_1 = require("./state");
const world_1 = require("./world");
class Player {
    constructor(scene) {
        this.group = new THREE.Group();
        this.dead = false;
        const mat = new THREE.MeshStandardMaterial({ color: 0xd8e6ff, flatShading: true, roughness: 0.35, metalness: 0.55, emissive: 0x243048, emissiveIntensity: 0.35 });
        // fuselage
        const body = new THREE.BoxGeometry(2.2, 1.1, 4.6);
        this.craft = new THREE.Mesh(body, mat);
        this.group.add(this.craft);
        // nose
        const noseGeo = new THREE.ConeGeometry(1.15, 2.6, 4);
        const nose = new THREE.Mesh(noseGeo, mat);
        nose.rotation.x = -Math.PI / 2;
        nose.position.z = -3.6;
        this.group.add(nose);
        // wings
        const wingGeo = new THREE.BoxGeometry(6.4, 0.34, 2.2);
        const wing = new THREE.Mesh(wingGeo, mat);
        wing.position.set(0, 0.1, 1.3);
        this.group.add(wing);
        const tailGeo = new THREE.BoxGeometry(2.6, 0.28, 1.1);
        const tail = new THREE.Mesh(tailGeo, mat);
        tail.position.set(0, 0.55, 1.9);
        this.group.add(tail);
        // engine glows
        this.boostMat = new THREE.MeshStandardMaterial({ color: 0x0a0602, emissive: 0xff8a2a, emissiveIntensity: 1.6, roughness: 0.5 });
        this.glowL = new THREE.Mesh(new THREE.ConeGeometry(0.5, 2.2, 6), this.boostMat);
        this.glowL.rotation.x = Math.PI / 2;
        this.glowL.position.set(-0.8, -0.1, 2.6);
        this.glowR = this.glowL.clone();
        this.glowR.position.x = 0.8;
        const boostMat2 = new THREE.MeshStandardMaterial({ color: 0x021014, emissive: 0x59e6ff, emissiveIntensity: 2.2, roughness: 0.4 });
        this.boostGlow = new THREE.Mesh(new THREE.ConeGeometry(0.9, 3.4, 6), boostMat2);
        this.boostGlow.rotation.x = Math.PI / 2;
        this.boostGlow.position.set(0, -0.1, 3.1);
        this.boostGlow.visible = false;
        this.group.add(this.glowL, this.glowR, this.boostGlow);
        scene.add(this.group);
    }
    reset() {
        this.dead = false;
        this.group.visible = true;
        this.group.position.set(0, 10, 0);
        this.group.rotation.set(0, 0, 0);
        this.boostGlow.visible = false;
    }
    // dt, s, input axis -> returns hit info or null
    update(dt, s, steer, pitch, world) {
        // speed
        const target = s.boosting ? 185 : 66;
        const rate = s.boosting ? 90 : 16;
        s.speed += (target - s.speed) * Math.min(1, dt * rate * 0.35);
        // flight feel: lateral + vertical acceleration
        const acc = 260;
        s.vx += steer * acc * dt;
        s.vy += pitch * acc * 0.92 * dt;
        s.vx *= Math.pow(0.16, dt);
        s.vy *= Math.pow(0.16, dt);
        s.px += s.vx * dt;
        s.py += s.vy * dt;
        // soft bounds
        if (s.py < state_1.FLOOR + 3) {
            s.py = state_1.FLOOR + 3;
            s.vy = Math.max(0, s.vy);
        }
        if (s.py > state_1.CEIL) {
            s.py = state_1.CEIL;
            s.vy = Math.min(0, s.vy);
        }
        // bank
        const targetRoll = steer * 0.85 + s.vx * 0.0015;
        s.roll += (targetRoll - s.roll) * Math.min(1, dt * 9);
        // visual
        this.group.position.set(s.px, s.py, 0);
        this.group.rotation.z = -s.roll;
        this.group.rotation.x = -pitch * 0.3 - s.vy * 0.0012;
        this.group.rotation.y = -s.roll * 0.45;
        // engine flicker
        const flick = 0.85 + Math.sin(s.time * 47) * 0.15 + (s.boosting ? 0.8 : 0);
        this.glowL.material.emissiveIntensity = flick;
        this.glowR.material.emissiveIntensity = flick;
        this.boostGlow.visible = s.boosting;
        if (s.boosting) {
            const sc = 1 + Math.sin(s.time * 30) * 0.2;
            this.boostGlow.scale.set(sc, sc, sc);
        }
        // ---- collisions ----
        const c = world.chunkAt();
        if (!c)
            return null;
        const pz = world.playerZ();
        // walls (soft bounce)
        const col = world.columnAt(c, pz);
        const half = col.w / 2 + world_1.PLAYER_R;
        if (s.px > col.x + half) {
            s.px = col.x + half;
            s.vx = -Math.abs(s.vx) * 0.35;
            return this.hit('SLAMMED A CANYON WALL');
        }
        if (s.px < col.x - half) {
            s.px = col.x - half;
            s.vx = Math.abs(s.vx) * 0.35;
            return this.hit('SLAMMED A CANYON WALL');
        }
        // ceiling/floor slab
        const slab = 60;
        if (s.py + world_1.PLAYER_R > col.y + slab)
            return this.hit('HIT THE CEILING');
        // monoliths
        for (const m of c.monos) {
            const dz = Math.abs(pz - m.z);
            if (dz < m.r + world_1.PLAYER_R + 2) {
                const dx = s.px - m.x;
                const dy = s.py - (m.y - m.h / 2 + 2);
                const r = m.r + world_1.PLAYER_R - 0.3;
                if (dx * dx + dy * dy < r * r)
                    return this.hit('SHATTERED A MONOLITH');
            }
        }
        // squeeze bolts
        for (const b of c.bolts) {
            const dx = s.px - b.x, dy = s.py - b.y;
            const dz = pz - b.z; // >0 means bolt ahead of player
            if (Math.abs(dx) < b.hx + world_1.PLAYER_R && Math.abs(dy) < b.hy + world_1.PLAYER_R && dz > -world_1.PLAYER_R && dz < b.hz + world_1.PLAYER_R) {
                return this.hit('CRUSHED IN A SQUEEZE');
            }
        }
        return null;
    }
    hit(reason) {
        this.dead = true;
        this.group.visible = false;
        return { reason };
    }
}
exports.Player = Player;
};

// ── module: src/post.ts ──
__mods["src/post.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostFX = void 0;
// Post pipeline: threshold -> blur (2 passes) -> brighten -> composite (shake, flash, vignette).
class PostFX {
    constructor(renderer) {
        this.renderer = renderer;
        this.w = 2;
        this.h = 2;
        this.flash = { r: 0, g: 0, b: 0 };
        this.shakeAmp = 0;
        this.shakeX = 0;
        this.shakeY = 0;
        this.scene = new THREE.Scene();
        this.cam = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
        this.mat = new THREE.ShaderMaterial({
            uniforms: {
                tDiffuse: { value: null }, uMode: { value: 0 }, uDir: { value: new THREE.Vector3(1, 0) },
                uStrength: { value: 1 }, uOffset: { value: new THREE.Vector3(0, 0, 0) },
                uShake: { value: 0 }, uFlash: { value: new THREE.Vector3(0, 0, 0) }, uTime: { value: 0 },
            },
            vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = vec4(position.xy, 0.0, 1.0); }`,
            fragmentShader: `
        precision highp float;
        uniform sampler2D tDiffuse;
        uniform int uMode;
        uniform vec2 uDir;
        uniform float uStrength;
        uniform vec3 uOffset;
        uniform float uShake;
        uniform vec3 uFlash;
        uniform float uTime;
        varying vec2 vUv;
        void main(){
          vec2 uv = vUv;
          vec3 col;
          if (uMode == 0) {
            vec4 s = texture2D(tDiffuse, uv);
            col = max(s.rgb - 0.52, 0.0) * uStrength;
          } else if (uMode == 1 || uMode == 2) {
            vec3 acc = vec3(0.0);
            float wsum = 0.0;
            for (int i = 0; i < 8; i++) {
              float fi = float(i);
              float w = 1.0 - fi / 8.0;
              vec2 off = uDir * (fi + 1.0) * uStrength;
              acc += texture2D(tDiffuse, uv + off).rgb * w;
              acc += texture2D(tDiffuse, uv - off).rgb * w;
              wsum += 2.0 * w;
            }
            col = acc / wsum;
          } else {
            vec4 s = texture2D(tDiffuse, uv);
            vec3 blur = texture2D(tDiffuse, uv + uOffset.xy).rgb;
            col = max(s.rgb, blur * 1.15);
            col += uFlash;
            float d = distance(uv, vec2(0.5));
            col *= 1.0 - smoothstep(0.45, 0.95, d) * 0.55;
          }
          col += uShake * 0.012;
          gl_FragColor = vec4(col, 1.0);
        }`,
        });
        const geo = new THREE.BufferGeometry();
        const p = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]);
        const uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
        geo.setAttribute('position', new THREE.BufferAttribute(p, 3));
        geo.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
        geo.setIndex([0, 1, 2, 0, 2, 3]);
        this.quad = new THREE.Mesh(geo, this.mat);
        this.scene.add(this.quad);
        const opts = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, depthBuffer: false };
        this.r1 = new THREE.WebGLRenderTarget(2, 2, opts);
        this.r2 = new THREE.WebGLRenderTarget(2, 2, opts);
        this.r3 = new THREE.WebGLRenderTarget(2, 2, opts);
    }
    setSize(w, h) {
        // full-res threshold downsampled; blur at quarter; composite full
        const bw = Math.max(2, Math.floor(w / 2));
        const bh = Math.max(2, Math.floor(h / 2));
        const qw = Math.max(2, Math.floor(w / 4));
        const qh = Math.max(2, Math.floor(h / 4));
        this.w = bw;
        this.h = bh;
        this.r1.setSize(bw, bh);
        this.r2.setSize(qw, qh);
        this.r3.setSize(qw, qh);
    }
    renderTo(t, from, mode) {
        const u = this.mat.uniforms;
        u.tDiffuse.value = from.texture;
        u.uMode.value = mode;
        // blur taps in target's pixel units (target may be smaller than texture)
        if (mode === 1) {
            u.uDir.value.set(2 / t.width, 0);
        }
        else if (mode === 2) {
            u.uDir.value.set(0, 2 / t.height);
        }
        this.renderer.setRenderTarget(t);
        this.renderer.render(this.scene, this.cam);
        this.renderer.setRenderTarget(null);
    }
    render(scene, camera, mainRT) {
        const u = this.mat.uniforms;
        // 1. threshold from main buffer
        this.renderTo(this.r1, mainRT, 0);
        // 2. separable blur -> r3
        this.renderTo(this.r2, this.r1, 1);
        this.renderTo(this.r3, this.r2, 2);
        // 3. composite to screen with shake offset
        const sx = (Math.random() * 2 - 1) * this.shakeAmp;
        const sy = (Math.random() * 2 - 1) * this.shakeAmp;
        this.shakeX = sx;
        this.shakeY = sy;
        u.uMode.value = 3;
        u.tDiffuse.value = mainRT.texture;
        u.uOffset.value.set(sx / mainRT.width, -sy / mainRT.height, 0);
        u.uShake.value = this.shakeAmp;
        u.uFlash.value.set(this.flash.r, this.flash.g, this.flash.b);
        u.uTime.value = 0;
        this.renderer.setRenderTarget(null);
        this.renderer.render(scene, camera);
        // decay
        const d = 0.9;
        this.flash.r *= d;
        this.flash.g *= d;
        this.flash.b *= d;
        this.shakeAmp *= 0.92;
    }
}
exports.PostFX = PostFX;
};

// ── module: src/particles.ts ──
__mods["src/particles.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Particles = void 0;
// Engine trail + speed-dust particles (two Points systems, ring buffers).
class Particles {
    constructor(scene) {
        this.N = 480;
        this.idx = 0;
        this.D = 500;
        this.didx = 0;
        this.trailVx = new Float32Array(this.N);
        this.trailVy = new Float32Array(this.N);
        this.dspd = new Float32Array(this.D);
        this.scene = scene;
        // trail
        this.pos = new Float32Array(this.N * 3).fill(9999);
        this.col = new Float32Array(this.N * 3);
        this.life = new Float32Array(this.N);
        this.maxLife = new Float32Array(this.N).fill(1);
        this.geo = new THREE.BufferGeometry();
        this.geo.setAttribute('position', new THREE.BufferAttribute(this.pos, 3));
        this.geo.setAttribute('color', new THREE.BufferAttribute(this.col, 3));
        const mat = new THREE.PointsMaterial({ size: 0.9, vertexColors: true, transparent: true, opacity: 0.9, blending: THREE.AdditiveBlending, depthWrite: false, sizeAttenuation: true });
        this.pts = new THREE.Points(this.geo, mat);
        this.pts.frustumCulled = false;
        scene.add(this.pts);
        // dust
        this.dpos = new Float32Array(this.D * 3);
        this.dlife = new Float32Array(this.D);
        for (let i = 0; i < this.D; i++)
            this.dpos[i * 3 + 1] = 9999;
        this.dgeo = new THREE.BufferGeometry();
        this.dgeo.setAttribute('position', new THREE.BufferAttribute(this.dpos, 3));
        const dmat = new THREE.PointsMaterial({ size: 0.45, color: 0xffc890, transparent: true, opacity: 0.5, blending: THREE.AdditiveBlending, depthWrite: false, sizeAttenuation: true });
        this.dpts = new THREE.Points(this.dgeo, dmat);
        this.dpts.frustumCulled = false;
        scene.add(this.dpts);
    }
    reset() {
        this.life.fill(0);
        this.dlife.fill(0);
        this.pos.fill(9999);
        for (let i = 0; i < this.D; i++)
            this.dpos[i * 3 + 1] = 9999;
        this.geo.attributes.position.needsUpdate = true;
        this.dgeo.attributes.position.needsUpdate = true;
    }
    // p: world coords behind craft; boost intensity 0..1
    spawn(px, py, pz, vx, vy, boost) {
        const i = this.idx;
        this.idx = (this.idx + 1) % this.N;
        this.pos[i * 3] = px + (Math.random() - 0.5) * 1.6;
        this.pos[i * 3 + 1] = py + (Math.random() - 0.5) * 1.2;
        this.pos[i * 3 + 2] = pz + 2.2;
        this.life[i] = 1;
        this.maxLife[i] = boost ? 0.7 : 0.45;
        const heat = boost ? 1 : 0.6;
        this.col[i * 3] = 1.0 * heat + (Math.random() - 0.5) * 0.2;
        this.col[i * 3 + 1] = (boost ? 0.75 : 0.42) * heat;
        this.col[i * 3 + 2] = boost ? 0.55 : 0.18;
        this.trailVx[i] = vx * 0.1;
        this.trailVy[i] = vy * 0.1;
    }
    spawnDust(px, py, pz, speed) {
        const i = this.didx;
        this.didx = (this.didx + 1) % this.D;
        this.dpos[i * 3] = px + (Math.random() - 0.5) * 60;
        this.dpos[i * 3 + 1] = py + (Math.random() - 0.5) * 40;
        this.dpos[i * 3 + 2] = pz - 20 - Math.random() * 60;
        this.dlife[i] = 1;
        this.dspd[i] = speed * (0.8 + Math.random() * 0.5);
    }
    update(dt, pz) {
        // trail: move in player space (world z moves with player since scene doesn't translate; we shift each particle toward player)
        for (let i = 0; i < this.N; i++) {
            if (this.life[i] <= 0)
                continue;
            this.life[i] -= dt / this.maxLife[i];
            if (this.life[i] <= 0) {
                this.pos[i * 3 + 1] = 9999;
                continue;
            }
            this.pos[i * 3] += this.trailVx[i] * dt;
            this.pos[i * 3 + 1] += this.trailVy[i] * dt;
            // push behind relative to player z
            this.pos[i * 3 + 2] += dt * 14;
        }
        for (let i = 0; i < this.D; i++) {
            if (this.dlife[i] <= 0)
                continue;
            this.dlife[i] -= dt * 1.4;
            if (this.dlife[i] <= 0) {
                this.dpos[i * 3 + 1] = 9999;
                continue;
            }
            this.dpos[i * 3 + 2] += this.dspd[i] * dt;
        }
        this.geo.attributes.position.needsUpdate = true;
        this.geo.attributes.color.needsUpdate = true;
        this.dgeo.attributes.position.needsUpdate = true;
    }
}
exports.Particles = Particles;
};

// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HUD = void 0;
// Minimal DOM HUD updates (cheap: only touch what changed).
class HUD {
    constructor() {
        this.lastScore = -1;
        this.lastMult = -1;
        this.lastSpeed = -1;
        this.score = document.getElementById('score');
        this.dist = document.getElementById('dist');
        this.mult = document.getElementById('mult');
        this.speed = document.getElementById('speed');
        this.speedBar = document.getElementById('speedBar');
        this.energy = document.getElementById('energyFill');
        this.energyWrap = document.getElementById('energyWrap');
        this.turbo = document.getElementById('turboFill');
        this.boostHint = document.getElementById('boostHint');
        this.flash = document.getElementById('flash');
        this.flashCyan = document.getElementById('flashCyan');
        this.hud = document.getElementById('hud');
    }
    show(run) { this.hud.classList.toggle('on', run); }
    update(s, nearActive) {
        const sc = Math.floor(s.score);
        if (sc !== this.lastScore) {
            this.score.textContent = sc.toLocaleString();
            this.lastScore = sc;
        }
        this.dist.textContent = Math.floor(s.dist).toLocaleString() + ' m';
        const m = Math.floor(s.mult);
        if (m !== this.lastMult) {
            this.mult.textContent = '×' + m;
            this.lastMult = m;
            this.mult.classList.remove('pulse');
            void this.mult.offsetWidth;
            this.mult.classList.add('pulse');
        }
        this.mult.classList.toggle('near', nearActive);
        const sp = Math.round(s.speed * 2.2);
        if (sp !== this.lastSpeed) {
            this.speed.textContent = String(sp);
            this.lastSpeed = sp;
        }
        this.speedBar.style.width = Math.min(100, (s.speed / 185) * 100).toFixed(1) + '%';
        this.energy.style.width = Math.max(0, s.energy).toFixed(1) + '%';
        this.energyWrap.classList.toggle('low', s.energy < 30);
        this.turbo.style.width = s.turbo.toFixed(1) + '%';
        this.boostHint.classList.toggle('on', s.turbo >= 100 && !s.boosting);
        if (s.nearFlash > 0) {
            this.flash.style.opacity = (s.nearFlash * 0.22).toFixed(3);
            s.nearFlash *= 0.86;
            if (s.nearFlash < 0.02)
                s.nearFlash = 0;
        }
        else if (this.flash.style.opacity !== '0') {
            this.flash.style.opacity = '0';
        }
        if (s.orbFlash > 0) {
            this.flashCyan.style.opacity = (s.orbFlash * 0.14).toFixed(3);
            s.orbFlash *= 0.88;
            if (s.orbFlash < 0.02)
                s.orbFlash = 0;
        }
        else if (this.flashCyan.style.opacity !== '0') {
            this.flashCyan.style.opacity = '0';
        }
    }
    q(id) {
        const e = document.getElementById(id);
        if (!e)
            throw new Error('missing #' + id);
        return e;
    }
    showOver(s, reason) {
        this.q('scrStart').classList.add('hidden');
        this.q('scrOver').classList.remove('hidden');
        this.q('scrPause').classList.add('hidden');
        this.q('overlay').classList.remove('off');
        this.q('finScore').textContent = Math.floor(s.score).toLocaleString();
        this.q('finSpeed').textContent = String(Math.round(s.maxSpeed * 2.2));
        this.q('finDist').textContent = Math.floor(s.dist).toLocaleString() + ' m';
        this.q('finMult').textContent = '×' + Math.floor(s.maxMult);
        this.q('overReason').textContent = reason;
    }
    showStart() {
        this.q('scrStart').classList.remove('hidden');
        this.q('scrOver').classList.add('hidden');
        this.q('scrPause').classList.add('hidden');
        this.q('overlay').classList.remove('off');
    }
    showPause() {
        this.q('scrPause').classList.remove('hidden');
        this.q('scrOver').classList.add('hidden');
        this.q('overlay').classList.remove('off');
    }
    hideOverlays() { this.q('overlay').classList.add('off'); }
}
exports.HUD = HUD;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.8","provider":"brigid","persona":"typescript","sessionId":"df927e95-13a0-4524-8fce-2258ffc871e0","tokensIn":3891873,"tokensOut":133923,"tokensTotal":4025796,"cost":0,"turns":79,"toolCalls":112,"failedToolCalls":0,"timestamp":"2026-09-09T06:10:09.330Z"} -->