← Flight Simulator results

Flight Simulator

Qwen 3.6 35b a3b · 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 66,279 bytes · SHA-256 13182c894cf1
<!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 Simulator</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; font-family: 'Courier New', monospace; }
#canvas-container { width: 100%; height: 100%; position: relative; }
canvas { display: block; width: 100%; height: 100%; }

/* HUD Overlay */
#hud {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 10; display: none;
}
.hud-panel {
  position: absolute; background: rgba(0, 20, 40, 0.6);
  border: 1px solid rgba(0, 200, 255, 0.3); border-radius: 4px;
  padding: 8px 14px; color: #0cf; font-size: 13px; line-height: 1.6;
}
#hud-left { top: 20px; left: 20px; }
#hud-right { top: 20px; right: 20px; text-align: right; }
#hud-center { bottom: 20px; left: 50%; transform: translateX(-50%); text-align: center; }
#hud-bottom { bottom: 20px; left: 20px; }
.hud-label { color: rgba(0, 200, 255, 0.6); font-size: 10px; text-transform: uppercase; letter-spacing: 2px; }
.hud-value { color: #0cf; font-size: 20px; font-weight: bold; }
.hud-bar { width: 120px; height: 6px; background: rgba(0, 200, 255, 0.15); border-radius: 3px; margin-top: 2px; }
.hud-bar-fill { height: 100%; border-radius: 3px; transition: width 0.1s; }
#hud-center .hud-value { font-size: 28px; text-shadow: 0 0 20px rgba(0, 200, 255, 0.5); }

/* Crosshair */
#crosshair {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 40px; height: 40px; pointer-events: none;
}
#crosshair::before, #crosshair::after {
  content: ''; position: absolute; background: rgba(0, 255, 150, 0.6);
}
#crosshair::before { width: 20px; height: 2px; top: 19px; left: 10px; }
#crosshair::after { width: 2px; height: 20px; top: 10px; left: 19px; }

/* Pitch ladder */
#pitch-ladder {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 200px; height: 300px; pointer-events: none; opacity: 0.4;
}

/* Menu Screen */
#menu {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  z-index: 100; background: linear-gradient(180deg, #0a0a2e 0%, #1a1a3e 40%, #0d2137 100%);
}
#menu.hidden { display: none; }
#menu h1 {
  font-size: 64px; color: #0cf; text-shadow: 0 0 40px rgba(0, 200, 255, 0.5), 0 0 80px rgba(0, 200, 255, 0.2);
  margin-bottom: 8px; letter-spacing: 8px; font-weight: 300;
}
#menu .subtitle { color: rgba(0, 200, 255, 0.5); font-size: 14px; letter-spacing: 6px; margin-bottom: 50px; }
.menu-section { margin-bottom: 30px; width: 500px; max-width: 90vw; }
.menu-section h2 { color: rgba(0, 200, 255, 0.7); font-size: 12px; letter-spacing: 4px; text-transform: uppercase; margin-bottom: 12px; }
.aircraft-grid { display: flex; gap: 12px; }
.aircraft-card {
  flex: 1; padding: 16px; background: rgba(0, 200, 255, 0.05);
  border: 1px solid rgba(0, 200, 255, 0.15); border-radius: 6px;
  cursor: pointer; transition: all 0.3s; text-align: center;
}
.aircraft-card:hover { border-color: rgba(0, 200, 255, 0.5); background: rgba(0, 200, 255, 0.1); }
.aircraft-card.selected { border-color: #0cf; background: rgba(0, 200, 255, 0.15); box-shadow: 0 0 20px rgba(0, 200, 255, 0.2); }
.aircraft-card .name { color: #0cf; font-size: 16px; font-weight: bold; margin-bottom: 6px; }
.aircraft-card .desc { color: rgba(0, 200, 255, 0.5); font-size: 11px; line-height: 1.4; }
.aircraft-card .stats { margin-top: 10px; }
.stat-row { display: flex; justify-content: space-between; font-size: 10px; color: rgba(0, 200, 255, 0.6); margin: 3px 0; }
.stat-row .bar { width: 60px; height: 4px; background: rgba(0, 200, 255, 0.1); border-radius: 2px; overflow: hidden; }
.stat-row .bar-fill { height: 100%; background: #0cf; border-radius: 2px; }

.mode-grid { display: flex; gap: 12px; }
.mode-card {
  flex: 1; padding: 20px; background: rgba(0, 200, 255, 0.05);
  border: 1px solid rgba(0, 200, 255, 0.15); border-radius: 6px;
  cursor: pointer; transition: all 0.3s; text-align: center;
}
.mode-card:hover { border-color: rgba(0, 200, 255, 0.5); background: rgba(0, 200, 255, 0.1); }
.mode-card.selected { border-color: #0cf; background: rgba(0, 200, 255, 0.15); box-shadow: 0 0 20px rgba(0, 200, 255, 0.2); }
.mode-card .name { color: #0cf; font-size: 18px; font-weight: bold; margin-bottom: 6px; }
.mode-card .desc { color: rgba(0, 200, 255, 0.5); font-size: 11px; }

#start-btn {
  margin-top: 30px; padding: 14px 60px; background: rgba(0, 200, 255, 0.15);
  border: 1px solid #0cf; border-radius: 4px; color: #0cf; font-size: 16px;
  font-family: 'Courier New', monospace; letter-spacing: 4px; cursor: pointer;
  transition: all 0.3s; text-transform: uppercase;
}
#start-btn:hover { background: rgba(0, 200, 255, 0.3); box-shadow: 0 0 30px rgba(0, 200, 255, 0.3); }
#start-btn:disabled { opacity: 0.3; cursor: default; }

/* Controls help */
#controls-help {
  position: absolute; bottom: 60px; left: 50%; transform: translateX(-50%);
  color: rgba(0, 200, 255, 0.4); font-size: 11px; text-align: center;
  letter-spacing: 1px; pointer-events: none;
}
.key { display: inline-block; padding: 2px 6px; border: 1px solid rgba(0, 200, 255, 0.3); border-radius: 3px; margin: 0 2px; font-size: 10px; }

/* Mission overlay */
#mission-overlay {
  position: absolute; top: 100px; left: 50%; transform: translateX(-50%);
  color: #0cf; font-size: 18px; letter-spacing: 3px; text-align: center;
  pointer-events: none; opacity: 0; transition: opacity 0.5s;
  text-shadow: 0 0 20px rgba(0, 200, 255, 0.5);
}
#mission-overlay.visible { opacity: 1; }

/* Stall warning */
#stall-warning {
  position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%);
  color: #f44; font-size: 24px; letter-spacing: 4px; pointer-events: none;
  opacity: 0; text-shadow: 0 0 30px rgba(255, 0, 0, 0.5);
  animation: none;
}
#stall-warning.active { opacity: 1; animation: blink 0.3s infinite; }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }

/* G-force indicator */
#g-indicator {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 60px; height: 100px; pointer-events: none; opacity: 0.3;
}
#g-indicator .tick { position: absolute; left: 50%; width: 8px; height: 1px; background: #0cf; transform: translateX(-50%); }
#g-indicator .needle {
  position: absolute; bottom: 50%; left: 50%; width: 2px; height: 40px;
  background: #f44; transform-origin: bottom center; transform: translateX(-50%) rotate(0deg);
  transition: transform 0.1s;
}

/* Dogfight markers */
#enemy-marker {
  position: absolute; width: 20px; height: 20px; pointer-events: none; opacity: 0;
}
#enemy-marker::before {
  content: ''; position: absolute; width: 100%; height: 100%;
  border: 2px solid #f44; border-radius: 50%;
  animation: pulse 1s infinite;
}
#enemy-marker::after {
  content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 4px; height: 4px; background: #f44; border-radius: 50%;
}
@keyframes pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.5; } }

/* Minimap */
#minimap {
  position: absolute; bottom: 20px; right: 20px; width: 150px; height: 150px;
  border: 1px solid rgba(0, 200, 255, 0.3); border-radius: 4px;
  background: rgba(0, 20, 40, 0.6); pointer-events: none;
}
#minimap canvas { width: 100%; height: 100%; border-radius: 4px; }

/* Loading */
#loading {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  background: #000; z-index: 200; color: #0cf; font-size: 14px; letter-spacing: 3px;
}
#loading.hidden { display: none; }
</style>
</head>
<body>

<div id="loading">INITIALIZING AERO SYSTEMS...</div>

<div id="canvas-container"></div>

