Canyon Run
Swift Qwen3.8 27B GGUF · 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 41,026 bytes · SHA-256 9bc9b8962fdb
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SUNDRIFT — High-Speed Procedural Canyon 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&display=swap" rel="stylesheet">
<style>
:root {
--orange: #ff9a3c; --orange-soft: #ffd9a8; --purple: #7a5aa8; --silver: #c8cfdd;
--bg: #0a0514;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; overflow: hidden; background: var(--bg);
font-family: 'Rajdhani', 'Segoe UI', sans-serif; color: #e8ecf5; user-select: none; -webkit-user-select: none; }
canvas { position: fixed; inset: 0; display: block; z-index: 0; }
#speedfx { position: fixed; inset: 0; z-index: 1; pointer-events: none; opacity: 0;
background: radial-gradient(ellipse at center, transparent 42%, rgba(255,110,50,0.30) 100%); }
/* ============ HUD ============ */
#hud { position: fixed; inset: 0; z-index: 2; pointer-events: none;
display: flex; flex-direction: column; justify-content: space-between; padding: 26px 34px; }
.hidden { display: none !important; }
.label { font-size: 11px; font-weight: 600; letter-spacing: 4px; color: rgba(232,236,245,0.45); }
.hud-top { display: flex; justify-content: space-between; align-items: flex-start; }
#score { font-size: 34px; font-weight: 700; letter-spacing: 2px; line-height: 1.05;
text-shadow: 0 0 18px rgba(255,154,60,0.35); }
#mult-wrap { text-align: right; }
#mult { font-size: 74px; font-weight: 700; line-height: 1; color: var(--orange);
text-shadow: 0 0 26px rgba(255,138,42,0.55); display: inline-block; }
#mult.pulse { animation: multPulse 0.35s ease-out; }
@keyframes multPulse { 0% { transform: scale(1); } 35% { transform: scale(1.3); color: #fff; } 100% { transform: scale(1); } }
#nearmiss { position: fixed; top: 26%; left: 50%; transform: translate(-50%, 0); z-index: 2;
pointer-events: none; font-size: 20px; font-weight: 700; letter-spacing: 10px;
color: var(--orange-soft); opacity: 0; text-shadow: 0 0 20px rgba(255,154,60,0.8); }
#nearmiss.flash { animation: nmFlash 0.7s ease-out; }
@keyframes nmFlash {
0% { opacity: 0; transform: translate(-50%, 6px) scale(0.92); }
18% { opacity: 1; transform: translate(-50%, 0) scale(1.04); }
75% { opacity: 0.85; }
100% { opacity: 0; transform: translate(-50%, -8px) scale(1.08); } }
.hud-bottom { display: flex; justify-content: space-between; align-items: flex-end; gap: 40px; }
#speed-wrap { display: flex; flex-direction: column; gap: 6px; }
#speedbar { width: 190px; height: 2px; background: rgba(232,236,245,0.18); }
#speedfill { height: 100%; width: 0%; background: var(--orange); box-shadow: 0 0 10px rgba(255,154,60,0.8); }
#speedval { font-size: 24px; font-weight: 700; letter-spacing: 1px; }
#speedval small { font-size: 11px; letter-spacing: 3px; color: rgba(232,236,245,0.45); font-weight: 600; }
#bars { display: flex; flex-direction: column; gap: 12px; align-items: flex-end; }
.bar-row { display: flex; flex-direction: column; gap: 5px; align-items: flex-end; }
#energybar { width: 280px; height: 7px; background: rgba(232,236,245,0.12); }
#energyfill { height: 100%; width: 100%; background: linear-gradient(90deg, #ff6a2a, #ffd9a8);
box-shadow: 0 0 14px rgba(255,154,60,0.7); }
#energybar.low #energyfill { background: linear-gradient(90deg, #ff2d20, #ff7a3c); animation: lowPulse 0.7s infinite; }
@keyframes lowPulse { 50% { opacity: 0.45; } }
#turbobar { width: 280px; height: 3px; background: rgba(232,236,245,0.12); }
#turbofill { height: 100%; width: 0%; background: #a8b8e8; }
#turbo-wrap.ready #turbofill { background: #dfe8ff; box-shadow: 0 0 12px #a8b8e8; }
#boostkey { opacity: 0; transition: opacity 0.25s; color: #cdd8f5; }
#turbo-wrap.ready #boostkey { opacity: 1; }
/* ============ Overlays ============ */
.overlay { position: fixed; inset: 0; z-index: 3; display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 18px; text-align: center;
background: radial-gradient(ellipse at center, rgba(10,5,20,0.25) 0%, rgba(10,5,20,0.72) 100%); }
#menu { cursor: pointer; }
.title { font-size: clamp(56px, 9vw, 96px); font-weight: 700; letter-spacing: 18px; padding-left: 18px;
background: linear-gradient(180deg, #fff2e0 10%, #ff9a3c 90%);
-webkit-background-clip: text; background-clip: text; color: transparent;
filter: drop-shadow(0 0 28px rgba(255,138,42,0.45)); }
.subtitle { font-size: 14px; font-weight: 600; letter-spacing: 7px; color: rgba(232,236,245,0.6); }
.controls { display: flex; gap: 34px; margin-top: 26px; flex-wrap: wrap; justify-content: center; }
.ctl { display: flex; flex-direction: column; gap: 8px; align-items: center; }
kbd { font-family: inherit; font-size: 13px; font-weight: 700; letter-spacing: 2px; color: var(--orange-soft);
border: 1px solid rgba(255,154,60,0.55); border-radius: 4px; padding: 5px 12px;
background: rgba(255,154,60,0.08); box-shadow: 0 0 12px rgba(255,138,42,0.18); }
.start-hint { margin-top: 34px; font-size: 15px; font-weight: 700; letter-spacing: 6px;
color: var(--orange); animation: hintPulse 1.6s ease-in-out infinite; }
@keyframes hintPulse { 50% { opacity: 0.35; } }
#go-reason { font-size: 13px; font-weight: 700; letter-spacing: 8px; color: #ff6a5a; }
.go-title { font-size: 44px; font-weight: 700; letter-spacing: 12px; padding-left: 12px;
color: #f2f5fb; text-shadow: 0 0 24px rgba(255,154,60,0.35); }
.stats { display: flex; flex-direction: column; gap: 10px; margin-top: 18px; min-width: 320px; }
.stat { display: flex; justify-content: space-between; border-bottom: 1px solid rgba(232,236,245,0.15); padding-bottom: 8px; }
.stat span { font-size: 12px; font-weight: 600; letter-spacing: 4px; color: rgba(232,236,245,0.5); }
.stat b { font-size: 22px; font-weight: 700; letter-spacing: 1px; color: var(--orange-soft); }
#restart { pointer-events: auto; margin-top: 30px; font-family: inherit; font-size: 16px; font-weight: 700;
letter-spacing: 6px; color: var(--orange-soft); background: transparent; border: 1px solid var(--orange);
padding: 13px 42px; cursor: pointer; transition: all 0.2s ease; }
#restart:hover { background: var(--orange); color: #1a0b06; box-shadow: 0 0 30px rgba(255,138,42,0.5); }
.hint { font-size: 11px; letter-spacing: 4px; color: rgba(232,236,245,0.35); }
</style>
</head>
<body>
<div id="speedfx"></div>
<div id="hud" class="hidden">
<div class="hud-top">
<div>
<div class="label">SCORE</div>
<div id="score">0</div>
</div>
<div id="mult-wrap">
<div id="mult">×1</div>
<div class="label" style="text-align:right">NEAR-MISS MULT</div>
</div>
</div>
<div class="hud-bottom">
<div id="speed-wrap">
<div class="label">VELOCITY</div>
<div id="speedbar"><div id="speedfill"></div></div>
<div id="speedval">0 <small>KM/H</small></div>
</div>
<div id="bars">
<div class="bar-row">
<div class="label">ENERGY</div>
<div id="energybar"><div id="energyfill"></div></div>
</div>
<div class="bar-row" id="turbo-wrap">
<div class="label">TURBO <span id="boostkey">SPACE ▸</span></div>
<div id="turbobar"><div id="turbofill"></div></div>
</div>
</div>
</div>
</div>
<div id="nearmiss">NEAR MISS</div>
<div id="menu" class="overlay">
<div class="title">SUNDRIFT</div>
<div class="subtitle">HIGH-SPEED PROCEDURAL CANYON RACER</div>
<div class="controls">
<div class="ctl"><kbd>A D</kbd><div class="label">BANK / STEER</div></div>
<div class="ctl"><kbd>W S</kbd><div class="label">PITCH / THROTTLE</div></div>
<div class="ctl"><kbd>SPACE</kbd><div class="label">TURBO BOOST</div></div>
</div>
<div class="start-hint">CLICK OR PRESS ENTER TO LAUNCH</div>
<div class="hint">SKIM THE WALLS FOR MULTIPLIERS · COLLECT ORBS TO STAY FUELED</div>
</div>
<div id="gameover" class="overlay hidden">
<div id="go-reason">WALL IMPACT</div>
<div class="go-title">FLIGHT TERMINATED</div>
<div class="stats">
<div class="stat"><span>FINAL SCORE</span><b id="go-score">0</b></div>
<div class="stat"><span>MAX SPEED</span><b id="go-speed">0</b></div>
<div class="stat"><span>BEST MULTIPLIER</span><b id="go-mult">×1</b></div>
<div class="stat"><span>DISTANCE</span><b id="go-dist">0</b></div>
</div>
<button id="restart">RESTART</button>
<div class="hint">OR PRESS ENTER</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { AfterimagePass } from 'three/addons/postprocessing/AfterimagePass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
/* ============================== helpers ============================== */
let seed = Math.random() * 1000;
const hash1 = n => { const s = Math.sin(n * 127.1 + seed * 0.373) * 43758.5453; return s - Math.floor(s); };
const damp = (a, b, l, dt) => b + (a - b) * Math.exp(-l * dt);
const clamp = THREE.MathUtils.clamp;
/* ============================== constants ============================== */
const CHUNK_LEN = 220;
const AHEAD_CHUNKS = 7;
const PLAYER_R = 1.6;
const MAXV = 210;
const sunDir = new THREE.Vector3(0.42, 0.13, 0.89).normalize();
/* ============================== renderer / scene ============================== */
const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: 'high-performance' });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.75));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.15;
document.body.prepend(renderer.domElement);
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x2a1440, 0.0011);
const camera = new THREE.PerspectiveCamera(68, window.innerWidth / window.innerHeight, 0.1, 2500);
camera.position.set(0, 12, -14);
scene.add(new THREE.HemisphereLight(0x7a55a5, 0x4a2438, 0.9));
const sun = new THREE.DirectionalLight(0xffa050, 2.6);
sun.castShadow = true;
sun.shadow.mapSize.set(2048, 2048);
Object.assign(sun.shadow.camera, { left: -150, right: 150, top: 150, bottom: -150, near: 20, far: 800 });
sun.shadow.camera.updateProjectionMatrix();
sun.shadow.bias = -0.0004;
scene.add(sun, sun.target);
const fillLight = new THREE.DirectionalLight(0x9a6ab0, 0.55);
const fillDir = new THREE.Vector3(-0.55, 0.25, -0.4).normalize();
scene.add(fillLight, fillLight.target);
/* ---- cyber-sunset skydome ---- */
const skyMat = new THREE.ShaderMaterial({
side: THREE.BackSide, depthWrite: false, fog: false,
uniforms: {
uTop: { value: new THREE.Color(0x140b2e) },
uHorizon: { value: new THREE.Color(0xff7038) },
uSunDir: { value: sunDir }
},
vertexShader: `
varying vec3 vDir;
void main() {
vDir = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`,
fragmentShader: `
varying vec3 vDir;
uniform vec3 uTop; uniform vec3 uHorizon; uniform vec3 uSunDir;
void main() {
vec3 d = normalize(vDir);
float h = clamp(d.y * 0.5 + 0.5, 0.0, 1.0);
vec3 col = mix(uHorizon, uTop, pow(h, 0.55));
float sd = max(dot(d, uSunDir), 0.0);
col += vec3(1.0, 0.50, 0.22) * pow(sd, 5.0) * 0.22;
col += vec3(1.0, 0.72, 0.42) * smoothstep(0.99975, 0.99996, sd) * 1.8;
gl_FragColor = vec4(col, 1.0);
}`
});
const sky = new THREE.Mesh(new THREE.SphereGeometry(1600, 24, 16), skyMat);
sky.frustumCulled = false;
scene.add(sky);
/* ---- canyon floor ---- */
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(20000, 20000),
new THREE.MeshStandardMaterial({ color: 0x150a24, roughness: 1, metalness: 0 })
);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -46;
ground.receiveShadow = true;
scene.add(ground);
/* ============================== player craft ============================== */
const craft = new THREE.Group();
let flame, engineMat;
{
const bodyMat = new THREE.MeshStandardMaterial({ color: 0xd8dee9, roughness: 0.35, metalness: 0.55, flatShading: true });
const wingMat = new THREE.MeshStandardMaterial({ color: 0x9aa2b8, roughness: 0.5, metalness: 0.4, flatShading: true });
const accentMat = new THREE.MeshStandardMaterial({ color: 0x2a1a3e, emissive: 0xff6a2a, emissiveIntensity: 1.4, roughness: 0.6, flatShading: true });
engineMat = new THREE.MeshStandardMaterial({ color: 0x110805, emissive: 0xffa040, emissiveIntensity: 2.2, roughness: 0.5 });
const nose = new THREE.Mesh(new THREE.ConeGeometry(0.9, 3.4, 5), bodyMat);
nose.rotation.x = Math.PI / 2; nose.position.z = 1.9;
const body = new THREE.Mesh(new THREE.BoxGeometry(1.3, 0.75, 3.4), bodyMat);
body.position.z = -0.2;
const wing = new THREE.Mesh(new THREE.BoxGeometry(4.8, 0.14, 1.5), wingMat);
wing.position.set(0, -0.15, -0.9);
const tipL = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.22, 0.9), accentMat);
tipL.position.set(-2.3, -0.15, -0.9);
const tipR = tipL.clone(); tipR.position.x = 2.3;
const fin = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.95, 1.3), wingMat);
fin.position.set(0, 0.6, -1.1);
const canopy = new THREE.Mesh(new THREE.OctahedronGeometry(0.42), accentMat);
canopy.position.set(0, 0.52, 0.7);
flame = new THREE.Mesh(new THREE.ConeGeometry(0.5, 1.6, 5), engineMat);
flame.rotation.x = -Math.PI / 2; flame.position.set(0, -0.05, -2.7);
craft.add(nose, body, wing, tipL, tipR, fin, canopy, flame);
craft.traverse(o => { if (o.isMesh) o.castShadow = true; });
scene.add(craft);
}
/* ============================== particles ============================== */
const P_MAX = 900;
const pGeo = new THREE.BufferGeometry();
pGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(P_MAX * 3), 3));
pGeo.setAttribute('aColor', new THREE.BufferAttribute(new Float32Array(P_MAX * 3), 3));
pGeo.setAttribute('aSize', new THREE.BufferAttribute(new Float32Array(P_MAX), 1));
pGeo.setAttribute('aAlpha', new THREE.BufferAttribute(new Float32Array(P_MAX), 1));
const pPos = pGeo.attributes.position.array;
const pCol = pGeo.attributes.aColor.array;
const pSize = pGeo.attributes.aSize.array;
const pAlpha = pGeo.attributes.aAlpha.array;
for (let i = 0; i < P_MAX; i++) pPos[i * 3 + 1] = -99999;
const pMat = new THREE.ShaderMaterial({
transparent: true, depthWrite: false, blending: THREE.AdditiveBlending,
vertexShader: `
attribute vec3 aColor; attribute float aSize; attribute float aAlpha;
varying vec3 vColor; varying float vAlpha;
void main() {
vColor = aColor; vAlpha = aAlpha;
vec4 mv = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = aSize * (160.0 / -mv.z);
gl_Position = projectionMatrix * mv;
}`,
fragmentShader: `
varying vec3 vColor; varying float vAlpha;
void main() {
float d = length(gl_PointCoord - 0.5);
float a = smoothstep(0.5, 0.06, d) * vAlpha;
if (a < 0.01) discard;
gl_FragColor = vec4(vColor, a);
}`
});
const points = new THREE.Points(pGeo, pMat);
points.frustumCulled = false;
scene.add(points);
const P = new Array(P_MAX).fill(null);
function spawnP(x, y, z, vx, vy, vz, life, size, r, g, b, drag, grav) {
for (let i = 0; i < P_MAX; i++) {
if (!P[i]) {
P[i] = { x, y, z, vx, vy, vz, life, maxLife: life, size, drag, grav };
pCol[i * 3] = r; pCol[i * 3 + 1] = g; pCol[i * 3 + 2] = b;
return;
}
}
}
function updateParticles(dt) {
for (let i = 0; i < P_MAX; i++) {
const p = P[i];
if (!p) continue;
p.life -= dt;
if (p.life <= 0) { P[i] = null; pAlpha[i] = 0; pPos[i * 3 + 1] = -99999; continue; }
const dr = Math.exp(-p.drag * dt);
p.vx *= dr; p.vz *= dr; p.vy = p.vy * dr - p.grav * dt;
p.x += p.vx * dt; p.y += p.vy * dt; p.z += p.vz * dt;
const lt = p.life / p.maxLife;
pPos[i * 3] = p.x; pPos[i * 3 + 1] = p.y; pPos[i * 3 + 2] = p.z;
pAlpha[i] = lt * lt;
pSize[i] = p.size * (0.4 + 0.6 * lt);
}
pGeo.attributes.position.needsUpdate = true;
pGeo.attributes.aAlpha.needsUpdate = true;
pGeo.attributes.aSize.needsUpdate = true;
pGeo.attributes.aColor.needsUpdate = true;
}
/* ============================== procedural chunks ============================== */
const wallMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.95, metalness: 0.05, flatShading: true, vertexColors: true });
const monolithMat = new THREE.MeshStandardMaterial({ color: 0x6a5a8a, roughness: 0.8, metalness: 0.15, flatShading: true });
const edgeMat = new THREE.LineBasicMaterial({ color: 0xff7a3c, transparent: true, opacity: 0.55 });
const orbMat = new THREE.MeshStandardMaterial({ color: 0xffc060, emissive: 0xffa030, emissiveIntensity: 2.6, roughness: 0.4 });
const chunks = [];
let chunkIdx = 0;
let lastSqueezeZ = -9999;
let squeezeZ = -1, squeezeOffset = 0;
function pathX(z) {
const a = 0.45 + 0.55 * clamp(z / 9000, 0, 1);
return (Math.sin(z * 0.004) * 78 + Math.sin(z * 0.0013 + 2.0) * 46) * a;
}
function wallHalf(z, side) {
let half = 62 + 20 * Math.sin(z * 0.005 + side * 2.5) + 14 * Math.sin(z * 0.011 + side * 1.7);
if (squeezeZ > 0) {
const dz = Math.abs(z - squeezeZ);
if (dz < 90) half *= 0.15 + 0.85 * Math.pow(dz / 90, 1.5);
}
return Math.max(7, half);
}
function squeezeShift(z) {
if (squeezeZ < 0) return 0;
const dz = z - squeezeZ;
if (Math.abs(dz) > 260) return 0;
return squeezeOffset * (1 - Math.abs(dz) / 260);
}
function wallTopY(z, side) { return 26 + 10 * Math.sin(z * 0.007 + side * 3.1); }
function buildWallSide(side, z0, colliders) {
const N = 40;
const pos = [], col = [], idx = [];
const c1 = new THREE.Color(0x8a4a52), c2 = new THREE.Color(0x6a4a80), cTop = new THREE.Color(0xff9a60);
for (let i = 0; i <= N; i++) {
const z = z0 + (i / N) * CHUNK_LEN;
const x = pathX(z) + squeezeShift(z) + (side > 0 ? wallHalf(z, side) : -wallHalf(z, side));
const topY = wallTopY(z, side);
pos.push(x, -46, z, x, topY * 0.55, z, x, topY, z);
col.push(c1.r, c1.g, c1.b, c2.r, c2.g, c2.b, cTop.r, cTop.g, cTop.b);
}
for (let i = 0; i < N; i++) {
const a = i * 3, b = (i + 1) * 3;
idx.push(a, b, a + 1, a + 1, b, b + 1, a + 1, b + 1, a + 2, a + 2, b + 1, b + 2);
}
const g = new THREE.BufferGeometry();
g.setAttribute('position', new THREE.Float32BufferAttribute(pos, 3));
g.setAttribute('color', new THREE.Float32BufferAttribute(col, 3));
g.setIndex(idx);
g.computeVertexNormals();
const mesh = new THREE.Mesh(g, wallMat);
mesh.castShadow = mesh.receiveShadow = true;
for (let i = 0; i < N; i++) {
const z = z0 + (i + 0.5) / N * CHUNK_LEN;
const cx = pathX(z) + squeezeShift(z);
const half = wallHalf(z, side);
const topY = wallTopY(z, side);
const innerX = side > 0 ? cx + half : cx - half;
const outerX = side > 0 ? innerX + 60 : innerX - 60;
colliders.push({ type: 'box', minx: Math.min(innerX, outerX), maxx: Math.max(innerX, outerX), miny: -46, maxy: topY, minz: z - CHUNK_LEN / (N * 2), maxz: z + CHUNK_LEN / (N * 2) });
}
return mesh;
}
function createChunk(idx) {
const z0 = idx * CHUNK_LEN;
const group = new THREE.Group();
const colliders = [];
const diff = clamp((z0 - 700) / 9000, 0, 1);
let sqZ = -1;
if (z0 > 1000 && hash1(idx * 7.31) < 0.45 + 0.4 * diff && z0 - lastSqueezeZ > 750) {
sqZ = z0 + 80 + hash1(idx * 3.17) * (CHUNK_LEN - 160);
lastSqueezeZ = sqZ;
squeezeZ = sqZ;
squeezeOffset = (hash1(sqZ * 0.77) - 0.5) * 56;
}
group.add(buildWallSide(1, z0, colliders), buildWallSide(-1, z0, colliders));
const nMon = Math.min(4, Math.floor(hash1(idx * 5.77) * (1 + 2.6 * diff)));
for (let m = 0; m < nMon; m++) {
const mz = z0 + 70 + hash1(idx * 11.3 + m * 4.9) * (CHUNK_LEN - 140);
if (sqZ > 0 && Math.abs(mz - sqZ) < 130) continue;
const cx = pathX(mz) + squeezeShift(mz);
const half = wallHalf(mz, 1);
const mx = cx + (hash1(idx * 17.9 + m * 7.1) - 0.5) * half * 1.1;
const r = 6 + 9 * hash1(idx * 23.7 + m * 3.3);
const h = 34 + 44 * hash1(idx * 29.3 + m * 5.9);
const faces = 5 + (hash1(idx + m * 2.1) > 0.5 ? 1 : 0);
const geo = new THREE.CylinderGeometry(r * 0.78, r, h, faces, 1);
const mesh = new THREE.Mesh(geo, monolithMat);
mesh.position.set(mx, h / 2 - 4, mz);
mesh.rotation.y = hash1(idx * 31.7 + m) * Math.PI;
mesh.castShadow = mesh.receiveShadow = true;
group.add(mesh);
const edges = new THREE.LineSegments(new THREE.EdgesGeometry(geo, 20), edgeMat);
edges.position.copy(mesh.position);
edges.rotation.copy(mesh.rotation);
group.add(edges);
colliders.push({ type: 'sph', x: mx, y: h / 2 - 4, z: mz, r: r * 0.85 });
}
scene.add(group);
const c = { z0, group, colliders, idx, sqZ };
chunks.push(c);
spawnOrbsForChunk(c, diff);
return c;
}
function disposeChunk(c) {
scene.remove(c.group);
c.group.traverse(o => { if (o.isMesh || o.isLineSegments) o.geometry.dispose(); });
}
/* ============================== orbs ============================== */
const orbGeo = new THREE.IcosahedronGeometry(1.4, 0);
const orbPool = [];
const orbs = [];
for (let i = 0; i < 48; i++) {
const m = new THREE.Mesh(orbGeo, orbMat);
m.visible = false;
m.userData.active = false;
scene.add(m);
orbPool.push(m);
}
function spawnOrb(x, y, z) {
const m = orbPool.find(o => !o.userData.active);
if (!m) return;
m.userData.active = true;
m.visible = true;
m.position.set(x, y, z);
orbs.push({ m, y0: y, phase: Math.random() * 6.28 });
}
function spawnOrbsForChunk(c, diff) {
const n = 5 + Math.floor(hash1(c.idx * 9.23) * 4);
for (let i = 0; i < n; i++) {
const z = c.z0 + 30 + (i / n) * (CHUNK_LEN - 60);
const cx = pathX(z) + squeezeShift(z);
const half = wallHalf(z, 1);
const off = (hash1(c.idx * 13.7 + i * 3.31) - 0.5) * Math.min(half * 1.15, 74);
spawnOrb(cx + off, 3 + hash1(c.idx * 4.9 + i * 7.7) * 15, z);
}
if (c.sqZ > 0) {
const cx = pathX(c.sqZ) + squeezeShift(c.sqZ);
spawnOrb(cx, 6, c.sqZ - 26);
spawnOrb(cx, 7, c.sqZ);
spawnOrb(cx, 6, c.sqZ + 26);
}
}
/* ============================== audio ============================== */
const AudioSys = {
ctx: null, master: null, engine: null,
init() {
if (this.ctx) { if (this.ctx.state === 'suspended') this.ctx.resume(); return; }
const C = window.AudioContext || window.webkitAudioContext;
this.ctx = new C();
this.master = this.ctx.createGain();
this.master.gain.value = 0.5;
this.master.connect(this.ctx.destination);
const len = this.ctx.sampleRate * 2;
const buf = this.ctx.createBuffer(1, len, this.ctx.sampleRate);
const d = buf.getChannelData(0);
for (let i = 0; i < len; i++) d[i] = Math.random() * 2 - 1;
this.noise = buf;
// engine: two detuned oscillators + lowpass
const o1 = this.ctx.createOscillator(); o1.type = 'sawtooth'; o1.frequency.value = 48;
const o2 = this.ctx.createOscillator(); o2.type = 'sawtooth'; o2.frequency.value = 48.7;
const lp = this.ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.value = 240; lp.Q.value = 4;
const g = this.ctx.createGain(); g.gain.value = 0;
o1.connect(lp); o2.connect(lp); lp.connect(g); g.connect(this.master);
o1.start(); o2.start();
this.engine = { o1, o2, lp, g };
},
update(speed, boost) {
if (!this.ctx) return;
const t = this.ctx.currentTime;
const f = 42 + speed * 1.15 + (boost ? 40 : 0);
this.engine.o1.frequency.setTargetAtTime(f, t, 0.08);
this.engine.o2.frequency.setTargetAtTime(f * 1.013, t, 0.08);
this.engine.lp.frequency.setTargetAtTime(200 + speed * 9 + (boost ? 900 : 0), t, 0.1);
this.engine.g.gain.setTargetAtTime(0.05 + speed / MAXV * 0.1 + (boost ? 0.05 : 0), t, 0.15);
},
ping() {
if (!this.ctx) return;
const t = this.ctx.currentTime;
const o = this.ctx.createOscillator(); o.type = 'sine';
o.frequency.setValueAtTime(880, t);
o.frequency.exponentialRampToValueAtTime(1760, t + 0.09);
const g = this.ctx.createGain();
g.gain.setValueAtTime(0.0001, t);
g.gain.exponentialRampToValueAtTime(0.22, t + 0.015);
g.gain.exponentialRampToValueAtTime(0.0001, t + 0.35);
o.connect(g); g.connect(this.master);
o.start(t); o.stop(t + 0.4);
},
whoosh(intensity) {
if (!this.ctx) return;
const t = this.ctx.currentTime;
const src = this.ctx.createBufferSource(); src.buffer = this.noise;
const bp = this.ctx.createBiquadFilter(); bp.type = 'bandpass'; bp.Q.value = 1.2;
bp.frequency.setValueAtTime(300, t);
bp.frequency.exponentialRampToValueAtTime(2400, t + 0.18);
bp.frequency.exponentialRampToValueAtTime(500, t + 0.45);
const g = this.ctx.createGain();
g.gain.setValueAtTime(0.0001, t);
g.gain.exponentialRampToValueAtTime(0.3 * intensity, t + 0.06);
g.gain.exponentialRampToValueAtTime(0.0001, t + 0.5);
src.connect(bp); bp.connect(g); g.connect(this.master);
src.start(t); src.stop(t + 0.55);
},
crash() {
if (!this.ctx) return;
const t = this.ctx.currentTime;
const src = this.ctx.createBufferSource(); src.buffer = this.noise; src.loop = true;
const lp = this.ctx.createBiquadFilter(); lp.type = 'lowpass';
lp.frequency.setValueAtTime(3000, t);
lp.frequency.exponentialRampToValueAtTime(120, t + 0.8);
const g = this.ctx.createGain();
g.gain.setValueAtTime(0.5, t);
g.gain.exponentialRampToValueAtTime(0.0001, t + 1.0);
src.connect(lp); lp.connect(g); g.connect(this.master);
src.start(t); src.stop(t + 1.1);
const o = this.ctx.createOscillator(); o.type = 'sine';
o.frequency.setValueAtTime(160, t);
o.frequency.exponentialRampToValueAtTime(38, t + 0.7);
const g2 = this.ctx.createGain();
g2.gain.setValueAtTime(0.4, t);
g2.gain.exponentialRampToValueAtTime(0.0001, t + 0.8);
o.connect(g2); g2.connect(this.master);
o.start(t); o.stop(t + 0.9);
},
stopEngine() {
if (!this.ctx) return;
this.engine.g.gain.setTargetAtTime(0, this.ctx.currentTime, 0.2);
}
};
/* ============================== post-processing ============================== */
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const afterimage = new AfterimagePass(0.6);
afterimage.uniforms['damp'].value = 0.35;
composer.addPass(afterimage);
const bloom = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 0.7, 0.5, 0.35);
composer.addPass(bloom);
composer.addPass(new OutputPass());
/* ============================== game state ============================== */
const G = {
state: 'menu', // menu | playing | dead
px: 0, py: 10, pz: 0,
vx: 0, vy: 0, vz: 28,
roll: 0, pitch: 0,
speed: 28,
energy: 100, turbo: 0, boosting: false, boostLeft: 0,
score: 0, mult: 1, bestMult: 1, maxSpeed: 0,
shake: 0, time: 0, dead: 0,
nearCd: 0
};
const keys = {};
window.addEventListener('keydown', e => {
keys[e.code] = true;
if (e.code === 'Space') e.preventDefault();
if (e.code === 'Enter') {
if (G.state === 'menu') startGame();
else if (G.state === 'dead' && G.dead > 0.8) startGame();
}
});
window.addEventListener('keyup', e => { keys[e.code] = false; });
const el = id => document.getElementById(id);
window.__game = () => ({ G, startGame, chunks, orbs, pathX, wallHalf, keys });
const ui = {
hud: el('hud'), menu: el('menu'), over: el('gameover'),
score: el('score'), mult: el('mult'), speedfill: el('speedfill'), speedval: el('speedval'),
energybar: el('energybar'), energyfill: el('energyfill'),
turboWrap: el('turbo-wrap'), turbofill: el('turbofill'),
nearmiss: el('nearmiss'), speedfx: el('speedfx'),
goScore: el('go-score'), goSpeed: el('go-speed'), goMult: el('go-mult'), goDist: el('go-dist'), goReason: el('go-reason'),
restart: el('restart')
};
ui.menu.addEventListener('click', () => { if (G.state === 'menu') startGame(); });
ui.restart.addEventListener('click', () => { if (G.state === 'dead' && G.dead > 0.5) startGame(); });
function startGame() {
AudioSys.init();
// reset world
for (const c of chunks) disposeChunk(c);
chunks.length = 0;
for (const o of orbs) { o.m.userData.active = false; o.m.visible = false; }
orbs.length = 0;
chunkIdx = 0; lastSqueezeZ = -9999; squeezeZ = -1; squeezeOffset = 0;
seed = Math.random() * 1000;
Object.assign(G, {
state: 'playing', px: pathX(0), py: 10, pz: 0, vx: 0, vy: 0, vz: 28,
roll: 0, pitch: 0, speed: 28, energy: 100, turbo: 0, boosting: false, boostLeft: 0,
score: 0, mult: 1, bestMult: 1, maxSpeed: 28, shake: 0, time: 0, dead: 0, nearCd: 0
});
for (let i = 0; i < AHEAD_CHUNKS + 1; i++) createChunk(chunkIdx++);
ui.menu.classList.add('hidden');
ui.over.classList.add('hidden');
ui.hud.classList.remove('hidden');
}
function gameOver(reason) {
if (G.state !== 'playing') return;
G.state = 'dead'; G.dead = 0;
AudioSys.crash(); AudioSys.stopEngine();
G.shake = 1.6;
ui.hud.classList.add('hidden');
// explosion burst
for (let i = 0; i < 70; i++) {
const a = Math.random() * 6.28, b = Math.random() * 6.28, s = 8 + Math.random() * 30;
spawnP(G.px, G.py, G.pz, Math.cos(a) * Math.cos(b) * s, Math.sin(b) * s, Math.sin(a) * Math.cos(b) * s,
0.6 + Math.random() * 0.8, 1.5 + Math.random() * 2, 1, 0.45 + Math.random() * 0.5, 0.15, 1.5, 6);
}
craft.visible = false;
ui.speedfx.style.opacity = 0;
ui.goReason.textContent = reason;
ui.goScore.textContent = Math.floor(G.score).toLocaleString();
ui.goSpeed.textContent = Math.round(G.maxSpeed * 3.6) + ' KM/H';
ui.goMult.textContent = '×' + G.bestMult.toFixed(1);
ui.goDist.textContent = Math.round(G.pz / 100).toLocaleString() + ' KM';
ui.over.classList.remove('hidden');
}
/* ============================== physics & collisions ============================== */
function stepPhysics(dt) {
const diff = clamp(G.pz / 12000, 0, 1);
G.speed = damp(G.speed, 58 + 88 * diff, 0.22, dt);
if (keys['KeyS']) G.speed = damp(G.speed, G.speed * 0.9, 0.3, dt); // throttle down
if (keys['KeyW'] && G.energy > 0) G.speed = damp(G.speed, Math.min(MAXV, G.speed + 18), 0.5, dt);
// turbo
const wantBoost = keys['Space'] && G.turbo >= 100 && G.boostLeft <= 0 && G.energy > 0;
if (wantBoost) { G.boosting = true; G.boostLeft = 2.6; }
if (G.boosting) {
G.boostLeft -= dt;
G.turbo = Math.max(0, G.turbo - 40 * dt);
G.energy = Math.max(0, G.energy - 10 * dt);
G.speed = damp(G.speed, MAXV * 1.28, 1.4, dt);
if (G.boostLeft <= 0) G.boosting = false;
} else {
if (G.speed > 120) G.turbo = Math.min(100, G.turbo + (G.speed - 110) * 0.09 * dt * 10);
G.turbo = Math.max(0, G.turbo - 1.2 * dt);
}
G.speed = Math.min(G.speed, MAXV * 1.3);
// flight controls: lateral / vertical drift, damped
const steer = (keys['KeyD'] ? 1 : 0) - (keys['KeyA'] ? 1 : 0);
const pitchIn = (keys['KeyW'] ? 1 : 0) - (keys['KeyS'] ? 1 : 0);
G.vx = damp(G.vx, steer * G.speed * 0.62, 2.4, dt);
G.vy = damp(G.vy, pitchIn * G.speed * 0.5, 2.4, dt);
G.px += G.vx * dt;
G.py += G.vy * dt;
G.pz += G.speed * dt;
G.py = clamp(G.py, 2.2, 52);
// banking
G.roll = damp(G.roll, -steer * 0.85, 5, dt);
G.pitch = damp(G.pitch, -pitchIn * 0.45, 5, dt);
// energy decay (faster at speed & while boosting)
G.energy = Math.max(0, G.energy - (1.5 + G.speed / MAXV * 1.6) * dt);
if (G.energy <= 0) { gameOver('OUT OF ENERGY'); return; }
G.maxSpeed = Math.max(G.maxSpeed, G.speed);
G.score += G.speed * dt * 0.5 * G.mult;
// collisions & near-miss
G.nearCd -= dt;
let minDist = 1e9;
for (const c of chunks) {
if (c.z0 > G.pz + 80 || c.z0 + CHUNK_LEN < G.pz - 80) continue;
for (const col of c.colliders) {
let d;
if (col.type === 'box') {
const dx = G.px - clamp(G.px, col.minx, col.maxx);
const dy = G.py - clamp(G.py, col.miny, col.maxy);
const dz = G.pz - clamp(G.pz, col.minz, col.maxz);
d = Math.sqrt(dx * dx + dy * dy + dz * dz);
} else {
const dx = G.px - col.x, dy = G.py - col.y, dz = G.pz - col.z;
d = Math.sqrt(dx * dx + dy * dy + dz * dz) - col.r;
}
if (d < PLAYER_R) { gameOver('WALL IMPACT'); return; }
if (d < minDist) minDist = d;
}
}
// monolith Z-range check (spheres are infinite cylinders in XZ; restrict to their height band)
if (G.state !== 'playing') return;
const near = minDist - PLAYER_R;
if (near < 5 && G.nearCd <= 0) {
G.nearCd = 0.35;
const closeness = clamp(1 - near / 5, 0, 1);
const gained = 0.25 + closeness * 0.9;
G.mult = Math.min(20, G.mult + gained);
G.bestMult = Math.max(G.bestMult, G.mult);
G.shake = Math.max(G.shake, 0.35 + closeness * 0.5);
AudioSys.whoosh(0.4 + closeness * 0.6);
ui.mult.classList.remove('pulse'); void ui.mult.offsetWidth; ui.mult.classList.add('pulse');
if (closeness > 0.55) {
ui.nearmiss.classList.remove('flash'); void ui.nearmiss.offsetWidth; ui.nearmiss.classList.add('flash');
}
}
// multiplier decays
G.mult = Math.max(1, G.mult - 0.35 * dt);
// orbs
for (let i = orbs.length - 1; i >= 0; i--) {
const o = orbs[i];
const dx = G.px - o.m.position.x, dy = G.py - o.m.position.y, dz = G.pz - o.m.position.z;
if (dx * dx + dy * dy + dz * dz < 26) {
o.m.userData.active = false; o.m.visible = false;
orbs.splice(i, 1);
G.energy = Math.min(100, G.energy + 24);
G.turbo = Math.min(100, G.turbo + 14);
G.score += 150 * G.mult;
AudioSys.ping();
for (let k = 0; k < 14; k++) {
const a = Math.random() * 6.28, s = 4 + Math.random() * 10;
spawnP(o.m.position.x, o.m.position.y, o.m.position.z,
Math.cos(a) * s, Math.sin(a) * s, 0, 0.4 + Math.random() * 0.4, 1.2, 1, 0.75, 0.3, 2, 0);
}
} else if (o.m.position.z < G.pz - 30) {
o.m.userData.active = false; o.m.visible = false;
orbs.splice(i, 1);
}
}
}
/* ============================== camera & effects ============================== */
const camPos = new THREE.Vector3(0, 12, -14);
function updateCamera(dt) {
const target = new THREE.Vector3(
G.px + G.vx * 0.06,
G.py + 2.9 + G.vy * 0.05,
G.pz - 11.5
);
camPos.x = damp(camPos.x, target.x, 6, dt);
camPos.y = damp(camPos.y, target.y, 6, dt);
camPos.z = damp(camPos.z, target.z, 10, dt);
G.shake = Math.max(0, G.shake - dt * 2.2);
const sh = G.shake * (G.boosting ? 0.5 : 1);
camera.position.set(
camPos.x + (Math.random() - 0.5) * sh * 0.7,
camPos.y + (Math.random() - 0.5) * sh * 0.7,
camPos.z + (Math.random() - 0.5) * sh * 0.4
);
camera.lookAt(G.px + G.vx * 0.15, G.py + G.vy * 0.12 + 0.5, G.pz + 12);
const fovT = 68 + (G.speed / MAXV) * 16 + (G.boosting ? 14 : 0);
camera.fov = damp(camera.fov, fovT, 4, dt);
camera.updateProjectionMatrix();
// sky + sun follow
sky.position.set(G.px, 0, G.pz);
sun.position.set(G.px + sunDir.x * 260, G.py + sunDir.y * 260, G.pz + sunDir.z * 260);
sun.target.position.set(G.px, 0, G.pz);
fillLight.position.set(G.px + fillDir.x * 200, G.py + fillDir.y * 200, G.pz + fillDir.z * 200);
fillLight.target.position.set(G.px, 0, G.pz);
ground.position.x = G.px; ground.position.z = G.pz;
// speed FX
const sp = clamp((G.speed - 100) / (MAXV * 1.3 - 100), 0, 1);
ui.speedfx.style.opacity = (sp * 0.55 + (G.boosting ? 0.3 : 0)).toFixed(2);
}
function updateTrail(dt) {
const n = Math.floor(G.speed * dt * 1.6) + 1;
for (let i = 0; i < n; i++) {
const t = Math.random();
spawnP(
G.px - Math.sin(G.roll) * 0.8 + (Math.random() - 0.5) * 0.7,
G.py - 0.1 + (Math.random() - 0.5) * 0.5,
G.pz - 2.6 - t * 0.8,
-G.vx * 0.05 + (Math.random() - 0.5) * 2,
(Math.random() - 0.5) * 2,
-G.speed * 0.35 - Math.random() * 6,
0.25 + Math.random() * 0.3,
0.9 + Math.random() * 0.8,
1, 0.55 + Math.random() * 0.3, 0.15, 3, 0
);
}
// speed dust streaks
if (G.speed > 90) {
const k = Math.floor((G.speed - 90) / 40);
for (let i = 0; i < k; i++) {
const a = Math.random() * 6.28, r = 6 + Math.random() * 10;
spawnP(
G.px + Math.cos(a) * r, G.py + Math.sin(a) * r * 0.6, G.pz + 2,
0, 0, -G.speed * 1.5,
0.3, 0.5 + Math.random() * 0.6,
0.75, 0.7, 0.85, 0, 0
);
}
}
}
/* ============================== HUD ============================== */
function updateHUD() {
ui.score.textContent = Math.floor(G.score).toLocaleString();
ui.mult.textContent = '×' + G.mult.toFixed(1);
const kmh = Math.round(G.speed * 3.6);
ui.speedval.innerHTML = kmh + ' <small>KM/H</small>';
ui.speedfill.style.width = clamp(G.speed / (MAXV * 1.3), 0, 1) * 100 + '%';
ui.energyfill.style.width = G.energy + '%';
ui.energybar.classList.toggle('low', G.energy < 25);
ui.turbofill.style.width = G.turbo + '%';
ui.turboWrap.classList.toggle('ready', G.turbo >= 100);
}
/* ============================== chunk streaming ============================== */
function streamChunks() {
while (chunks[chunks.length - 1].z0 + CHUNK_LEN < G.pz + AHEAD_CHUNKS * CHUNK_LEN) {
createChunk(chunkIdx++);
}
while (chunks.length && chunks[0].z0 + CHUNK_LEN < G.pz - 200) {
disposeChunk(chunks.shift());
}
}
/* ============================== main loop ============================== */
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.05);
G.time += dt;
if (G.state === 'playing') {
stepPhysics(dt);
streamChunks();
updateCamera(dt);
updateTrail(dt);
updateHUD();
AudioSys.update(G.speed, G.boosting);
// craft orientation
craft.position.set(G.px, G.py, G.pz);
craft.rotation.z = G.roll;
craft.rotation.x = G.pitch;
flame.scale.set(1, 1 + G.speed / MAXV * 1.6 + (G.boosting ? 1.4 : 0), 1);
flame.scale.x = flame.scale.z = 0.8 + Math.random() * 0.3;
engineMat.emissiveIntensity = 1.2 + G.speed / MAXV * 1.8 + (G.boosting ? 2.5 : 0);
afterimage.uniforms['damp'].value = clamp(0.2 + G.speed / MAXV * 0.45 + (G.boosting ? 0.25 : 0), 0, 0.92);
bloom.strength = 0.85 + (G.boosting ? 0.5 : 0);
} else if (G.state === 'dead') {
G.dead += dt;
updateCamera(dt);
G.shake = Math.max(0, G.shake - dt * 1.5);
camera.fov = damp(camera.fov, 68, 3, dt);
camera.updateProjectionMatrix();
afterimage.uniforms['damp'].value = damp(afterimage.uniforms['damp'].value, 0.2, 2, dt);
bloom.strength = damp(bloom.strength, 0.7, 2, dt);
sky.position.set(G.px, 0, G.pz);
sun.position.set(G.px + sunDir.x * 260, G.py + sunDir.y * 260, G.pz + sunDir.z * 260);
sun.target.position.set(G.px, 0, G.pz);
} else {
// menu idle: slow drift through the canyon
G.pz += 30 * dt;
G.px = damp(G.px, pathX(G.pz) * 0.5, 0.8, dt);
G.py = damp(G.py, 12 + Math.sin(G.time * 0.5) * 3, 0.8, dt);
streamChunks();
updateCamera(dt);
craft.position.set(G.px, G.py, G.pz);
craft.rotation.z = damp(craft.rotation.z, Math.sin(G.time * 0.7) * 0.3, 2, dt);
flame.scale.set(1, 1.2, 1);
sky.position.set(G.px, 0, G.pz);
sun.position.set(G.px + sunDir.x * 260, 12 + sunDir.y * 260, G.pz + sunDir.z * 260);
sun.target.position.set(G.px, 0, G.pz);
}
// orbs bob & spin
for (const o of orbs) {
o.m.rotation.y += dt * 2.2;
o.m.position.y = o.y0 + Math.sin(G.time * 2.4 + o.phase) * 0.9;
}
updateParticles(dt);
composer.render();
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
});
/* boot: pre-build menu world */
for (let i = 0; i < AHEAD_CHUNKS + 1; i++) createChunk(chunkIdx++);
animate();
</script>
</body>
</html>
<!-- agent-meta {"model":"ukisai/Swift-Qwen3.8-27B-GGUF","provider":"brigid","persona":"full","sessionId":"5d5b843b-51dc-40b6-a84a-89f1543b3e25","tokensIn":5932239,"tokensOut":101171,"tokensTotal":6033410,"cost":0,"turns":69,"toolCalls":79,"failedToolCalls":2,"timestamp":"2026-09-29T19:11:46.708Z"} -->