Flight Simulator
GPT 6 Luna · full

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 30,218 bytes · SHA-256 ac9e6d8fdb54
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>APEX AERO — Flight Operations</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;500;600;700;800&family=DM+Mono:wght@400;500&display=swap');
:root{--ice:#d9f6ff;--cyan:#61e8ff;--amber:#ffc66d;--muted:#82a4af;--glass:rgba(4,17,27,.72);--line:rgba(154,218,232,.21)}*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#07121a;color:var(--ice);font-family:'Barlow Condensed',sans-serif}#scene{position:absolute;inset:0;width:100%;height:100%;display:block}#shade{position:absolute;inset:0;pointer-events:none;background:linear-gradient(180deg,rgba(2,12,20,.42),transparent 23%,transparent 65%,rgba(2,12,20,.52));box-shadow:inset 0 0 130px rgba(0,0,0,.25)}.topbar{position:absolute;left:30px;right:30px;top:24px;display:flex;align-items:flex-start;justify-content:space-between;pointer-events:none}.brand{display:flex;gap:12px;align-items:center}.brand-mark{width:35px;height:35px;border:1px solid var(--cyan);display:grid;place-items:center;color:var(--cyan);font-size:20px;transform:rotate(45deg)}.brand-mark span{transform:rotate(-45deg)}.brand-name{font-size:19px;font-weight:800;letter-spacing:3px;line-height:1}.brand-sub{font:9px 'DM Mono',monospace;letter-spacing:2px;color:#9ab9c2;margin-top:5px}.topright{display:flex;gap:9px}.chip{border:1px solid var(--line);background:rgba(4,17,27,.58);padding:8px 11px;font:10px 'DM Mono',monospace;letter-spacing:1px;color:#c8e6ed}.chip b{color:var(--cyan);font-weight:500}.hud-left{position:absolute;left:30px;top:105px;display:grid;gap:8px;width:168px}.readout{background:var(--glass);border-left:2px solid var(--cyan);padding:9px 11px 8px;backdrop-filter:blur(8px)}.readout .label{font:9px 'DM Mono',monospace;color:#92adb5;letter-spacing:1.4px;text-transform:uppercase}.readout .value{font-size:25px;line-height:1.1;font-weight:600;letter-spacing:.3px}.readout .unit{font:10px 'DM Mono',monospace;color:#9bb6bf;margin-left:5px}.right-stack{position:absolute;right:30px;top:105px;width:190px;display:grid;gap:9px}.panel{background:var(--glass);border:1px solid var(--line);padding:11px 13px;backdrop-filter:blur(8px)}.panel-title{font:9px 'DM Mono',monospace;color:#9ab6c0;letter-spacing:1.4px;margin-bottom:9px}.barline{display:flex;justify-content:space-between;font:10px 'DM Mono',monospace;margin:7px 0 4px;color:#c8e2e8}.bar{height:3px;background:rgba(220,245,255,.14)}.bar i{display:block;height:100%;width:0;background:var(--cyan);box-shadow:0 0 8px #61e8ff}.bar.orange i{background:var(--amber);box-shadow:0 0 8px #ffc66d}.compass{position:absolute;top:24px;left:50%;transform:translateX(-50%);text-align:center;color:#d9f6ff;font:10px 'DM Mono',monospace;letter-spacing:1px}.compass .heading{font-size:18px;font-weight:500;margin-top:3px}.reticle{position:absolute;left:50%;top:48%;width:88px;height:58px;transform:translate(-50%,-50%);opacity:.82;pointer-events:none}.reticle:before,.reticle:after{content:'';position:absolute;background:var(--cyan);opacity:.9}.reticle:before{height:1px;width:100%;top:50%;left:0}.reticle:after{width:1px;height:100%;top:0;left:50%}.reticle i{position:absolute;inset:13px 24px;border:1px solid var(--cyan);border-radius:50%;opacity:.75}.bottomleft{position:absolute;left:30px;bottom:24px;display:flex;gap:8px}.keyhint{background:rgba(4,17,27,.72);border:1px solid var(--line);padding:8px 10px;color:#b9d7df;font:9px 'DM Mono',monospace}.keyhint strong{color:var(--cyan);font-weight:500}.bottomright{position:absolute;right:30px;bottom:24px;text-align:right;font:9px 'DM Mono',monospace;color:#a5c3ca;line-height:1.7}.status{color:var(--cyan);font-size:11px;letter-spacing:1px}.overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(4,13,20,.46);backdrop-filter:blur(6px);transition:opacity .25s}.overlay.hidden{opacity:0;pointer-events:none}.menu{width:min(800px,calc(100% - 36px));background:rgba(5,17,26,.94);border:1px solid rgba(157,219,232,.28);box-shadow:0 24px 80px #0009;padding:30px 34px 27px;position:relative}.menu:before{content:'';position:absolute;top:-1px;left:34px;width:75px;height:2px;background:var(--cyan);box-shadow:0 0 14px var(--cyan)}.eyebrow{font:10px 'DM Mono',monospace;color:var(--cyan);letter-spacing:2px}.menu h1{font-size:42px;line-height:1;margin:8px 0 8px;letter-spacing:1px}.menu p{margin:0;color:#9eb9c1;font-size:15px;letter-spacing:.2px}.section-label{font:10px 'DM Mono',monospace;color:#8da9b2;letter-spacing:1.5px;margin:23px 0 9px}.fleet{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}.plane-card{border:1px solid rgba(152,199,210,.2);background:rgba(255,255,255,.025);padding:12px;cursor:pointer;transition:.2s;min-height:110px}.plane-card:hover,.plane-card.selected{border-color:var(--cyan);background:rgba(49,183,205,.1)}.plane-card .plane-icon{color:var(--cyan);font-size:22px;float:right}.plane-card h3{font-size:18px;margin:0 0 3px}.plane-card small{font:9px 'DM Mono',monospace;color:#8eabb4;letter-spacing:.6px}.plane-card .stats{margin-top:11px;font:9px 'DM Mono',monospace;color:#b3cbd1;display:flex;justify-content:space-between}.modes{display:grid;grid-template-columns:1fr 1fr;gap:9px}.mode{padding:11px 13px;border:1px solid rgba(152,199,210,.2);cursor:pointer;background:rgba(255,255,255,.025)}.mode.selected,.mode:hover{border-color:var(--amber);background:rgba(255,180,71,.07)}.mode b{font-size:16px}.mode span{display:block;color:#91aeb7;font-size:12px;margin-top:2px}.menu-footer{display:flex;justify-content:space-between;align-items:center;margin-top:22px}.start{border:0;background:var(--cyan);color:#04151d;padding:11px 25px;font:700 14px 'Barlow Condensed',sans-serif;letter-spacing:1.4px;cursor:pointer}.start:hover{background:#b5f6ff}.footer-note{font:9px 'DM Mono',monospace;color:#79949c;line-height:1.7}.toast{position:absolute;top:79px;left:50%;transform:translateX(-50%);background:rgba(5,18,26,.86);border:1px solid var(--line);padding:8px 14px;color:var(--amber);font:10px 'DM Mono',monospace;letter-spacing:1px;opacity:0;transition:opacity .2s}.toast.show{opacity:1}@media(max-width:700px){.topbar{left:14px;right:14px;top:13px}.brand-name{font-size:15px}.chip{padding:6px;font-size:8px}.hud-left{left:12px;top:86px;width:125px}.readout{padding:7px}.readout .value{font-size:20px}.right-stack{right:12px;top:86px;width:145px}.panel{padding:8px}.bottomleft{left:12px;bottom:12px;gap:4px}.keyhint{font-size:7px;padding:6px}.bottomright{right:12px;bottom:12px;font-size:8px}.menu{padding:23px 19px}.menu h1{font-size:34px}.fleet{gap:5px}.plane-card{padding:8px;min-height:104px}.plane-card h3{font-size:15px}.plane-card .stats{font-size:7px}.plane-card small{font-size:7px}.compass{top:68px}.reticle{top:46%}}
</style></head>
<body>
<canvas id="scene"></canvas><div id="shade"></div>
<div class="topbar"><div class="brand"><div class="brand-mark"><span>⌁</span></div><div><div class="brand-name">APEX AERO</div><div class="brand-sub">FLIGHT OPERATIONS // 07</div></div></div><div class="topright"><div class="chip">SIM <b>LIVE</b></div><div class="chip" id="weatherChip">WX <b>VMC</b></div><div class="chip">FPS <b id="fps">60</b></div></div></div>
<div class="compass">HDG <div class="heading" id="hdg">090°</div></div>
<div class="hud-left"><div class="readout"><div class="label">Indicated airspeed</div><div class="value"><span id="speed">000</span><span class="unit">KTS</span></div></div><div class="readout"><div class="label">Radar altitude</div><div class="value"><span id="alt">0000</span><span class="unit">FT</span></div></div><div class="readout"><div class="label">Vertical speed</div><div class="value"><span id="vs">+000</span><span class="unit">FPM</span></div></div></div>
<div class="right-stack"><div class="panel"><div class="panel-title">FLIGHT PARAMETERS</div><div class="barline"><span>THROTTLE</span><span id="throttleNum">72%</span></div><div class="bar"><i id="throttleBar"></i></div><div class="barline"><span>G-LOAD</span><span id="gload">1.0 G</span></div><div class="bar orange"><i id="gBar"></i></div><div class="barline"><span>ANGLE OF ATTACK</span><span id="aoa">2°</span></div></div><div class="panel"><div class="panel-title">AIRCRAFT / FLIGHT</div><div style="font-size:17px" id="aircraftLabel">F-16 FALCON</div><div style="font:9px 'DM Mono';color:#8faeb7;margin-top:3px" id="modeLabel">TAKEOFF / LANDING</div></div><div class="panel"><div class="panel-title">NAVIGATION</div><div class="barline"><span>RUNWAY 09/27</span><span id="runwayDistance">—</span></div><div class="barline"><span>GROUND CONTACT</span><span id="gearStatus" style="color:var(--cyan)">GEAR DOWN</span></div></div></div>
<div class="reticle"><i></i></div><div class="toast" id="toast">FLIGHT DATA</div>
<div class="bottomleft"><div class="keyhint"><strong>W/S</strong> PITCH</div><div class="keyhint"><strong>A/D</strong> ROLL</div><div class="keyhint"><strong>Q/E</strong> YAW</div><div class="keyhint"><strong>SHIFT / CTRL</strong> THROTTLE</div><div class="keyhint"><strong>R</strong> RESET</div></div><div class="bottomright"><div class="status" id="flightStatus">PARKED · RUNWAY 09</div><div>MOUSE DRAG OR KEYBOARD // SPACE: PAUSE</div><div id="soundState">AUDIO SYSTEM STANDBY</div></div>
<div class="overlay" id="overlay"><div class="menu"><div class="eyebrow">APEX AERO // FLIGHT SIMULATION</div><h1>OWN THE SKY.</h1><p>Every input has weight. Every knot matters. Fly the aircraft, not the controls.</p><div class="section-label">01 — SELECT AIRFRAME</div><div class="fleet"><div class="plane-card selected" data-plane="falcon"><span class="plane-icon">✦</span><h3>F-16 FALCON</h3><small>LIGHT MULTIROLE · HIGH AGILITY</small><div class="stats"><span>THRUST 130 KN</span><span>WING 27.9 M²</span></div></div><div class="plane-card" data-plane="hawk"><span class="plane-icon">◈</span><h3>HAWK T-1</h3><small>TRAINER · FORGIVING HANDLING</small><div class="stats"><span>THRUST 24 KN</span><span>WING 16.7 M²</span></div></div><div class="plane-card" data-plane="mustang"><span class="plane-icon">✧</span><h3>P-51 MUSTANG</h3><small>CLASSIC · LONG-RANGE POWER</small><div class="stats"><span>THRUST 42 KN</span><span>WING 21.8 M²</span></div></div></div><div class="section-label">02 — MISSION PROFILE</div><div class="modes"><div class="mode selected" data-mode="landing"><b>01 PRECISION FLIGHT</b><span>Take off, circuit and land on runway 09/27.</span></div><div class="mode" data-mode="dogfight"><b>02 MERGE / DOGFIGHT</b><span>High-G pursuit. Track and intercept the bandit.</span></div></div><div class="menu-footer"><div class="footer-note">CUSTOM NEWTONIAN FLIGHT MODEL<br>ATMOSPHERE · INERTIA · STALL · G-LOADING</div><button class="start" id="startBtn">START FLIGHT →</button></div></div></div>
<script>
(()=>{'use strict';
const canvas=document.getElementById('scene'),ctx=canvas.getContext('2d');let W=innerWidth,H=innerHeight,dpr=Math.min(devicePixelRatio||1,1.6);function resize(){W=innerWidth;H=innerHeight;canvas.width=W*dpr;canvas.height=H*dpr;canvas.style.width=W+'px';canvas.style.height=H+'px';ctx.setTransform(dpr,0,0,dpr,0,0);if(THREE&&renderer){renderer.setSize(W,H);camera.aspect=W/H;camera.updateProjectionMatrix()}}window.addEventListener('resize',resize);
let renderer=null,scene3=null,camera=null,plane3=null,terrainMesh=null,runwayMesh=null,threeReady=false;const THREE=window.THREE;
const planes={falcon:{name:'F-16 FALCON',mass:9500,thrust:128000,area:27.9,cl:4.8,cd:.024,stall:19,roll:1.55,pitch:.82,max:650,color:0x9faeae},hawk:{name:'HAWK T-1',mass:5700,thrust:24000,area:16.7,cl:5.4,cd:.032,stall:17,roll:.78,pitch:.62,max:330,color:0xc5c9bd},mustang:{name:'P-51 MUSTANG',mass:4200,thrust:42000,area:21.8,cl:4.7,cd:.027,stall:16,roll:1.0,pitch:.63,max:440,color:0x8b9b88}};
let selected='falcon',mode='landing',running=false,paused=false,audio=null,osc=null,gain=null,windGain=null,last=performance.now(),fpsCount=0,fpsTime=0,mouseDown=false,mouseDX=0,mouseDY=0;
const s={x:-520,y:8,z:0,vx:0,vy:0,vz:0,yaw:Math.PI/2,pitch:0,roll:0,rollRate:0,pitchRate:0,yawRate:0,throttle:.72,g:1,aoa:0,stall:false,gear:true,landed:false,targetX:800,targetY:850,targetZ:2500,targetVX:-80};let keys={};
function ground(x,z){const h1=Math.sin(x*.00039+Math.cos(z*.00022)*1.6)*360;const h2=Math.cos(z*.00031-x*.00013)*210;const h3=Math.sin((x+z)*.00017)*280;const ridge=Math.pow(Math.max(0,Math.sin(x*.00021+z*.00016)),2)*500;const coast=-Math.max(0,Math.sin(x*.00009+z*.00007)-.35)*190;const runwayArea=Math.abs(z)<95&&x<900&&x>-1000;return runwayArea?Math.max(0,(h1+h2+h3+ridge+coast)*.025):Math.max(-80,h1+h2+h3+ridge+coast)}
function initThree(){if(!THREE)return;let gl=null;try{gl=canvas.getContext('webgl2',{antialias:true,alpha:false})||canvas.getContext('webgl',{antialias:true,alpha:false})||canvas.getContext('experimental-webgl')}catch(e){}if(!gl)return;try{renderer=new THREE.WebGLRenderer({canvas,context:gl,antialias:true,alpha:false});renderer.setPixelRatio(dpr);renderer.setSize(W,H);renderer.outputEncoding=THREE.sRGBEncoding;renderer.shadowMap.enabled=true;scene3=new THREE.Scene();scene3.background=new THREE.Color(0x8bbbd0);scene3.fog=new THREE.FogExp2(0x9bbdc2,.000075);camera=new THREE.PerspectiveCamera(68,W/H,.5,60000);scene3.add(new THREE.HemisphereLight(0xcceaff,0x39412e,1.35));let sun=new THREE.DirectionalLight(0xffedc9,2.4);sun.position.set(-5000,9000,3000);sun.castShadow=true;scene3.add(sun);let geo=new THREE.PlaneGeometry(40000,40000,150,150);geo.rotateX(-Math.PI/2);let pos=geo.attributes.position;for(let i=0;i<pos.count;i++){let x=pos.getX(i),z=pos.getZ(i);pos.setY(i,ground(x,z));}geo.computeVertexNormals();let colors=[];for(let i=0;i<pos.count;i++){let y=pos.getY(i),n=THREE.MathUtils.clamp((y+100)/1000,0,1);let col=new THREE.Color().setHSL(.27-n*.12,.26-n*.2,.22+n*.28);if(y>850)col.setHSL(.1,.08,.64);colors.push(col.r,col.g,col.b)}geo.setAttribute('color',new THREE.Float32BufferAttribute(colors,3));let mat=new THREE.MeshStandardMaterial({vertexColors:true,roughness:.96,metalness:.02,flatShading:true});terrainMesh=new THREE.Mesh(geo,mat);terrainMesh.receiveShadow=true;scene3.add(terrainMesh);
let ocean=new THREE.Mesh(new THREE.PlaneGeometry(90000,90000),new THREE.MeshStandardMaterial({color:0x397d91,roughness:.48,metalness:.1}));ocean.rotation.x=-Math.PI/2;ocean.position.y=-85;scene3.add(ocean);
let rg=new THREE.Group();let asphalt=new THREE.Mesh(new THREE.BoxGeometry(1800,1,160),new THREE.MeshStandardMaterial({color:0x3a4041,roughness:.88}));asphalt.position.set(-70,3,0);rg.add(asphalt);let lineMat=new THREE.MeshBasicMaterial({color:0xe6e4d7});for(let x=-850;x<800;x+=110){let stripe=new THREE.Mesh(new THREE.BoxGeometry(45,1,3),lineMat);stripe.position.set(x,3.7,0);rg.add(stripe)}for(let z of [-74,74]){let edge=new THREE.Mesh(new THREE.BoxGeometry(1700,1,2),lineMat);edge.position.set(-70,3.7,z);rg.add(edge)}scene3.add(rg);
plane3=buildAircraft();scene3.add(plane3);const sunBall=new THREE.Mesh(new THREE.SphereGeometry(130,16,12),new THREE.MeshBasicMaterial({color:0xffe9ba}));sunBall.position.set(9000,7000,-18000);scene3.add(sunBall);threeReady=true;resize()}catch(e){console.warn('Three renderer fallback',e);renderer=null;threeReady=false}}
function buildAircraft(){let g=new THREE.Group(),mat=new THREE.MeshStandardMaterial({color:planes[selected].color,metalness:.48,roughness:.42}),dark=new THREE.MeshStandardMaterial({color:0x26353b,metalness:.55,roughness:.32}),glass=new THREE.MeshStandardMaterial({color:0x4c9eb1,metalness:.65,roughness:.16});let fus=new THREE.Mesh(new THREE.CylinderGeometry(.48,.65,10,12),mat);fus.rotation.x=Math.PI/2;fus.position.y=.35;g.add(fus);let nose=new THREE.Mesh(new THREE.ConeGeometry(.48,3,12),dark);nose.rotation.x=-Math.PI/2;nose.position.z=-6.5;g.add(nose);let wing=new THREE.Mesh(new THREE.BoxGeometry(13,.18,3.3),mat);wing.position.set(0,.15,-.4);wing.rotation.y=.1;g.add(wing);let tail=new THREE.Mesh(new THREE.BoxGeometry(5.1,.14,1.7),mat);tail.position.set(0,.3,4);g.add(tail);let fin=new THREE.Mesh(new THREE.BoxGeometry(.18,2.3,2.4),mat);fin.position.set(0,1.25,3.7);g.add(fin);let canopy=new THREE.Mesh(new THREE.SphereGeometry(1.05,16,10,0,Math.PI*2,0,Math.PI/2),glass);canopy.scale.set(.75,.75,2.2);canopy.position.set(0,1,-1.6);g.add(canopy);let engine=new THREE.Mesh(new THREE.CylinderGeometry(.45,.52,1.7,12),dark);engine.rotation.x=Math.PI/2;engine.position.z=5.2;g.add(engine);let glow=new THREE.Mesh(new THREE.ConeGeometry(.32,2.1,10),new THREE.MeshBasicMaterial({color:0xff963f,transparent:true,opacity:.7}));glow.rotation.x=Math.PI/2;glow.position.z=6.9;glow.name='exhaust';g.add(glow);return g}
function rebuildPlane(){if(!threeReady)return;scene3.remove(plane3);plane3=buildAircraft();scene3.add(plane3)}
function reset(){let p=planes[selected];Object.assign(s,{x:-520,y:8,z:0,vx:0,vy:0,vz:0,yaw:Math.PI/2,pitch:0,roll:0,rollRate:0,pitchRate:0,yawRate:0,throttle:mode==='landing'?.72:.88,g:1,aoa:0,stall:false,landed:false});if(mode==='dogfight'){s.x=0;s.y=1200;s.z=0;s.yaw=0;s.vz=180;s.vx=0;s.vy=0;s.targetX=400;s.targetY=1050;s.targetZ=2300;s.targetVX=-95}document.getElementById('flightStatus').textContent=mode==='landing'?'PARKED · RUNWAY 09':'MERGE · BANDIT NORTH';document.getElementById('toast').classList.remove('show')}
function setPlane(k){selected=k;document.querySelectorAll('.plane-card').forEach(e=>e.classList.toggle('selected',e.dataset.plane===k));document.getElementById('aircraftLabel').textContent=planes[k].name;rebuildPlane()}
document.querySelectorAll('.plane-card').forEach(e=>e.onclick=()=>setPlane(e.dataset.plane));document.querySelectorAll('.mode').forEach(e=>e.onclick=()=>{mode=e.dataset.mode;document.querySelectorAll('.mode').forEach(x=>x.classList.toggle('selected',x===e));document.getElementById('modeLabel').textContent=mode==='landing'?'TAKEOFF / LANDING':'MERGE / DOGFIGHT'});
document.getElementById('startBtn').onclick=()=>{document.getElementById('overlay').classList.add('hidden');running=true;paused=false;reset();startAudio()};
function startAudio(){try{audio=audio||new(window.AudioContext||window.webkitAudioContext)();if(audio.state==='suspended')audio.resume();if(!osc){osc=audio.createOscillator();gain=audio.createGain();windGain=audio.createGain();let filter=audio.createBiquadFilter();filter.type='lowpass';filter.frequency.value=460;osc.type='sawtooth';osc.frequency.value=75;osc.connect(filter);filter.connect(gain);gain.connect(audio.destination);gain.gain.value=.018;let noise=audio.createBufferSource(),buffer=audio.createBuffer(1,audio.sampleRate*2,audio.sampleRate),d=buffer.getChannelData(0);for(let i=0;i<d.length;i++)d[i]=Math.random()*2-1;noise.buffer=buffer;noise.loop=true;let nf=audio.createBiquadFilter();nf.type='lowpass';nf.frequency.value=850;noise.connect(nf);nf.connect(windGain);windGain.connect(audio.destination);windGain.gain.value=.001;osc.start();noise.start()}document.getElementById('soundState').textContent='AUDIO SYSTEM ACTIVE'}catch(e){}}
function showToast(t){let el=document.getElementById('toast');el.textContent=t;el.classList.add('show');setTimeout(()=>el.classList.remove('show'),2400)}
window.addEventListener('keydown',e=>{keys[e.code]=true;if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Space'].includes(e.code))e.preventDefault();if(e.code==='Space'&&running){paused=!paused;showToast(paused?'SIMULATION PAUSED':'FLIGHT RESUMED')}if(e.code==='KeyR'&&running){reset();showToast('AIRCRAFT RESET')}if(e.code==='Escape'&&running){document.getElementById('overlay').classList.remove('hidden');running=false}});window.addEventListener('keyup',e=>keys[e.code]=false);canvas.addEventListener('pointerdown',e=>{mouseDown=true;mouseDX=0;mouseDY=0;canvas.setPointerCapture(e.pointerId)});canvas.addEventListener('pointerup',()=>mouseDown=false);canvas.addEventListener('pointermove',e=>{if(mouseDown){mouseDX+=(e.movementX||0);mouseDY+=(e.movementY||0)}});
function update(dt){if(!running||paused)return;let p=planes[selected],speed=Math.hypot(s.vx,s.vy,s.vz),rho=Math.exp(-Math.max(0,s.y)/8500),q=.5*1.225*rho*speed*speed;let pitchIn=(keys.KeyW||keys.ArrowUp?1:0)-(keys.KeyS||keys.ArrowDown?1:0),rollIn=(keys.KeyD||keys.ArrowRight?1:0)-(keys.KeyA||keys.ArrowLeft?1:0),yawIn=(keys.KeyE?1:0)-(keys.KeyQ?1:0);if(mouseDown){rollIn=clamp(rollIn+mouseDX*.009,-1,1);pitchIn=clamp(pitchIn-mouseDY*.008,-1,1);mouseDX*=.35;mouseDY*=.35}if(keys.ShiftLeft||keys.ShiftRight)s.throttle=Math.min(1,s.throttle+dt*.32);if(keys.ControlLeft||keys.ControlRight)s.throttle=Math.max(0,s.throttle-dt*.32);
let rollAccel=(rollIn*p.roll-s.rollRate*2.6);s.rollRate+=rollAccel*dt;s.roll+=s.rollRate*dt;s.roll=clamp(s.roll,-1.35,1.35);let pitchAccel=pitchIn*p.pitch-s.pitchRate*1.8;s.pitchRate+=pitchAccel*dt;s.pitch+=s.pitchRate*dt;s.pitch=clamp(s.pitch,-.55,.62);
let fwd={x:Math.sin(s.yaw)*Math.cos(s.pitch),y:Math.sin(s.pitch),z:Math.cos(s.yaw)*Math.cos(s.pitch)},right={x:Math.cos(s.yaw),y:0,z:-Math.sin(s.yaw)},up={x:-Math.sin(s.yaw)*Math.sin(s.pitch),y:Math.cos(s.pitch),z:-Math.cos(s.yaw)*Math.sin(s.pitch)};let c=Math.cos(s.roll),sn=Math.sin(s.roll),upR={x:up.x*c+right.x*sn,y:up.y*c,z:up.z*c+right.z*sn},rightR={x:right.x*c-up.x*sn,y:-up.y*sn,z:right.z*c-up.z*sn};
let velForward=s.vx*fwd.x+s.vy*fwd.y+s.vz*fwd.z,flightPath=Math.atan2(s.vy,Math.max(1,Math.hypot(s.vx,s.vz))),alpha=clamp(s.pitch-flightPath,-.5,.5);s.aoa=alpha*180/Math.PI;let cl=clamp(.18+alpha*p.cl,-.72,1.55);s.stall=alpha>p.stall*Math.PI/180; if(s.stall)cl*=Math.max(.34,1-(alpha-p.stall*Math.PI/180)*2.8);let lift=q*p.area*cl;let maxLift=p.mass*9.81*5.6;lift=Math.min(lift,maxLift);let induced=cl*cl*.042,drag=q*p.area*(p.cd+induced);let thrust=p.thrust*s.throttle*Math.max(.48,rho**.7);let fx=fwd.x*thrust-upR.x*lift-(speed>1?s.vx/speed*drag:0),fy=fwd.y*thrust+upR.y*lift-p.mass*9.81-(speed>1?s.vy/speed*drag:0),fz=fwd.z*thrust+upR.z*lift-(speed>1?s.vz/speed*drag:0);
// coordinated turn and rudder inertia
let coordinated=Math.tan(s.roll)*9.81/Math.max(35,speed);let desiredYaw=yawIn*.65+coordinated;s.yawRate+=(desiredYaw-s.yawRate)*Math.min(1,dt*1.7);s.yaw+=s.yawRate*dt;
// Ground roll and tire friction
let gh=ground(s.x,s.z);if(s.y<=gh+2&&speed<95){let forwardGround=s.vx*fwd.x+s.vz*fwd.z;let ff=forwardGround>0?Math.min(1,(thrust/p.mass)*.65):.1;fx-=s.vx*p.mass*(.12+ff*.06);fz-=s.vz*p.mass*(.12+ff*.06);if(s.y<gh+1){s.vy=Math.max(0,s.vy);fy+=p.mass*9.81;if(speed>75)fy+=p.mass*9.81*.38;}}
let ax=fx/p.mass,ay=fy/p.mass,az=fz/p.mass;s.vx+=ax*dt;s.vy+=ay*dt;s.vz+=az*dt;s.x+=s.vx*dt;s.y+=s.vy*dt;s.z+=s.vz*dt;s.g=clamp(lift/(p.mass*9.81),-2,9);
let floor=ground(s.x,s.z);if(s.y<floor+1){let sink=-s.vy;let runway=Math.abs(s.z)<83&&s.x>-980&&s.x<850;if(runway&&sink<4.3&&speed>35&&speed<90&&s.pitch>-.12){s.y=floor+1;s.vy=0;s.vx*=.992;s.vz*=.992;if(!s.landed){s.landed=true;showToast('LANDING ACCEPTED · +100');document.getElementById('flightStatus').textContent='LANDED · RUNWAY 09/27'}}else if(s.y>floor-10||sink>0){s.y=floor+1;s.vy=Math.max(0,s.vy)*-.12;s.vx*=.7;s.vz*=.7;if(speed>40){document.getElementById('flightStatus').textContent='TERRAIN IMPACT · R TO RESET';s.throttle=0}}}
if(mode==='dogfight'){s.targetX+=s.targetVX*dt;s.targetZ-=35*dt;s.targetY=1050+Math.sin(performance.now()*.0005)*170;if(s.targetX< -1700){s.targetX=1700;s.targetZ=1500}let d=Math.hypot(s.targetX-s.x,s.targetY-s.y,s.targetZ-s.z);if(d<120){document.getElementById('flightStatus').textContent='GUNS · BANDIT IN RANGE'}else document.getElementById('flightStatus').textContent='BANDIT RANGE · '+Math.round(d/10)*10+' M'}
if(s.y>17000){s.y=17000;s.vy=Math.min(0,s.vy)}if(audio&&osc){osc.frequency.setTargetAtTime(55+s.throttle*125+speed*.025,audio.currentTime,.15);gain.gain.setTargetAtTime(.009+s.throttle*.025,audio.currentTime,.2);windGain.gain.setTargetAtTime(Math.min(.045,speed/18000),audio.currentTime,.2)}updateHUD(speed,rho);}
function clamp(v,a,b){return Math.max(a,Math.min(b,v))}
function updateHUD(speed,rho){let alt=Math.max(0,s.y-ground(s.x,s.z)),kts=speed*1.94384,ft=alt*3.28084,vs=s.vy*196.85;document.getElementById('speed').textContent=String(Math.round(kts)).padStart(3,'0');document.getElementById('alt').textContent=String(Math.round(ft)).padStart(4,'0');document.getElementById('vs').textContent=(vs>=0?'+':'')+String(Math.round(vs)).padStart(3,'0');document.getElementById('hdg').textContent=String(Math.round((s.yaw*180/Math.PI+360)%360)).padStart(3,'0')+'°';document.getElementById('throttleNum').textContent=Math.round(s.throttle*100)+'%';document.getElementById('throttleBar').style.width=(s.throttle*100)+'%';document.getElementById('gload').textContent=s.g.toFixed(1)+' G';document.getElementById('gBar').style.width=Math.min(100,s.g/6*100)+'%';document.getElementById('aoa').textContent=Math.round(s.aoa)+'°'+(s.stall?' · STALL':'');document.getElementById('runwayDistance').textContent=Math.round(Math.hypot(s.x+70,s.z)/100)*100+' M';document.getElementById('gearStatus').textContent=s.landed?'ON GROUND':'GEAR DOWN';document.getElementById('gearStatus').style.color=s.landed?'var(--amber)':'var(--cyan)';document.getElementById('weatherChip').innerHTML='WX <b>'+ (s.y>9000?'THIN AIR':'VMC')+'</b>'}
function renderThree(){if(!threeReady)return false;let fwd=new THREE.Vector3(Math.sin(s.yaw)*Math.cos(s.pitch),Math.sin(s.pitch),Math.cos(s.yaw)*Math.cos(s.pitch)),right=new THREE.Vector3(Math.cos(s.yaw),0,-Math.sin(s.yaw)),up=new THREE.Vector3().crossVectors(fwd,right).normalize();right.applyAxisAngle(fwd,s.roll);up.applyAxisAngle(fwd,s.roll);plane3.position.set(s.x,s.y,s.z);let m=new THREE.Matrix4().makeBasis(right,up,fwd.clone().negate());plane3.quaternion.setFromRotationMatrix(m);let camPos=new THREE.Vector3(s.x-fwd.x*25+up.x*7,s.y-fwd.y*25+up.y*7,s.z-fwd.z*25+up.z*7);camera.position.copy(camPos);let look=camPos.clone().add(fwd.clone().multiplyScalar(130));camera.up.copy(up);camera.lookAt(look);let ex=plane3.getObjectByName('exhaust');if(ex)ex.scale.setScalar(.35+s.throttle*1.5);renderer.render(scene3,camera);return true}
// Standalone projected landscape renderer used if WebGL is unavailable.
function renderFallback(){let sky=ctx.createLinearGradient(0,0,0,H*.68);sky.addColorStop(0,'#43809b');sky.addColorStop(.52,'#94bdc6');sky.addColorStop(1,'#e5d5af');ctx.fillStyle=sky;ctx.fillRect(0,0,W,H);let horizon=H*.43-s.pitch*H*.7;let sun=ctx.createRadialGradient(W*.72,H*.19,4,W*.72,H*.19,180);sun.addColorStop(0,'rgba(255,239,190,.55)');sun.addColorStop(1,'rgba(255,220,155,0)');ctx.fillStyle=sun;ctx.fillRect(W*.5,0,W*.5,H*.55);
// layered distant ridges
for(let layer=0;layer<4;layer++){ctx.beginPath();let base=horizon+layer*H*.06;ctx.moveTo(0,H);for(let x=0;x<=W+12;x+=12){let wx=s.x+(x-W/2)*4+layer*210;let v=Math.sin(wx*.0017+layer)*.45+Math.sin(wx*.0041+4)*.23+Math.cos(wx*.0007)*.32;let y=base-v*(55+layer*48)-Math.max(0,Math.sin(wx*.00033+layer))*70;ctx.lineTo(x,y)}ctx.lineTo(W,H);ctx.closePath();ctx.fillStyle=['#9aaea5','#7b9688','#586f62','#3f5449'][layer];ctx.fill()}
// terrain perspective grid patches in front
let yaw=s.yaw,cs=Math.cos(yaw),sn=Math.sin(yaw),roll=s.roll,cr=Math.cos(roll),sr=Math.sin(roll),fov=H*1.08;function project(wx,wz,wy){let dx=wx-s.x,dz=wz-s.z;let forward=dx*sn+dz*cs,side=dx*cs-dz*sn;let dy=wy-s.y;let vertical=dy*Math.cos(s.pitch)-forward*Math.sin(s.pitch),depth=forward*Math.cos(s.pitch)+dy*Math.sin(s.pitch);if(depth<25)return null;let sx=W/2+(side*cr-vertical*sr)*fov/depth,sy=horizon+(vertical*cr+side*sr)*fov/depth;return {x:sx,y:sy,d:depth}}
let grid=[];for(let iz=0;iz<25;iz++){let d=90+iz*iz*27;let row=[];for(let ix=0;ix<=28;ix++){let side=(ix-14)*d*.105;let wx=s.x+sn*d+cs*side,wz=s.z+cs*d-sn*side;let h=ground(wx,wz);row.push({wx,wz,h,p:project(wx,wz,h)})}grid.push(row)}for(let iz=grid.length-2;iz>=0;iz--){for(let ix=0;ix<28;ix++){let a=grid[iz][ix],b=grid[iz][ix+1],c=grid[iz+1][ix+1],d=grid[iz+1][ix];if(!a.p||!b.p||!c.p||!d.p)continue;let elev=(a.h+b.h+c.h+d.h)/4;let l=clamp(.26+(elev+100)/1700,.25,.78);let color=elev>1000?`rgb(${Math.round(110*l)},${Math.round(118*l)},${Math.round(111*l)})`:`rgb(${Math.round((53+elev*.035)*l)},${Math.round((75+elev*.045)*l)},${Math.round((50+elev*.022)*l)})`;ctx.fillStyle=color;ctx.beginPath();ctx.moveTo(a.p.x,a.p.y);ctx.lineTo(b.p.x,b.p.y);ctx.lineTo(c.p.x,c.p.y);ctx.lineTo(d.p.x,d.p.y);ctx.closePath();ctx.fill()}}
// runway seen from above, simplified
let rp1=project(-970, -75,ground(-970,-75)+5),rp2=project(830,-75,ground(830,-75)+5),rp3=project(830,75,ground(830,75)+5),rp4=project(-970,75,ground(-970,75)+5);if(rp1&&rp2&&rp3&&rp4){ctx.fillStyle='#43494a';ctx.beginPath();[rp1,rp2,rp3,rp4].forEach((p,i)=>i?ctx.lineTo(p.x,p.y):ctx.moveTo(p.x,p.y));ctx.closePath();ctx.fill();ctx.strokeStyle='rgba(240,239,219,.8)';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(rp1.x,rp1.y);ctx.lineTo(rp4.x,rp4.y);ctx.moveTo(rp2.x,rp2.y);ctx.lineTo(rp3.x,rp3.y);ctx.stroke()}
// stylized own aircraft silhouette
ctx.save();ctx.translate(W/2,H*.77);ctx.rotate(s.roll*.48);ctx.fillStyle='#182a30';ctx.strokeStyle='rgba(210,241,244,.45)';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(0,-31);ctx.lineTo(9,9);ctx.lineTo(43,25);ctx.lineTo(42,31);ctx.lineTo(7,23);ctx.lineTo(5,46);ctx.lineTo(18,52);ctx.lineTo(18,56);ctx.lineTo(0,51);ctx.lineTo(-18,56);ctx.lineTo(-18,52);ctx.lineTo(-5,46);ctx.lineTo(-7,23);ctx.lineTo(-42,31);ctx.lineTo(-43,25);ctx.lineTo(-9,9);ctx.closePath();ctx.fill();ctx.stroke();ctx.restore();}
function frame(now){let dt=Math.min(.04,(now-last)/1000);last=now;fpsCount++;if(now-fpsTime>700){document.getElementById('fps').textContent=Math.round(fpsCount*1000/(now-fpsTime));fpsCount=0;fpsTime=now}update(dt);if(!renderThree())renderFallback();requestAnimationFrame(frame)}
initThree();resize();updateHUD(0,1);requestAnimationFrame(frame);
})();
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-6-luna","provider":"openrouter","persona":"full","sessionId":"49518212-bacd-41da-802d-08ba30825e51","tokensIn":105774,"tokensOut":12910,"tokensTotal":118684,"cost":0.009541275000000002,"turns":7,"toolCalls":7,"failedToolCalls":0,"timestamp":"2026-09-23T00:27:40.964Z"} -->