<div id="menu">
  <h1>APEX AERO</h1>
  <div class="subtitle">FLIGHT SIMULATOR</div>

  <div class="menu-section">
    <h2>Select Aircraft</h2>
    <div class="aircraft-grid">
      <div class="aircraft-card selected" data-plane="fighter">
        <div class="name">F-22 RAPTOR</div>
        <div class="desc">Air superiority fighter. High thrust, excellent maneuverability, moderate lift.</div>
        <div class="stats">
          <div class="stat-row"><span>Speed</span><div class="bar"><div class="bar-fill" style="width:95%"></div></div></div>
          <div class="stat-row"><span>Thrust</span><div class="bar"><div class="bar-fill" style="width:90%"></div></div></div>
          <div class="stat-row"><span>Lift</span><div class="bar"><div class="bar-fill" style="width:60%"></div></div></div>
          <div class="stat-row"><span>Maneuver</span><div class="bar"><div class="bar-fill" style="width:95%"></div></div></div>
        </div>
      </div>
      <div class="aircraft-card" data-plane="glider">
        <div class="name">DG-1 GLIDER</div>
        <div class="desc">High-lift sailplane. Slow but efficient. Excellent climb in thermal updrafts.</div>
        <div class="stats">
          <div class="stat-row"><span>Speed</span><div class="bar"><div class="bar-fill" style="width:20%"></div></div></div>
          <div class="stat-row"><span>Thrust</span><div class="bar"><div class="bar-fill" style="width:0%"></div></div></div>
          <div class="stat-row"><span>Lift</span><div class="bar"><div class="bar-fill" style="width:100%"></div></div></div>
          <div class="stat-row"><span>Maneuver</span><div class="bar"><div class="bar-fill" style="width:15%"></div></div></div>
        </div>
      </div>
      <div class="aircraft-card" data-plane="bomber">
        <div class="name">B-17 FORTRESS</div>
        <div class="desc">Heavy bomber. High lift, slow acceleration, massive turning radius. Needs long runway.</div>
        <div class="stats">
          <div class="stat-row"><span>Speed</span><div class="bar"><div class="bar-fill" style="width:40%"></div></div></div>
          <div class="stat-row"><span>Thrust</span><div class="bar"><div class="bar-fill" style="width:50%"></div></div></div>
          <div class="stat-row"><span>Lift</span><div class="bar"><div class="bar-fill" style="width:95%"></div></div></div>
          <div class="stat-row"><span>Maneuver</span><div class="bar"><div class="bar-fill" style="width:25%"></div></div></div>
        </div>
      </div>
    </div>
  </div>

  <div class="menu-section">
    <h2>Select Scenario</h2>
    <div class="mode-grid">
      <div class="mode-card selected" data-mode="landing">
        <div class="name">✈ TAKEOFF & LANDING</div>
        <div class="desc">Master precision flight. Take off from runway and return for a smooth landing.</div>
      </div>
      <div class="mode-card" data-mode="dogfight">
        <div class="name">⚔ DOGFIGHT</div>
        <div class="desc">High-G combat. Pursue and down enemy aircraft using advanced maneuvering.</div>
      </div>
    </div>
  </div>

  <button id="start-btn">LAUNCH</button>
</div>

<div id="hud">
  <div class="hud-panel" id="hud-left">
    <div class="hud-label">Airspeed</div>
    <div class="hud-value" id="hud-speed">0 <small>KT</small></div>
    <div class="hud-label" style="margin-top:8px">Altitude (AGL)</div>
    <div class="hud-value" id="hud-alt">0 <small>FT</small></div>
    <div class="hud-label" style="margin-top:8px">Vertical Speed</div>
    <div class="hud-value" id="hud-vspeed">0 <small>FT/MIN</small></div>
  </div>

  <div class="hud-panel" id="hud-right">
    <div class="hud-label">Throttle</div>
    <div class="hud-value" id="hud-throttle">0 <small>%</small></div>
    <div class="hud-bar" style="margin-top:4px"><div class="hud-bar-fill" id="hud-throttle-bar" style="width:0%;background:#0cf"></div></div>
    <div class="hud-label" style="margin-top:8px">RPM</div>
    <div class="hud-value" id="hud-rpm">0 <small>×</small></div>
    <div class="hud-label" style="margin-top:8px">Angle of Attack</div>
    <div class="hud-value" id="hud-aoa">0 <small>°</small></div>
    <div class="hud-label" style="margin-top:8px">G-Force</div>
    <div class="hud-value" id="hud-g">1.0 <small>G</small></div>
  </div>

  <div class="hud-panel" id="hud-center">
    <div class="hud-label">Attitude</div>
    <div class="hud-value" id="hud-pitch-roll">0° / 0°</div>
    <div class="hud-label" style="margin-top:8px">Heading</div>
    <div class="hud-value" id="hud-heading">360 <small>°</small></div>
  </div>

  <div class="hud-panel" id="hud-bottom">
    <div class="hud-label">Engine Temp</div>
    <div class="hud-bar"><div class="hud-bar-fill" id="hud-temp-bar" style="width:30%;background:#0f0"></div></div>
    <div id="hud-temp-val" style="font-size:11px;margin-top:2px">120°C</div>
  </div>

  <div id="crosshair"></div>
  <div id="stall-warning">STALL WARNING</div>
</div>

<div id="controls-help">
  <span class="key">W</span><span class="key">S</span> Pitch &nbsp;
  <span class="key">A</span><span class="key">D</span> Roll &nbsp;
  <span class="key">Q</span><span class="key">E</span> Yaw &nbsp;
  <span class="key">↑</span><span class="key">↓</span> Throttle &nbsp;
  <span class="key">C</span> Camera &nbsp;
  <span class="key">R</span> Reset &nbsp;
  <span class="key">ESC</span> Menu
</div>

<div id="mission-overlay"></div>

<div id="minimap"><canvas id="minimap-canvas" width="150" height="150"></canvas></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// ============================================================
// APEX AERO — Flight Simulator
// ============================================================

(function() {
'use strict';

// ==================== CONFIGURATION ====================
const CONFIG = {
  // Physics
  gravity: 9.81,
  seaLevelDensity: 1.225,
  atmosphereScaleHeight: 8500,
  maxGForce: 9.0,
  minGForce: -3.0,
  stallAOA: 16, // degrees
  maxAOA: 30,
  // Terrain
  terrainSize: 80000,
  terrainResolution: 256,
  terrainLOD: 2,
  // Camera
  camFOV: 65,
  camNear: 0.5,
  camFar: 100000,
  // Audio
  audioEnabled: true,
  // Rendering
  shadowMapSize: 2048,
  fogNear: 5000,
  fogFar: 60000,
};

// ==================== AIRCRAFT DEFINITIONS ====================
const AIRCRAFT = {
  fighter: {
    name: 'F-22 RAPTOR',
    mass: 19700,           // kg
    wingArea: 78.04,       // m²
    wingSpan: 13.56,       // m
    aspectRatio: 3.1,
    liftCoeff: 0.08,       // per degree
    liftMax: 2.2,
    dragCoeff: 0.025,
    inducedDragFactor: 0.05,
    maxThrust: 240000,     // N
    idleThrust: 5000,
    thrustResponse: 0.8,
    pitchRate: 2.5,
    rollRate: 3.0,
    yawRate: 1.8,
    pitchResponse: 0.4,
    rollResponse: 0.35,
    yawResponse: 0.5,
    stallSpeed: 85,        // m/s
    maxSpeed: 340,         // m/s
    color: 0x4a6a8a,
    accentColor: 0x0cf,
    length: 18,
    type: 'fighter',
  },
  glider: {
    name: 'DG-1 GLIDER',
    mass: 500,
    wingArea: 12.0,
    wingSpan: 15.0,
    aspectRatio: 25,
    liftCoeff: 0.12,
    liftMax: 1.8,
    dragCoeff: 0.012,
    inducedDragFactor: 0.004,
    maxThrust: 0,
    idleThrust: 0,
    thrustResponse: 0,
    pitchRate: 0.8,
    rollRate: 0.6,
    yawRate: 0.4,
    pitchResponse: 0.6,
    rollResponse: 0.5,
    yawResponse: 0.7,
    stallSpeed: 30,
    maxSpeed: 70,
    color: 0xe8e8e8,
    accentColor: 0xff8,
    length: 7,
    type: 'glider',
  },
  bomber: {
    name: 'B-17 FORTRESS',
    mass: 28000,
    wingArea: 134.3,
    wingSpan: 31.62,
    aspectRatio: 7.4,
    liftCoeff: 0.10,
    liftMax: 2.0,
    dragCoeff: 0.035,
    inducedDragFactor: 0.012,
    maxThrust: 116000,
    idleThrust: 3000,
    thrustResponse: 0.3,
    pitchRate: 1.2,
    rollRate: 1.0,
    yawRate: 0.6,
    pitchResponse: 0.5,
    rollResponse: 0.45,
    yawResponse: 0.6,
    stallSpeed: 110,
    maxSpeed: 200,
    color: 0x5a5a4a,
    accentColor: 0x8a8,
    length: 22,
    type: 'bomber',
  },
};

// ==================== GAME STATE ====================
let gameState = {
  mode: null,          // 'landing' or 'dogfight'
  selectedPlane: 'fighter',
  running: false,
  paused: false,
  cameraMode: 0,       // 0=chase, 1=cockpit, 2=free
  time: 0,
  dt: 0,
  lastTime: 0,

  // Aircraft physics state
  pos: new THREE.Vector3(0, 10, 0),
  vel: new THREE.Vector3(0, 0, 0),
  quat: new THREE.Quaternion(),
  euler: new THREE.Euler(0, 0, 0, 'YXZ'),

  // Control surface states (smooth interpolation)
  throttle: 0,
  throttleTarget: 0,
  pitchInput: 0,
  pitchTarget: 0,
  rollInput: 0,
  rollTarget: 0,
  yawInput: 0,
  yawTarget: 0,

  // Control surface deflections
  elevator: 0,
  aileron: 0,
  rudder: 0,
  elevatorTarget: 0,
  aileronTarget: 0,
  rudderTarget: 0,

  // Derived physics
  airspeed: 0,
  aoa: 0,
  sideslip: 0,
  lift: 0,
  drag: 0,
  thrust: 0,
  gForce: 1.0,
  engineTemp: 120,

  // Landing mode state
  onGround: false,
  landingGears: true,
  flapAngle: 0,
  runwayHit: false,
  landed: false,
  takeoffSpeedReached: false,
  score: 0,

  // Dogfight mode state
  enemyPos: new THREE.Vector3(),
  enemyVel: new THREE.Vector3(),
  enemyQuat: new THREE.Quaternion(),
  enemyHP: 100,
  enemyTarget: new THREE.Vector3(),
  enemyAggression: 0.5,
  kills: 0,
  shotsFired: 0,
  shotsHit: 0,
  ammo: 500,
  heat: 0,
  gunCooldown: 0,
};

// ==================== INPUT ====================
const keys = {};
const mouse = { x: 0, y: 0, dx: 0, dy: 0, locked: false };

document.addEventListener('keydown', (e) => {
  keys[e.code] = true;
  if (e.code === 'Escape' && gameState.running) {
    showMenu();
  }
});
document.addEventListener('keyup', (e) => { keys[e.code] = false; });

document.addEventListener('mousemove', (e) => {
  if (mouse.locked) {
    mouse.dx += e.movementX;
    mouse.dy += e.movementY;
  }
});

document.addEventListener('mousedown', (e) => {
  if (gameState.running && !mouse.locked) {
    document.body.requestPointerLock();
  }
});
document.addEventListener('pointerlockchange', () => {
  mouse.locked = document.pointerLockElement === document.body;
});

// ==================== THREE.JS SETUP ====================
let scene, camera, renderer, clock;
let terrainMesh, terrainLOD;
let sunLight, ambientLight, skyMesh;
let aircraftMesh, aircraftGroup;
let runwayMesh, runwayLights = [];
let clouds = [];
let particles = [];
let minimapCtx;
let enemyMesh, enemyGroup;
let muzzleFlash;
let wakeParticles;

function initThreeJS() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x87CEEB);
  scene.fog = new THREE.FogExp2(0x87CEEB, 0.00004);

  camera = new THREE.PerspectiveCamera(CONFIG.camFOV, window.innerWidth / window.innerHeight, CONFIG.camNear, CONFIG.camFar);
  camera.position.set(0, 20, 50);

  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 1.2;
  document.getElementById('canvas-container').appendChild(renderer.domElement);

  clock = new THREE.Clock();

  // Lighting
  ambientLight = new THREE.AmbientLight(0x405070, 0.6);
  scene.add(ambientLight);

  sunLight = new THREE.DirectionalLight(0xfff5e0, 1.5);
  sunLight.position.set(5000, 8000, 3000);
  sunLight.castShadow = true;
  sunLight.shadow.mapSize.set(CONFIG.shadowMapSize, CONFIG.shadowMapSize);
  sunLight.shadow.camera.near = 100;
  sunLight.shadow.camera.far = 50000;
  sunLight.shadow.camera.left = -5000;
  sunLight.shadow.camera.right = 5000;
  sunLight.shadow.camera.top = 5000;
  sunLight.shadow.camera.bottom = -5000;
  scene.add(sunLight);

  const hemiLight = new THREE.HemisphereLight(0x87CEEB, 0x3a5a2a, 0.4);
  scene.add(hemiLight);

  // Sky dome
  const skyGeo = new THREE.SphereGeometry(80000, 32, 32);
  const skyMat = new THREE.ShaderMaterial({
    uniforms: {
      topColor: { value: new THREE.Color(0x0044aa) },
      bottomColor: { value: new THREE.Color(0xaaccff) },
      offset: { value: 20 },
      exponent: { value: 0.4 },
    },
    vertexShader: `
      varying vec3 vWorldPosition;
      void main() {
        vec4 worldPos = modelMatrix * vec4(position, 1.0);
        vWorldPosition = worldPos.xyz;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    `,
    fragmentShader: `
      uniform vec3 topColor;
      uniform vec3 bottomColor;
      uniform float offset;
      uniform float exponent;
      varying vec3 vWorldPosition;
      void main() {
        float h = normalize(vWorldPosition + offset).y;
        gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h, 0.0), exponent), 0.0)), 1.0);
      }
    `,
    side: THREE.BackSide,
  });
  skyMesh = new THREE.Mesh(skyGeo, skyMat);
  scene.add(skyMesh);

  // Minimap
  minimapCtx = document.getElementById('minimap-canvas').getContext('2d');

  // Resize
  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  });
}

// ==================== TERRAIN GENERATION ====================
let terrainHeights = [];
let terrainGeo;

function generateTerrain() {
  const size = CONFIG.terrainSize;
  const res = CONFIG.terrainResolution;
  const step = size / res;

  // Generate heightmap using layered noise
  const heightData = new Float32Array((res + 1) * (res + 1));

  // Simple multi-octave noise
  function noise2D(x, y) {
    const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
    return n - Math.floor(n);
  }

  function smoothNoise(x, y, scale) {
    const sx = x / scale;
    const sy = y / scale;
    const ix = Math.floor(sx);
    const iy = Math.floor(sy);
    const fx = sx - ix;
    const fy = sy - iy;
    const a = noise2D(ix, iy);
    const b = noise2D(ix + 1, iy);
    const c = noise2D(ix, iy + 1);
    const d = noise2D(ix + 1, iy + 1);
    const ux = fx * fx * (3 - 2 * fx);
    const uy = fy * fy * (3 - 2 * fy);
    return a * (1 - ux) * (1 - uy) + b * ux * (1 - uy) + c * (1 - ux) * uy + d * ux * uy;
  }

  function fbm(x, y, octaves) {
    let val = 0, amp = 1, freq = 1, max = 0;
    for (let i = 0; i < octaves; i++) {
      val += smoothNoise(x * freq, y * freq, 1.0) * amp;
      max += amp;
      amp *= 0.5;
      freq *= 2.0;
    }
    return val / max;
  }

  for (let iz = 0; iz <= res; iz++) {
    for (let ix = 0; ix <= res; ix++) {
      const x = ix * step - size / 2;
      const z = iz * step - size / 2;

      // Base terrain
      let h = fbm(x, z, 6) * 2000;

      // Mountains
      const mountainNoise = fbm(x * 0.001 + 100, z * 0.001 + 100, 4);
      if (mountainNoise > 0.55) {
        h += (mountainNoise - 0.55) * 6000;
      }

      // Valley carving
      const valleyNoise = Math.sin(x * 0.0003) * Math.cos(z * 0.0004);
      if (valleyNoise < -0.3) {
        h -= (valleyNoise + 0.3) * 800;
      }

      // Coastline / sea level
      if (h < 50) {
        h = 50 + (h - 50) * 0.3;
      }

      // Flatten runway area
      const distToRunway = Math.sqrt(
        Math.pow(x - 0, 2) + Math.pow(z - 2000, 2)
      );
      if (distToRunway < 500) {
        const blend = Math.max(0, 1 - distToRunway / 500);
        h = h * (1 - blend) + 100 * blend;
      }

      heightData[iz * (res + 1) + ix] = h;
    }
  }

  // Create Three.js terrain
  const geo = new THREE.PlaneGeometry(size, size, res, res);
  geo.rotateX(-Math.PI / 2);

  const pos = geo.attributes.position;
  const colors = new Float32Array(pos.count * 3);

  for (let i = 0; i < pos.count; i++) {
    const ix = i % (res + 1);
    const iz = Math.floor(i / (res + 1));
    const h = heightData[iz * (res + 1) + ix];
    pos.setY(i, h);

    // Vertex colors based on height
    const t = Math.max(0, Math.min(1, (h - 50) / 1500));
    let r, g, b;
    if (h < 80) {
      // Water
      r = 0.1; g = 0.3; b = 0.6;
    } else if (t < 0.1) {
      // Beach/sand
      r = 0.76; g = 0.7; b = 0.5;
    } else if (t < 0.5) {
      // Grass
      r = 0.2 + t * 0.5; g = 0.5 + t * 0.3; b = 0.1;
    } else if (t < 0.7) {
      // Forest
      r = 0.15; g = 0.35; b = 0.1;
    } else if (t < 0.85) {
      // Rock
      r = 0.4; g = 0.38; b = 0.35;
    } else {
      // Snow
      r = 0.9; g = 0.92; b = 0.95;
    }
    colors[i * 3] = r;
    colors[i * 3 + 1] = g;
    colors[i * 3 + 2] = b;
  }

  geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
  geo.computeVertexNormals();

  const mat = new THREE.MeshStandardMaterial({
    vertexColors: true,
    roughness: 0.8,
    metalness: 0.1,
    flatShading: false,
  });

  terrainMesh = new THREE.Mesh(geo, mat);
  terrainMesh.receiveShadow = true;
  scene.add(terrainMesh);

  // Water plane
  const waterGeo = new THREE.PlaneGeometry(size, size);
  waterGeo.rotateX(-Math.PI / 2);
  const waterMat = new THREE.MeshStandardMaterial({
    color: 0x1a5a8a,
    transparent: true,
    opacity: 0.7,
    roughness: 0.3,
    metalness: 0.8,
  });
  const water = new THREE.Mesh(waterGeo, waterMat);
  water.position.y = 45;
  scene.add(water);

  // Store heightmap for collision
  terrainHeights = heightData;
  terrainGeo = geo;
}

function getTerrainHeight(x, z) {
  const halfSize = CONFIG.terrainSize / 2;
  const res = CONFIG.terrainResolution;
  const step = CONFIG.terrainSize / res;

  const ix = Math.floor((x + halfSize) / step);
  const iz = Math.floor((z + halfSize) / step);

  if (ix < 0 || ix > res || iz < 0 || iz > res) return 0;

  const fx = (x + halfSize) / step - ix;
  const fz = (z + halfSize) / step - iz;

  const h00 = terrainHeights[iz * (res + 1) + ix];
  const h10 = terrainHeights[iz * (res + 1) + ix + 1];
  const h01 = terrainHeights[(iz + 1) * (res + 1) + ix];
  const h11 = terrainHeights[(iz + 1) * (res + 1) + ix + 1];

  return h00 * (1 - fx) * (1 - fz) + h10 * fx * (1 - fz) + h01 * (1 - fx) * fz + h11 * fx * fz;
}

// ==================== RUNWAY ====================
function createRunway() {
  const rwLength = 3000;
  const rwWidth = 60;

  const geo = new THREE.PlaneGeometry(rwWidth, rwLength, 1, 1);
  geo.rotateX(-Math.PI / 2);

  // Create runway texture via canvas
  const canvas = document.createElement('canvas');
  canvas.width = 512;
  canvas.height = 4096;
  const ctx = canvas.getContext('2d');

  // Base gray
  ctx.fillStyle = '#3a3a3a';
  ctx.fillRect(0, 0, 512, 4096);

  // Edge lines
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(10, 0, 8, 4096);
  ctx.fillRect(494, 0, 8, 4096);

  // Center dashes
  ctx.fillStyle = '#ffcc00';
  for (let y = 100; y < 4096; y += 200) {
    ctx.fillRect(250, y, 12, 100);
  }

  // Threshold markings
  ctx.fillStyle = '#ffffff';
  for (let i = 0; i < 6; i++) {
    ctx.fillRect(80 + i * 60, 50, 30, 150);
    ctx.fillRect(402 - i * 60, 3896, 30, 150);
  }

  const texture = new THREE.CanvasTexture(canvas);
  texture.wrapS = THREE.ClampToEdgeWrapping;
  texture.wrapT = THREE.ClampToEdgeWrapping;

  const mat = new THREE.MeshStandardMaterial({ map: texture, roughness: 0.9 });
  runwayMesh = new THREE.Mesh(geo, mat);
  runwayMesh.position.set(0, getTerrainHeight(0, 2000) + 1, 0);
  scene.add(runwayMesh);

  // Runway lights
  const lightGeo = new THREE.SphereGeometry(1.5, 8, 8);
  for (let i = 0; i < 40; i++) {
    const z = -rwLength / 2 + (rwLength / 39) * i;
    for (let side = -1; side <= 1; side += 2) {
      const lightMat = new THREE.MeshStandardMaterial({
        color: 0x00ff44,
        emissive: 0x00ff44,
        emissiveIntensity: 2,
      });
      const light = new THREE.Mesh(lightGeo, lightMat);
      light.position.set(side * rwWidth / 2, getTerrainHeight(side * rwWidth / 2, z) + 2, z + 2000);
      scene.add(light);
      runwayLights.push(light);
    }
  }
}

// ==================== AIRCRAFT MODEL ====================
function createAircraftModel(type) {
  if (aircraftGroup) {
    scene.remove(aircraftGroup);
    aircraftGroup.traverse(child => {
      if (child.geometry) child.geometry.dispose();
      if (child.material) {
        if (Array.isArray(child.material)) child.material.forEach(m => m.dispose());
        else child.material.dispose();
      }
    });
  }

  aircraftGroup = new THREE.Group();

  const bodyColor = AIRCRAFT[type].color;
  const accentCol = AIRCRAFT[type].accentColor;

  // Fuselage
  const fuselageGeo = new THREE.CylinderGeometry(0.8, 0.6, 12, 12);
  fuselageGeo.rotateZ(Math.PI / 2);
  const bodyMat = new THREE.MeshStandardMaterial({ color: bodyColor, roughness: 0.4, metalness: 0.6 });
  const fuselage = new THREE.Mesh(fuselageGeo, bodyMat);
  aircraftGroup.add(fuselage);

  // Nose cone
  const noseGeo = new THREE.ConeGeometry(0.8, 3, 12);
  noseGeo.rotateZ(-Math.PI / 2);
  const nose = new THREE.Mesh(noseGeo, bodyMat);
  nose.position.x = 7.5;
  aircraftGroup.add(nose);

  // Cockpit
  const cockpitGeo = new THREE.SphereGeometry(0.7, 12, 8, 0, Math.PI * 2, 0, Math.PI / 2);
  const cockpitMat = new THREE.MeshStandardMaterial({ color: 0x88ccff, roughness: 0.1, metalness: 0.9, transparent: true, opacity: 0.7 });
  const cockpit = new THREE.Mesh(cockpitGeo, cockpitMat);
  cockpit.position.set(2, 0.8, 0);
  cockpit.rotation.x = -0.3;
  aircraftGroup.add(cockpit);

  // Main wings
  const wingSpan = AIRCRAFT[type].wingSpan;
  const wingChord = 3.5;
  const wingGeo = new THREE.BoxGeometry(wingSpan, 0.15, wingChord);
  const wingMat = new THREE.MeshStandardMaterial({ color: bodyColor, roughness: 0.5, metalness: 0.5 });
  const wings = new THREE.Mesh(wingGeo, wingMat);
  wings.position.set(-1, 0.1, 0);
  aircraftGroup.add(wings);

  // Wing tips
  const tipGeo = new THREE.BoxGeometry(1, 0.15, wingChord * 0.6);
  for (let side = -1; side <= 1; side += 2) {
    const tip = new THREE.Mesh(tipGeo, wingMat);
    tip.position.set(-1, 0.1, side * wingSpan / 2);
    aircraftGroup.add(tip);
  }

  // Vertical stabilizer
  const vstabGeo = new THREE.BoxGeometry(1.5, 3, 0.15);
  const vstab = new THREE.Mesh(vstabGeo, wingMat);
  vstab.position.set(-6, 1.5, 0);
  vstab.rotation.z = 0.3;
  aircraftGroup.add(vstab);

  // Horizontal stabilizers
  const hstabGeo = new THREE.BoxGeometry(4, 0.1, 1.5);
  for (let side = -1; side <= 1; side += 2) {
    const hstab = new THREE.Mesh(hstabGeo, wingMat);
    hstab.position.set(-7, 0.3, side * 2);
    aircraftGroup.add(hstab);
  }

  // Tail fin accent
  const tailAccentGeo = new THREE.BoxGeometry(1.2, 2.5, 0.2);
  const tailAccentMat = new THREE.MeshStandardMaterial({ color: accentCol, roughness: 0.3, metalness: 0.7 });
  const tailAccent = new THREE.Mesh(tailAccentGeo, tailAccentMat);
  tailAccent.position.set(-6, 2.8, 0);
  tailAccent.rotation.z = 0.3;
  aircraftGroup.add(tailAccent);

  // Engines (for powered aircraft)
  if (AIRCRAFT[type].maxThrust > 0) {
    const engineGeo = new THREE.CylinderGeometry(0.5, 0.4, 3, 12);
    engineGeo.rotateZ(Math.PI / 2);
    const engineMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.6, metalness: 0.8 });

    if (type === 'bomber') {
      // 4 engines
      for (let i = -1; i <= 1; i += 2) {
        for (let j = -1; j <= 1; j += 2) {
          const eng = new THREE.Mesh(engineGeo, engineMat);
          eng.position.set(-3, -0.5 + j * 0.3, i * 4);
          aircraftGroup.add(eng);
        }
      }
    } else {
      // 1-2 engines
      const eng1 = new THREE.Mesh(engineGeo, engineMat);
      eng1.position.set(-2, -0.3, 2);
      aircraftGroup.add(eng1);
      if (type === 'fighter') {
        const eng2 = new THREE.Mesh(engineGeo, engineMat);
        eng2.position.set(-2, -0.3, -2);
        aircraftGroup.add(eng2);
      }
    }
  }

  // Landing gear
  const gearMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.7, metalness: 0.8 });

  // Nose gear
  const noseGearGeo = new THREE.CylinderGeometry(0.08, 0.08, 2, 6);
  const noseGear = new THREE.Mesh(noseGearGeo, gearMat);
  noseGear.position.set(5, -1.2, 0);
  aircraftGroup.add(noseGear);

  const noseWheelGeo = new THREE.SphereGeometry(0.3, 8, 8);
  const noseWheel = new THREE.Mesh(noseWheelGeo, gearMat);
  noseWheel.position.set(5, -2.2, 0);
  aircraftGroup.add(noseWheel);

  // Main gear
  for (let side = -1; side <= 1; side += 2) {
    const gearGeo = new THREE.CylinderGeometry(0.1, 0.1, 2.5, 6);
    const mainGear = new THREE.Mesh(gearGeo, gearMat);
    mainGear.position.set(-2, -1.5, side * 2.5);
    aircraftGroup.add(mainGear);

    const mainWheel = new THREE.Mesh(noseWheelGeo, gearMat);
    mainWheel.position.set(-2, -2.8, side * 2.5);
    aircraftGroup.add(mainWheel);
  }

  // Muzzle flash (for dogfight)
  const flashGeo = new THREE.SphereGeometry(0.3, 8, 8);
  const flashMat = new THREE.MeshBasicMaterial({ color: 0xffaa00, transparent: true, opacity: 0 });
  muzzleFlash = new THREE.Mesh(flashGeo, flashMat);
  muzzleFlash.position.set(10, 0, 0);
  aircraftGroup.add(muzzleFlash);

  scene.add(aircraftGroup);
}

// ==================== ENEMY AIRCRAFT ====================
function createEnemyModel() {
  if (enemyGroup) scene.remove(enemyGroup);
  enemyGroup = new THREE.Group();

  const bodyMat = new THREE.MeshStandardMaterial({ color: 0x6a2a2a, roughness: 0.5, metalness: 0.5 });
  const wingMat = new THREE.MeshStandardMaterial({ color: 0x5a2020, roughness: 0.5, metalness: 0.5 });

  // Simple enemy plane
  const bodyGeo = new THREE.CylinderGeometry(0.6, 0.5, 10, 10);
  bodyGeo.rotateZ(Math.PI / 2);
  const body = new THREE.Mesh(bodyGeo, bodyMat);
  enemyGroup.add(body);

  const noseGeo = new THREE.ConeGeometry(0.6, 2.5, 10);
  noseGeo.rotateZ(-Math.PI / 2);
  const nose = new THREE.Mesh(noseGeo, bodyMat);
  nose.position.x = 6.25;
  enemyGroup.add(nose);

  const wingGeo = new THREE.BoxGeometry(10, 0.1, 2.5);
  const wings = new THREE.Mesh(wingGeo, wingMat);
  wings.position.set(-1, 0, 0);
  enemyGroup.add(wings);

  const vstabGeo = new THREE.BoxGeometry(1.2, 2.5, 0.12);
  const vstab = new THREE.Mesh(vstabGeo, bodyMat);
  vstab.position.set(-5, 1.3, 0);
  vstab.rotation.z = 0.3;
  enemyGroup.add(vstab);

  // Red accent
  const accentGeo = new THREE.BoxGeometry(1, 2, 0.15);
  const accentMat = new THREE.MeshStandardMaterial({ color: 0xff0000, emissive: 0x440000 });
  const accent = new THREE.Mesh(accentGeo, accentMat);
  accent.position.set(-5, 2.5, 0);
  accent.rotation.z = 0.3;
  enemyGroup.add(accent);

  enemyGroup.visible = false;
  scene.add(enemyGroup);
}

// ==================== CLOUDS ====================
function createClouds() {
  const cloudGeo = new THREE.SphereGeometry(1, 8, 6);
  const cloudMat = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    transparent: true,
    opacity: 0.6,
    roughness: 1,
  });

  for (let i = 0; i < 200; i++) {
    const group = new THREE.Group();
    const numPuffs = 3 + Math.floor(Math.random() * 5);

    for (let j = 0; j < numPuffs; j++) {
      const puff = new THREE.Mesh(cloudGeo, cloudMat);
      puff.scale.set(
        15 + Math.random() * 30,
        8 + Math.random() * 12,
        15 + Math.random() * 30
      );
      puff.position.set(
        (Math.random() - 0.5) * 40,
        (Math.random() - 0.5) * 10,
        (Math.random() - 0.5) * 40
      );
      group.add(puff);
    }

    group.position.set(
      (Math.random() - 0.5) * 60000,
      2000 + Math.random() * 8000,
      (Math.random() - 0.5) * 60000
    );
    scene.add(group);
    clouds.push(group);
  }
}

// ==================== WAKE / PARTICLES ====================
function createWakeSystem() {
  const count = 500;
  const geo = new THREE.BufferGeometry();
  const positions = new Float32Array(count * 3);
  const lifetimes = new Float32Array(count);
  geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  geo.userData = { lifetimes: lifetimes, velocities: [] };

  const mat = new THREE.PointsMaterial({
    color: 0xffffff,
    size: 2,
    transparent: true,
    opacity: 0.3,
    depthWrite: false,
  });

  wakeParticles = new THREE.Points(geo, mat);
  wakeParticles.userData.maxCount = count;
  scene.add(wakeParticles);
}

function updateWake(dt) {
  if (!wakeParticles) return;
  const pos = wakeParticles.geometry.attributes.position;
  const life = wakeParticles.geometry.userData.lifetimes;
  const count = wakeParticles.userData.maxCount;

  // Update existing particles
  for (let i = 0; i < count; i++) {
    if (life[i] > 0) {
      life[i] -= dt * 0.5;
      pos.array[i * 3] += (wakeParticles.userData.velocities[i]?.x || 0) * dt;
      pos.array[i * 3 + 1] += (wakeParticles.userData.velocities[i]?.y || 0) * dt;
      pos.array[i * 3 + 2] += (wakeParticles.userData.velocities[i]?.z || 0) * dt;
    } else {
      life[i] = 0;
      pos.array[i * 3 + 1] = -9999; // Hide
    }
  }
  pos.needsUpdate = true;
}

function emitWake(x, y, z, vx, vy, vz, amount) {
  if (!wakeParticles) return;
  const count = wakeParticles.userData.maxCount;
  const life = wakeParticles.geometry.userData.lifetimes;
  const pos = wakeParticles.geometry.attributes.position;
  const vel = wakeParticles.userData.velocities;

  for (let i = 0; i < count && amount > 0; i++) {
    if (life[i] <= 0) {
      pos.array[i * 3] = x + (Math.random() - 0.5) * 2;
      pos.array[i * 3 + 1] = y + (Math.random() - 0.5) * 2;
      pos.array[i * 3 + 2] = z + (Math.random() - 0.5) * 2;
      life[i] = 1 + Math.random() * 2;
      vel[i] = { x: vx * 0.1 + (Math.random() - 0.5) * 2, y: vy * 0.1 + (Math.random() - 0.5), z: vz * 0.1 + (Math.random() - 0.5) * 2 };
      amount--;
    }
  }
}

// ==================== ATMOSPHERE ====================
function getAirDensity(altitude) {
  return CONFIG.seaLevelDensity * Math.exp(-altitude / CONFIG.atmosphereScaleHeight);
}

// ==================== PHYSICS ENGINE ====================
const _v1 = new THREE.Vector3();
const _v2 = new THREE.Vector3();
const _v3 = new THREE.Vector3();
const _q1 = new THREE.Quaternion();
const _e1 = new THREE.Euler();

function updatePhysics(dt) {
  const plane = AIRCRAFT[gameState.selectedPlane];
  const gs = gameState;

  dt = Math.min(dt, 0.05); // Clamp delta time

  // === Throttle response ===
  gs.throttle += (gs.throttleTarget - gs.throttle) * plane.thrustResponse * dt * 3;
  gs.throttle = Math.max(0, Math.min(1, gs.throttle));

  // === Control surface response (smooth) ===
  gs.elevator += (gs.elevatorTarget - gs.elevator) * 4.0 * dt;
  gs.aileron += (gs.aileronTarget - gs.aileron) * 4.0 * dt;
  gs.rudder += (gs.rudderTarget - gs.rudder) * 3.0 * dt;
  gs.elevator = Math.max(-1, Math.min(1, gs.elevator));
  gs.aileron = Math.max(-1, Math.min(1, gs.aileron));
  gs.rudder = Math.max(-1, Math.min(1, gs.rudder));

  // === Compute thrust ===
  if (plane.maxThrust > 0) {
    gs.thrust = plane.idleThrust + (plane.maxThrust - plane.idleThrust) * gs.throttle;
  } else {
    gs.thrust = 0;
  }

  // === Engine temp ===
  if (plane.maxThrust > 0) {
    const targetTemp = 120 + gs.throttle * 400;
    gs.engineTemp += (targetTemp - gs.engineTemp) * dt * 0.5;
  }

  // === Airspeed ===
  gs.airspeed = gs.vel.length();

  // === Air density at altitude ===
  const altitude = gs.pos.y;
  const airDensity = getAirDensity(altitude);
  const densityRatio = airDensity / CONFIG.seaLevelDensity;

  // === Angle of Attack ===
  // Aircraft forward direction
  _v1.set(1, 0, 0).applyQuaternion(gs.quat);
  // Relative wind = -velocity
  _v2.copy(gs.vel).negate().normalize();
  // Project wind onto aircraft's vertical plane
  _v3.set(0, 1, 0).applyQuaternion(gs.quat);
  const forwardDotWind = _v1.dot(_v2);
  const aoaRad = Math.asin(Math.max(-1, Math.min(1, _v3.dot(_v2))));
  gs.aoa = aoaRad * 180 / Math.PI;
  if (forwardDotWind < 0) gs.aoa = -gs.aoa; // inverted flight

  // === Lift ===
  // C_L = liftCoeff * aoa (in degrees), clamped
  let cl = plane.liftCoeff * gs.aoa;
  cl = Math.max(-plane.liftMax, Math.min(plane.liftMax, cl));
  // Dynamic pressure
  const q_dyn = 0.5 * airDensity * gs.airspeed * gs.airspeed;
  gs.lift = q_dyn * plane.wingArea * cl;

  // === Drag ===
  // Profile drag
  const cd_profile = plane.dragCoeff;
  // Induced drag
  const cd_induced = plane.inducedDragFactor * cl * cl;
  const cd = cd_profile + cd_induced + (gs.aileron * gs.aileron + gs.elevator * gs.elevator) * 0.005;
  gs.drag = q_dyn * plane.wingArea * cd;

  // === Forces in body frame ===
  // Thrust along body X
  const thrustX = gs.thrust;
  const thrustY = 0;
  const thrustZ = 0;

  // Lift along aircraft Y (up)
  const liftX = 0;
  const liftY = gs.lift;
  const liftZ = 0;

  // Drag opposes velocity - transform to body frame
  const dragForce = gs.drag;
  const windDir = _v2.clone(); // -velocity direction
  const dragBody = windDir.clone().negate().multiplyScalar(dragForce);

  // === Total force in body frame ===
  const fx = thrustX + dragBody.x;
  const fy = liftY + dragBody.y;
  const fz = dragBody.z;

  // === Convert forces to world frame ===
  const forceWorld = new THREE.Vector3(fx, fy, fz).applyQuaternion(gs.quat);

  // === Weight ===
  const weight = _v1.set(0, -gs.mass * CONFIG.gravity, 0);

  // === Total acceleration ===
  const accel = forceWorld.add(weight).divideScalar(gs.mass);

  // === Apply angular rates from control surfaces ===
  // Pitch rate
  const pitchRate = plane.pitchRate * gs.elevator;
  // Roll rate
  const rollRate = plane.rollRate * gs.aileron;
  // Yaw rate
  const yawRate = plane.yawRate * gs.rudder;

  // Angular velocity in body frame
  const omegaBody = new THREE.Vector3(
    rollRate * gs.throttle + plane.rollRate * 0.1 * Math.sin(gs.time * 0.5),
    yawRate * 0.5,
    -pitchRate
  );

  // Convert omega to world frame
  const omegaWorld = omegaBody.clone().applyQuaternion(gs.quat);

  // === Update position ===
  gs.vel.add(accel.multiplyScalar(dt));

  // Speed clamp (prevent physics explosion)
  const maxSpeed = plane.maxSpeed * 1.5;
  if (gs.airspeed > maxSpeed) {
    gs.vel.normalize().multiplyScalar(maxSpeed);
  }

  gs.pos.add(gs.vel.clone().multiplyScalar(dt));

  // === Ground collision ===
  const terrainH = getTerrainHeight(gs.pos.x, gs.pos.z);
  if (gs.pos.y < terrainH + 2) {
    gs.pos.y = terrainH + 2;
    if (gs.vel.y < 0) gs.vel.y *= -0.3;
    if (gs.airspeed < 5 && gs.pos.y < terrainH + 5) {
      gs.onGround = true;
      gs.vel.y = 0;
    }
  } else {
    gs.onGround = false;
  }

  // === Update rotation ===
  const qOmega = new THREE.Quaternion().setFromAxisAngle(
    new THREE.Vector3(omegaWorld.x, omegaWorld.y, omegaWorld.z).normalize(),
    omegaWorld.length() * dt
  );
  gs.quat.multiply(qOmega).normalize();

  // === G-Force calculation ===
  // G = (acceleration - gravity) / g, along aircraft Y
  const gForceAccel = _v1.copy(accel).add(_v1.set(0, CONFIG.gravity, 0));
  const gAlongBodyY = gForceAccel.dot(_v1.set(0, 1, 0).applyQuaternion(gs.quat));
  gs.gForce = gAlongBodyY / CONFIG.gravity;

  // Clamp G-force
  gs.gForce = Math.max(gs.minGForce, Math.min(gs.maxGForce, gs.gForce));

  // === Vertical speed ===
  gs.verticalSpeed = gs.vel.y * 196.85; // m/s to ft/min

  // === Stall check ===
  const stallWarning = document.getElementById('stall-warning');
  if (gs.airspeed < plane.stallSpeed * 1.1 && gs.pos.y > terrainH + 10) {
    stallWarning.classList.add('active');
  } else {
    stallWarning.classList.remove('active');
  }

  // === Emit wake particles ===
  if (gs.airspeed > 30 && gs.pos.y > terrainH + 5) {
    const wingTipL = gs.quat.clone().multiply(new THREE.Vector3(0, 0, plane.wingSpan / 2));
    const wingTipR = gs.quat.clone().multiply(new THREE.Vector3(0, 0, -plane.wingSpan / 2));
    emitWake(
      gs.pos.x + wingTipL.x, gs.pos.y + wingTipL.y, gs.pos.z + wingTipL.z,
      -gs.vel.x * 0.05, -gs.vel.y * 0.02, -gs.vel.z * 0.05, 1
    );
    emitWake(
      gs.pos.x + wingTipR.x, gs.pos.y + wingTipR.y, gs.pos.z + wingTipR.z,
      -gs.vel.x * 0.05, -gs.vel.y * 0.02, -gs.vel.z * 0.05, 1
    );
  }
}

// ==================== AUDIO SYSTEM ====================
let audioCtx, masterGain;
let engineOsc1, engineOsc2, engineFilter, engineGain;
let windGain, windNoise;
let stallTone;

function initAudio() {
  try {
    audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    masterGain = audioCtx.createGain();
    masterGain.gain.value = 0.4;
    masterGain.connect(audioCtx.destination);

    // Engine sound - dual oscillator
    engineOsc1 = audioCtx.createOscillator();
    engineOsc1.type = 'sawtooth';
    engineOsc1.frequency.value = 80;

    engineOsc2 = audioCtx.createOscillator();
    engineOsc2.type = 'square';
    engineOsc2.frequency.value = 160;

    engineFilter = audioCtx.createBiquadFilter();
    engineFilter.type = 'lowpass';
    engineFilter.frequency.value = 800;
    engineFilter.Q.value = 2;

    engineGain = audioCtx.createGain();
    engineGain.gain.value = 0;

    engineOsc1.connect(engineFilter);
    engineOsc2.connect(engineFilter);
    engineFilter.connect(engineGain);
    engineGain.connect(masterGain);

    engineOsc1.start();
    engineOsc2.start();

    // Wind noise
    const bufferSize = audioCtx.sampleRate * 2;
    const noiseBuffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
    const data = noiseBuffer.getChannelData(0);
    for (let i = 0; i < bufferSize; i++) {
      data[i] = (Math.random() * 2 - 1) * 0.5;
    }

    windNoise = audioCtx.createBufferSource();
    windNoise.buffer = noiseBuffer;
    windNoise.loop = true;

    const windFilter = audioCtx.createBiquadFilter();
    windFilter.type = 'bandpass';
    windFilter.frequency.value = 1000;
    windFilter.Q.value = 0.5;

    windGain = audioCtx.createGain();
    windGain.gain.value = 0;

    windNoise.connect(windFilter);
    windFilter.connect(windGain);
    windGain.connect(masterGain);
    windNoise.start();

    // Stall warning tone
    stallTone = audioCtx.createOscillator();
    stallTone.type = 'square';
    stallTone.frequency.value = 800;

    const stallGain = audioCtx.createGain();
    stallGain.gain.value = 0;
    stallTone.connect(stallGain);
    stallGain.connect(masterGain);
    stallTone.start();
    stallTone._gain = stallGain;

    gameState.audioInitialized = true;
  } catch (e) {
    console.log('Audio not available:', e);
  }
}

function updateAudio() {
  if (!gameState.audioInitialized || !audioCtx) return;
  if (audioCtx.state !== 'running') return;

  const throttle = gameState.throttle;
  const airspeed = gameState.airspeed || 0;
  const altitude = gameState.pos.y || 0;

  try {
    const ct = audioCtx.currentTime;
    if (!isFinite(ct)) return;

    if (engineOsc1 && engineOsc2 && engineFilter && engineGain) {
      // Engine sound
      const baseFreq = 60 + throttle * 300;
      const jitter = Math.sin(gameState.time * 30) * 5 * throttle;

      engineOsc1.frequency.setTargetAtTime(baseFreq + jitter, ct, 0.05);
      engineOsc2.frequency.setTargetAtTime(baseFreq * 2 + jitter, ct, 0.05);
      engineFilter.frequency.setTargetAtTime(400 + throttle * 2000, ct, 0.05);
      engineGain.gain.setTargetAtTime(0.05 + throttle * 0.25, ct, 0.05);
    }

    // Wind noise
    if (windGain) {
      const windVol = Math.min(0.15, airspeed * 0.0005) * (1 - altitude / 15000);
      windGain.gain.setTargetAtTime(Math.max(0, Math.min(0.15, windVol)), ct, 0.1);
    }

    // Stall warning
    if (stallTone && stallTone._gain) {
      const plane = AIRCRAFT[gameState.selectedPlane];
      const isStalling = airspeed < plane.stallSpeed * 1.1 && gameState.pos.y > getTerrainHeight(gameState.pos.x, gameState.pos.z) + 10;
      stallTone._gain.gain.setTargetAtTime(isStalling ? 0.15 : 0, ct, 0.05);
      if (isStalling) {
        stallTone.frequency.setTargetAtTime(800 + Math.sin(gameState.time * 8) * 200, ct, 0.05);
      }
    }
  } catch (e) {
    // Audio context may have been destroyed
  }
}

// ==================== INPUT HANDLING ====================
function updateInput(dt) {
  const gs = gameState;

  // Keyboard controls
  gs.pitchTarget = 0;
  gs.rollTarget = 0;
  gs.yawTarget = 0;

  if (keys['KeyW']) gs.pitchTarget = 1;
  if (keys['KeyS']) gs.pitchTarget = -1;
  if (keys['KeyA']) gs.rollTarget = -1;
  if (keys['KeyD']) gs.rollTarget = 1;
  if (keys['KeyQ']) gs.yawTarget = -1;
  if (keys['KeyE']) gs.yawTarget = 1;

  // Mouse pitch/roll
  if (mouse.locked) {
    const sensitivity = 0.002;
    gs.pitchTarget += mouse.dy * sensitivity;
    gs.rollTarget += mouse.dx * sensitivity;
    mouse.dx = 0;
    mouse.dy = 0;
  }

  // Clamp
  gs.pitchTarget = Math.max(-1, Math.min(1, gs.pitchTarget));
  gs.rollTarget = Math.max(-1, Math.min(1, gs.rollTarget));

  // Throttle
  if (keys['ArrowUp'] || keys['KeyZ']) {
    gs.throttleTarget = Math.min(1, gs.throttleTarget + dt * 0.5);
  }
  if (keys['ArrowDown'] || keys['KeyX']) {
    gs.throttleTarget = Math.max(0, gs.throttleTarget - dt * 0.5);
  }

  // Camera
  if (keys['KeyC']) {
    gs.cameraMode = (gs.cameraMode + 1) % 3;
    keys['KeyC'] = false;
  }

  // Reset
  if (keys['KeyR']) {
    gs.pos.set(0, 10, 0);
    gs.vel.set(0, 0, 0);
    gs.quat.identity();
    gs.euler.set(0, 0, 0);
    gs.throttle = 0;
    gs.throttleTarget = 0;
    gs.pitchTarget = 0;
    gs.rollTarget = 0;
    gs.yawTarget = 0;
    gs.landed = false;
    gs.runwayHit = false;
    keys['KeyR'] = false;
  }

  // Gun fire (dogfight)
  if (keys['Mouse0'] && gs.mode === 'dogfight' && gs.gunCooldown <= 0) {
    fireGun();
  }

  // Update control surface targets
  gs.elevatorTarget = gs.pitchTarget;
  gs.aileronTarget = gs.rollTarget;
  gs.rudderTarget = gs.yawTarget;
}

// ==================== DOGFIGHT ====================
function fireGun() {
  const gs = gameState;
  if (gs.ammo <= 0) return;

  gs.ammo--;
  gs.shotsFired++;
  gs.heat += 5;
  gs.gunCooldown = 0.08;

  // Muzzle flash
  muzzleFlash.material.opacity = 1;
  muzzleFlash.scale.setScalar(2);

  // Check hit on enemy
  if (enemyGroup && enemyGroup.visible) {
    const dir = new THREE.Vector3(1, 0, 0).applyQuaternion(gs.quat);
    const toEnemy = _v1.copy(gs.enemyPos).sub(gs.pos);
    const dist = toEnemy.length();
    toEnemy.normalize();
    const dot = dir.dot(toEnemy);

    if (dot > 0.95 && dist < 2000) {
      // Hit!
      const accuracy = Math.max(0, dot - 0.95) * 20;
      if (Math.random() < accuracy * 0.3) {
        gs.enemyHP -= 5 + Math.random() * 5;
        gs.shotsHit++;
        if (gs.enemyHP <= 0) {
          gs.kills++;
          gs.enemyHP = 100;
          gs.enemyPos.set(
            (Math.random() - 0.5) * 20000,
            1000 + Math.random() * 5000,
            (Math.random() - 0.5) * 20000
          );
          showMission('TARGET DOWN! +100 PTS');
        }
      }
    }
  }
}

function updateEnemy(dt) {
  const gs = gameState;
  if (gs.mode !== 'dogfight') return;

  if (!enemyGroup.visible) return;

  // Enemy AI
  gs.enemyAggression += (Math.sin(gs.time * 0.1) * 0.5) * dt;
  gs.enemyAggression = Math.max(0.2, Math.min(0.9, gs.enemyAggression));

  // Move toward player with some maneuvering
  const toPlayer = _v1.copy(gs.pos).sub(gs.enemyPos);
  const dist = toPlayer.length();

  // Desired position
  const desiredDist = 800 + Math.sin(gs.time * 0.3) * 300;
  const desiredHeight = gs.pos.y + Math.sin(gs.time * 0.2) * 500;

  _v2.copy(toPlayer).normalize().multiplyScalar(desiredDist);
  _v2.y = desiredHeight;

  // Maneuvering offset
  _v3.set(
    Math.sin(gs.time * 0.5) * 500,
    Math.cos(gs.time * 0.7) * 300,
    Math.cos(gs.time * 0.5) * 500
  );

  const targetPos = _v2.add(_v3);
  const moveDir = targetPos.sub(gs.enemyPos).normalize();

  // Enemy velocity
  const enemySpeed = 80 + gs.enemyAggression * 60;
  gs.enemyVel.lerp(moveDir.multiplyScalar(enemySpeed), dt * 2);
  gs.enemyPos.add(gs.enemyVel.clone().multiplyScalar(dt));

  // Enemy rotation - face direction of travel
  if (gs.enemyVel.length() > 1) {
    const targetQuat = new THREE.Quaternion().setFromUnitVectors(
      new THREE.Vector3(1, 0, 0),
      gs.enemyVel.clone().normalize()
    );
    gs.enemyQuat.slerp(targetQuat, dt * 3);
  }

  // Keep above terrain
  const enemyTerrain = getTerrainHeight(gs.enemyPos.x, gs.enemyPos.z);
  if (gs.enemyPos.y < enemyTerrain + 100) {
    gs.enemyPos.y = enemyTerrain + 100;
  }

  // Update enemy mesh
  enemyGroup.position.copy(gs.enemyPos);
  enemyGroup.quaternion.copy(gs.enemyQuat);

  // Gun cooldown
  gs.gunCooldown -= dt;
  gs.heat = Math.max(0, gs.heat - dt * 10);

  // Muzzle flash decay
  if (muzzleFlash) {
    muzzleFlash.material.opacity *= 0.85;
    muzzleFlash.scale.multiplyScalar(0.9);
  }
}

// ==================== LANDING MODE ====================
function updateLanding(dt) {
  const gs = gameState;
  const plane = AIRCRAFT[gameState.selectedPlane];

  if (gs.mode !== 'landing') return;

  // Runway position
  const runwayZ = 2000;
  const runwayX = 0;
  const runwayY = getTerrainHeight(runwayX, runwayZ);

  // Check if near runway
  const distToRunway = Math.sqrt(
    Math.pow(gs.pos.x - runwayX, 2) + Math.pow(gs.pos.z - runwayZ, 2)
  );

  // Takeoff check
  if (!gs.takeoffSpeedReached && gs.airspeed > plane.stallSpeed * 1.3 && gs.onGround) {
    gs.takeoffSpeedReached = true;
    showMission('AIRBORNE! NAVIGATE AND RETURN FOR LANDING');
  }

  // Landing scoring
  if (gs.landed && !gs.runwayHit) {
    if (distToRunway < 300 && gs.pos.y < runwayY + 50) {
      gs.runwayHit = true;
      let score = 0;
      let msg = '';

      // Speed penalty
      if (gs.airspeed < 80) { score += 100; msg += 'PERFECT! +100\n'; }
      else if (gs.airspeed < 120) { score += 70; msg += 'GOOD LANDING +70\n'; }
      else if (gs.airspeed < 200) { score += 30; msg += 'ROUGH +30\n'; }
      else { msg += 'TOO FAST!\n'; }

      // Altitude penalty
      if (Math.abs(gs.pos.y - runwayY) < 5) { score += 50; msg += 'FLAT +50\n'; }
      else if (gs.pos.y < runwayY + 20) { score += 20; msg += 'OK +20\n'; }

      // Position penalty
      if (distToRunway < 100) { score += 50; msg += 'ON RUNWAY +50\n'; }
      else if (distToRunway < 300) { score += 20; msg += 'CLOSE +20\n'; }

      // G-force penalty
      if (gs.gForce < 3) { score += 30; msg += 'SMOOTH +30\n'; }
      else { msg += 'HARD IMPACT\n'; }

      msg += `SCORE: ${score}`;
      showMission(msg);
      gs.score = score;
    }
  }

  // Ground friction
  if (gs.onGround) {
    gs.vel.multiplyScalar(1 - dt * 0.3);
    gs.landed = true;
  }
}

// ==================== HUD UPDATE ====================
function updateHUD() {
  const gs = gameState;
  const plane = AIRCRAFT[gameState.selectedPlane];
  const altAGL = Math.max(0, gs.pos.y - getTerrainHeight(gs.pos.x, gs.pos.z));

  // Speed (knots)
  const speedKnots = gs.airspeed * 1.944;
  document.getElementById('hud-speed').innerHTML = `${Math.round(speedKnots)} <small>KT</small>`;

  // Altitude (feet)
  document.getElementById('hud-alt').innerHTML = `${Math.round(altAGL)} <small>FT</small>`;

  // Vertical speed
  const vs = gs.verticalSpeed;
  document.getElementById('hud-vspeed').innerHTML = `${Math.round(vs)} <small>FT/MIN</small>`;

  // Throttle
  document.getElementById('hud-throttle').innerHTML = `${Math.round(gs.throttle * 100)} <small>%</small>`;
  document.getElementById('hud-throttle-bar').style.width = `${gs.throttle * 100}%`;

  // RPM
  const rpm = Math.round(50 + gs.throttle * 950);
  document.getElementById('hud-rpm').innerHTML = `${rpm} <small>×</div>`;

  // AoA
  document.getElementById('hud-aoa').innerHTML = `${gs.aoa.toFixed(1)} <small>°</small>`;

  // G-force
  const gColor = gs.gForce > 5 ? '#f44' : gs.gForce < -1 ? '#f44' : '#0cf';
  document.getElementById('hud-g').innerHTML = `<span style="color:${gColor}">${gs.gForce.toFixed(1)}</span> <small>G</small>`;

  // Pitch/Roll
  const pitchDeg = gs.euler.x * 180 / Math.PI;
  const rollDeg = gs.euler.z * 180 / Math.PI;
  document.getElementById('hud-pitch-roll').innerHTML = `${pitchDeg.toFixed(1)}° / ${rollDeg.toFixed(1)}°`;

  // Heading
  let heading = gs.euler.y * 180 / Math.PI;
  if (heading < 0) heading += 360;
  document.getElementById('hud-heading').innerHTML = `${Math.round(heading)} <small>°</small>`;

  // Engine temp
  const temp = Math.round(gs.engineTemp);
  document.getElementById('hud-temp-val').textContent = `${temp}°C`;
  const tempBar = document.getElementById('hud-temp-bar');
  tempBar.style.width = `${Math.min(100, temp / 520 * 100)}%`;
  tempBar.style.background = temp > 400 ? '#f44' : temp > 300 ? '#ff4' : '#0f0';

  // Dogfight HUD
  if (gs.mode === 'dogfight') {
    document.getElementById('hud-bottom').innerHTML = `
      <div class="hud-label">Ammo</div>
      <div class="hud-bar"><div class="hud-bar-fill" style="width:${gs.ammo/5}%;background:#ff4"></div></div>
      <div style="font-size:11px;margin-top:2px">${gs.ammo} rounds</div>
      <div class="hud-label" style="margin-top:8px">Heat</div>
      <div class="hud-bar"><div class="hud-bar-fill" style="width:${Math.min(100,gs.heat)}%;background:${gs.heat>70?'#f44':'#ff4'}"></div></div>
      <div style="font-size:11px;margin-top:2px">${Math.round(gs.heat)}°</div>
      <div class="hud-label" style="margin-top:8px">Kills</div>
      <div class="hud-value" style="font-size:16px">${gs.kills}</div>
    `;
  } else {
    document.getElementById('hud-bottom').innerHTML = `
      <div class="hud-label">Engine Temp</div>
      <div class="hud-bar"><div class="hud-bar-fill" id="hud-temp-bar" style="width:${Math.min(100,gs.engineTemp/520*100)}%;background:${gs.engineTemp>400?'#f44':gs.engineTemp>300?'#ff4':'#0f0'}"></div></div>
      <div id="hud-temp-val" style="font-size:11px;margin-top:2px">${Math.round(gs.engineTemp)}°C</div>
    `;
  }

  // Minimap
  updateMinimap();
}

function updateMinimap() {
  const ctx = minimapCtx;
  const w = 150, h = 150;
  ctx.fillStyle = 'rgba(0, 20, 40, 0.8)';
  ctx.fillRect(0, 0, w, h);

  const scale = w / CONFIG.terrainSize * 3;
  const cx = w / 2;
  const cy = h / 2;

  // Player
  ctx.fillStyle = '#0cf';
  ctx.beginPath();
  ctx.arc(cx, cy, 3, 0, Math.PI * 2);
  ctx.fill();

  // Player heading indicator
  const heading = gameState.euler.y;
  ctx.strokeStyle = '#0cf';
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(cx, cy);
  ctx.lineTo(cx + Math.sin(heading) * 12, cy - Math.cos(heading) * 12);
  ctx.stroke();

  // Runway
  const rwX = cx + (0 - gameState.pos.x) * scale;
  const rwZ = cy + (2000 - gameState.pos.z) * scale;
  ctx.fillStyle = '#444';
  ctx.fillRect(rwX - 2, rwZ - 15, 4, 30);

  // Enemy
  if (gameState.mode === 'dogfight' && enemyGroup.visible) {
    const ex = cx + (gameState.enemyPos.x - gameState.pos.x) * scale;
    const ez = cy + (gameState.enemyPos.z - gameState.pos.z) * scale;
    if (ex > 0 && ex < w && ez > 0 && ez < h) {
      ctx.fillStyle = '#f44';
      ctx.beginPath();
      ctx.arc(ex, ez, 3, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  // Border
  ctx.strokeStyle = 'rgba(0, 200, 255, 0.3)';
  ctx.lineWidth = 1;
  ctx.strokeRect(0, 0, w, h);
}

// ==================== CAMERA ====================
function updateCamera() {
  const gs = gameState;
  const cam = camera;

  if (gs.cameraMode === 0) {
    // Chase camera
    const dist = 30;
    const height = 8;
    const targetPos = _v1.copy(gs.pos);

    // Behind aircraft
    const behind = _v2.set(0, 0, 1).applyQuaternion(gs.quat).multiplyScalar(dist);
    const up = _v3.set(0, 1, 0).applyQuaternion(gs.quat).multiplyScalar(height);

    const camTarget = targetPos.clone().sub(behind).add(up);

    cam.position.lerp(camTarget, 0.08);
    cam.lookAt(gs.pos);
  } else if (gs.cameraMode === 1) {
    // Cockpit camera
    const cockpitOffset = _v1.set(2.5, 0.5, 0).applyQuaternion(gs.quat);
    cam.position.copy(gs.pos).add(cockpitOffset);
    cam.quaternion.copy(gs.quat);
  } else {
    // Free camera
    cam.position.copy(gs.pos);
    cam.lookAt(gs.pos);
  }

  // Update sun shadow to follow player
  sunLight.position.set(gs.pos.x + 5000, gs.pos.y + 8000, gs.pos.z + 3000);
  sunLight.target.position.copy(gs.pos);
  sunLight.target.updateMatrixWorld();
}

// ==================== MISSION OVERLAY ====================
let missionTimeout;
function showMission(text) {
  const overlay = document.getElementById('mission-overlay');
  overlay.innerHTML = text.replace(/\n/g, '<br>');
  overlay.classList.add('visible');
  clearTimeout(missionTimeout);
  missionTimeout = setTimeout(() => overlay.classList.remove('visible'), 4000);
}

// ==================== SCENE MANAGEMENT ====================
function showMenu() {
  gameState.running = false;
  document.getElementById('menu').classList.remove('hidden');
  document.getElementById('hud').style.display = 'none';
  document.getElementById('controls-help').style.display = 'none';
  document.getElementById('minimap').style.display = 'none';
  document.getElementById('mission-overlay').classList.remove('visible');
  if (document.pointerLockElement) document.exitPointerLock();
}

function startGame() {
  const gs = gameState;
  gs.running = true;
  gs.mode = document.querySelector('.mode-card.selected').dataset.mode;
  gs.selectedPlane = document.querySelector('.aircraft-card.selected').dataset.plane;

  // Reset state
  gs.pos.set(0, 10, -1500);
  gs.vel.set(0, 0, 0);
  gs.quat.identity();
  gs.euler.set(0, 0, 0);
  gs.throttle = 0;
  gs.throttleTarget = 0;
  gs.pitchTarget = 0;
  gs.rollTarget = 0;
  gs.yawTarget = 0;
  gs.elevator = 0;
  gs.aileron = 0;
  gs.rudder = 0;
  gs.onGround = true;
  gs.landed = false;
  gs.takeoffSpeedReached = false;
  gs.score = 0;
  gs.kills = 0;
  gs.ammo = 500;
  gs.heat = 0;
  gs.enemyHP = 100;
  gs.time = 0;

  createAircraftModel(gs.selectedPlane);

  if (gs.mode === 'dogfight') {
    createEnemyModel();
    gs.enemyPos.set(
      (Math.random() - 0.5) * 15000,
      2000 + Math.random() * 4000,
      2000 + (Math.random() - 0.5) * 15000
    );
    gs.enemyVel.set(50, 0, 30);
    gs.enemyQuat.setFromUnitVectors(new THREE.Vector3(1, 0, 0), gs.enemyVel.clone().normalize());
    enemyGroup.position.copy(gs.enemyPos);
    enemyGroup.quaternion.copy(gs.enemyQuat);
    enemyGroup.visible = true;
    showMission('DOGFIGHT ENGAGED — DOWN THE ENEMY');
  } else {
    showMission('TAKEOFF — PULL UP AT 80 KT');
  }

  document.getElementById('menu').classList.add('hidden');
  document.getElementById('hud').style.display = 'block';
  document.getElementById('controls-help').style.display = 'block';
  document.getElementById('minimap').style.display = 'block';

  if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();

  // Request pointer lock
  document.body.requestPointerLock();
}

// ==================== MENU HANDLERS ====================
function initMenu() {
  const cards = document.querySelectorAll('.aircraft-card');
  cards.forEach(card => {
    card.addEventListener('click', () => {
      cards.forEach(c => c.classList.remove('selected'));
      card.classList.add('selected');
      checkReady();
    });
  });

  const modes = document.querySelectorAll('.mode-card');
  modes.forEach(mode => {
    mode.addEventListener('click', () => {
      modes.forEach(m => m.classList.remove('selected'));
      mode.classList.add('selected');
      checkReady();
    });
  });

  document.getElementById('start-btn').addEventListener('click', startGame);
}

function checkReady() {
  const hasPlane = document.querySelector('.aircraft-card.selected');
  const hasMode = document.querySelector('.mode-card.selected');
  document.getElementById('start-btn').disabled = !(hasPlane && hasMode);
}

// ==================== MAIN LOOP ====================
function mainLoop() {
  requestAnimationFrame(mainLoop);

  if (!gameState.running) {
    renderer.render(scene, camera);
    return;
  }

  const now = performance.now();
  const dt = Math.min((now - gameState.lastTime) / 1000, 0.05);
  gameState.lastTime = now;
  gameState.dt = dt;
  gameState.time += dt;

  // Update
  updateInput(dt);
  updatePhysics(dt);
  updateEnemy(dt);
  updateLanding(dt);
  updateCamera();
  updateAudio();
  updateHUD();
  updateWake(dt);

  // Update aircraft mesh
  if (aircraftGroup) {
    aircraftGroup.position.copy(gameState.pos);
    aircraftGroup.quaternion.copy(gameState.quat);
  }

  // Update clouds to follow player
  clouds.forEach(cloud => {
    const dx = cloud.position.x - gameState.pos.x;
    const dz = cloud.position.z - gameState.pos.z;
    if (Math.abs(dx) > 30000 || Math.abs(dz) > 30000) {
      cloud.position.x = gameState.pos.x + (Math.random() - 0.5) * 60000;
      cloud.position.z = gameState.pos.z + (Math.random() - 0.5) * 60000;
    }
  });

  // Update runway lights
  const runwayZ = 2000;
  runwayLights.forEach((light, i) => {
    light.position.y = getTerrainHeight(light.position.x, light.position.z) + 2;
    // Blink effect
    light.material.emissiveIntensity = 1.5 + Math.sin(gameState.time * 3 + i) * 0.5;
  });

  // Update sun position based on time
  const sunAngle = gameState.time * 0.01;
  sunLight.position.set(
    Math.cos(sunAngle) * 10000,
    8000 + Math.sin(sunAngle) * 2000,
    Math.sin(sunAngle) * 5000
  );

  // Fog color based on altitude
  const altRatio = Math.max(0, Math.min(1, gameState.pos.y / 10000));
  const fogR = 0.53 + altRatio * 0.1;
  const fogG = 0.81 - altRatio * 0.3;
  const fogB = 0.92 - altRatio * 0.4;
  scene.fog.color.setRGB(fogR, fogG, fogB);
  scene.background.setRGB(fogR, fogG, fogB);

  // Render
  renderer.render(scene, camera);
}

// ==================== INITIALIZATION ====================
function init() {
  initMenu();
  initThreeJS();
  generateTerrain();
  createRunway();
  createClouds();
  createWakeSystem();
  initAudio();

  // Hide loading
  setTimeout(() => {
    document.getElementById('loading').classList.add('hidden');
  }, 500);

  gameState.lastTime = performance.now();
  mainLoop();
}

// Start when Three.js is loaded
if (typeof THREE !== 'undefined') {
  init();
} else {
  window.addEventListener('load', init);
}

})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-35b-a3b","provider":"lmstudio","persona":"full","sessionId":"0ee6807e-45b2-4117-802b-b78fe816319a","tokensIn":1265539,"tokensOut":32588,"tokensTotal":1298127,"turns":23,"toolCalls":22,"failedToolCalls":0,"timestamp":"2026-07-21T01:33:21.769Z"} -->