← Flight Simulator results

Flight Simulator

Qwen 3.6 35b a3b · composer

◈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 67,020 bytes · SHA-256 09b5b8c3ea4e
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Apex Aero — Flight Simulator</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; font-family: 'Courier New', monospace; }
#gameCanvas { display: block; width: 100%; height: 100%; }

/* Menu Overlay */
.menu-overlay {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, rgba(10,15,30,0.95), rgba(20,30,50,0.92));
  backdrop-filter: blur(8px);
  transition: opacity 0.5s;
}
.menu-overlay.hidden { opacity: 0; pointer-events: none; }
.menu-panel {
  text-align: center; max-width: 900px; width: 90%;
  padding: 40px;
}
.menu-title {
  font-size: 64px; font-weight: 900; letter-spacing: 12px;
  background: linear-gradient(180deg, #fff, #4a9eff);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  text-shadow: 0 0 40px rgba(74,158,255,0.3);
  margin-bottom: 4px;
}
.menu-subtitle {
  font-size: 18px; color: #4a9eff; letter-spacing: 8px;
  margin-bottom: 36px; opacity: 0.8;
}
.menu-section { margin-bottom: 28px; }
.menu-section h3 {
  font-size: 14px; color: #4a9eff; letter-spacing: 4px;
  margin-bottom: 12px; opacity: 0.7;
}
.aircraft-options, .scenario-options {
  display: flex; gap: 16px; justify-content: center; flex-wrap: wrap;
}
.aircraft-card, .scenario-card {
  background: rgba(255,255,255,0.04); border: 1px solid rgba(74,158,255,0.15);
  border-radius: 12px; padding: 20px 24px; cursor: pointer;
  transition: all 0.3s; min-width: 200px; text-align: center;
}
.aircraft-card:hover, .scenario-card:hover {
  background: rgba(74,158,255,0.12); border-color: rgba(74,158,255,0.4);
  transform: translateY(-2px);
}
.aircraft-card.selected {
  background: rgba(74,158,255,0.18); border-color: #4a9eff;
  box-shadow: 0 0 20px rgba(74,158,255,0.2);
}
.scenario-card.selected {
  background: rgba(74,158,255,0.18); border-color: #4a9eff;
  box-shadow: 0 0 20px rgba(74,158,255,0.2);
}
.plane-icon { font-size: 36px; margin-bottom: 8px; }
.scenario-icon { font-size: 32px; margin-bottom: 8px; }
.aircraft-card h4, .scenario-card h4 { color: #fff; font-size: 16px; margin-bottom: 6px; }
.plane-stats { color: #8ab4f8; font-size: 12px; }
.scenario-card p { color: #8ab4f8; font-size: 12px; }
.menu-btn {
  background: linear-gradient(135deg, #4a9eff, #1a6fd4);
  border: none; color: #fff; font-family: inherit;
  font-size: 20px; font-weight: 700; letter-spacing: 6px;
  padding: 16px 64px; border-radius: 8px; cursor: pointer;
  transition: all 0.3s; margin-top: 12px;
}
.menu-btn:hover { transform: scale(1.05); box-shadow: 0 0 30px rgba(74,158,255,0.4); }
.controls-hint {
  color: #5a7a9a; font-size: 11px; margin-top: 20px; letter-spacing: 1px;
}

/* HUD */
.hud { position: fixed; inset: 0; z-index: 50; pointer-events: none; }
.hud.hidden { display: none; }
.hud-left, .hud-right {
  position: absolute; top: 20px; display: flex; flex-direction: column; gap: 4px;
}
.hud-left { left: 24px; }
.hud-right { right: 24px; }
.hud-item {
  display: flex; align-items: baseline; gap: 4px;
  background: rgba(0,0,0,0.4); padding: 4px 12px; border-radius: 4px;
  border-left: 2px solid #4a9eff;
}
.hud-label { color: #4a9eff; font-size: 10px; letter-spacing: 2px; }
.hud-value { color: #fff; font-size: 20px; font-weight: 700; }
.hud-unit { color: #5a7a9a; font-size: 10px; }
.hud-center {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center;
}
.hud-attitude {
  width: 200px; height: 200px; border-radius: 50%;
  border: 2px solid rgba(74,158,255,0.4);
  overflow: hidden; position: relative;
  background: linear-gradient(180deg, #1a3a5c 50%, #3a2a1a 50%);
}
.horizon-line {
  position: absolute; left: -50%; right: -50%; height: 2px;
  background: #4a9eff; top: 50%;
  transition: none;
}
.pitch-ladder {
  position: absolute; inset: 0;
}
.pitch-ladder-line {
  position: absolute; left: 50%; transform: translateX(-50%);
  width: 40px; height: 1px; background: rgba(255,255,255,0.5);
}
.pitch-ladder-label {
  position: absolute; left: 50%; transform: translateX(-50%);
  color: rgba(255,255,255,0.5); font-size: 8px; margin-left: 24px;
}
.center-dot {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 20px; height: 2px; background: #4a9eff;
}
.center-dot::before {
  content: ''; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%); width: 6px; height: 6px;
  border: 1px solid #4a9eff; border-radius: 50%;
}
.hud-instruments {
  display: flex; gap: 24px; margin-top: 12px;
}
.mini-gauge { text-align: center; }
.gauge-label { color: #4a9eff; font-size: 9px; letter-spacing: 2px; }
.gauge-bar {
  width: 80px; height: 6px; background: rgba(255,255,255,0.1);
  border-radius: 3px; overflow: hidden; margin-top: 4px;
}
.gauge-fill {
  height: 100%; border-radius: 3px;
  background: linear-gradient(90deg, #4a9eff, #ff6b35);
  transition: width 0.1s;
}
.gauge-value { color: #fff; font-size: 18px; font-weight: 700; }
.hud-bottom {
  position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
  color: #4a9eff; font-size: 14px; letter-spacing: 2px;
  text-align: center; text-shadow: 0 0 10px rgba(74,158,255,0.5);
}

/* Dogfight UI */
.dogfight-ui { position: fixed; inset: 0; z-index: 55; pointer-events: none; }
.dogfight-ui.hidden { display: none; }
.dogfight-score {
  position: absolute; top: 20px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 32px;
}
.score-item { text-align: center; background: rgba(0,0,0,0.4); padding: 6px 16px; border-radius: 4px; }
.score-label { color: #ff6b35; font-size: 10px; letter-spacing: 2px; }
.score-value { color: #fff; font-size: 24px; font-weight: 700; }
.dogfight-crosshair {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 40px; height: 40px;
}
.dogfight-crosshair::before, .dogfight-crosshair::after {
  content: ''; position: absolute; background: rgba(255,107,53,0.7);
}
.dogfight-crosshair::before { left: 50%; top: 0; bottom: 0; width: 1px; transform: translateX(-50%); }
.dogfight-crosshair::after { top: 50%; left: 0; right: 0; height: 1px; transform: translateY(-50%); }

/* Mission UI */
.mission-ui { position: fixed; top: 80px; left: 50%; transform: translateX(-50%); z-index: 55; pointer-events: none; }
.mission-ui.hidden { display: none; }
.mission-objective {
  background: rgba(0,0,0,0.5); border: 1px solid rgba(74,158,255,0.3);
  padding: 12px 24px; border-radius: 8px; color: #fff; font-size: 14px;
  text-align: center; letter-spacing: 1px;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="menuOverlay" class="menu-overlay">
  <div class="menu-panel">
    <h1 class="menu-title">APEX AERO</h1>
    <p class="menu-subtitle">Flight Simulator</p>
    <div class="menu-section">
      <h3>Select Aircraft</h3>
      <div class="aircraft-options">
        <div class="aircraft-card" data-plane="fighter" onclick="selectPlane('fighter')">
          <div class="plane-icon">✈</div>
          <h4>F-16 Fighter</h4>
          <p class="plane-stats">High Speed · Agile · Medium Range</p>
        </div>
        <div class="aircraft-card" data-plane="bomber" onclick="selectPlane('bomber')">
          <div class="plane-icon">✈</div>
          <h4>B-2 Bomber</h4>
          <p class="plane-stats">Heavy · Stable · Low Speed</p>
        </div>
        <div class="aircraft-card" data-plane="glider" onclick="selectPlane('glider')">
          <div class="plane-icon">✈</div>
          <h4>Sailplane</h4>
          <p class="plane-stats">Ultra Light · High Lift · No Engine</p>
        </div>
      </div>
    </div>
    <div class="menu-section">
      <h3>Select Scenario</h3>
      <div class="scenario-options">
        <div class="scenario-card" data-scenario="takeoff" onclick="selectScenario('takeoff')">
          <div class="scenario-icon">🛫</div>
          <h4>Takeoff &amp; Landing</h4>
          <p>Master precision flight on a runway</p>
        </div>
        <div class="scenario-card" data-scenario="dogfight" onclick="selectScenario('dogfight')">
          <div class="scenario-icon">⚔️</div>
          <h4>Dogfight</h4>
          <p>High-G aerial combat</p>
        </div>
      </div>
    </div>
    <button class="menu-btn" id="startBtn" onclick="startGame()">LAUNCH</button>
    <p class="controls-hint">W/S: Pitch · A/D: Roll · Q/E: Yaw · Shift/Ctrl: Throttle · F: Brake · R: Restart</p>
  </div>
</div>
<div id="hud" class="hud hidden">
  <div class="hud-left">
    <div class="hud-item"><span class="hud-label">SPD</span><span class="hud-value" id="hudSpeed">0</span><span class="hud-unit">kt</span></div>
    <div class="hud-item"><span class="hud-label">ALT</span><span class="hud-value" id="hudAlt">0</span><span class="hud-unit">ft</span></div>
    <div class="hud-item"><span class="hud-label">HDG</span><span class="hud-value" id="hudHdg">000</span><span class="hud-unit">°</span></div>
  </div>
  <div class="hud-center">
    <div class="hud-attitude" id="hudAttitude">
      <div class="horizon-line" id="horizonLine"></div>
      <div class="pitch-ladder" id="pitchLadder"></div>
      <div class="flight-path-marker" id="fpm"></div>
      <div class="center-dot"></div>
    </div>
    <div class="hud-instruments">
      <div class="mini-gauge"><div class="gauge-label">THR</div><div class="gauge-bar"><div class="gauge-fill" id="throttleBar" style="width:0%"></div></div></div>
      <div class="mini-gauge"><div class="gauge-label">AoA</div><div class="gauge-bar"><div class="gauge-fill" id="aoaBar" style="width:50%"></div></div></div>
      <div class="mini-gauge"><div class="gauge-label">G</div><div class="gauge-value" id="gForce">1.0</div></div>
    </div>
  </div>
  <div class="hud-right">
    <div class="hud-item"><span class="hud-label">RPM</span><span class="hud-value" id="hudRpm">0</span><span class="hud-unit">%</span></div>
    <div class="hud-item"><span class="hud-label">VS</span><span class="hud-value" id="hudVs">0</span><span class="hud-unit">ft/s</span></div>
    <div class="hud-item"><span class="hud-label">STALL</span><span class="hud-value" id="hudStall">—</span></div>
  </div>
  <div class="hud-bottom" id="hudMessage"></div>
</div>
<div id="dogfightUI" class="dogfight-ui hidden">
  <div class="dogfight-score"><div class="score-item"><span class="score-label">KILLS</span><span class="score-value" id="killCount">0</span></div><div class="score-item"><span class="score-label">TURNS</span><span class="score-value" id="turnCount">0</span></div></div>
  <div class="dogfight-crosshair" id="dogfightCrosshair"></div>
</div>
<div id="missionUI" class="mission-ui hidden"><div class="mission-objective" id="missionObj"></div></div>
<script>
'use strict';

// ── const: DEG2RAD ──
// ============================================================
// CONSTANTS
// ============================================================
const DEG2RAD = Math.PI / 180;

// ── const: RAD2DEG ──
const RAD2DEG = 180 / Math.PI;

// ── const: FT2M ──
const FT2M = 0.3048;

// ── const: M2FT ──
const M2FT = 1 / FT2M;

// ── const: KT2MS ──
const KT2MS = 0.514444;

// ── const: MS2KT ──
const MS2KT = 1 / KT2MS;

// ── const: SEA_LEVEL_DENSITY ──
const SEA_LEVEL_DENSITY = 1.225;

// ── const: GRAVITY ──
// kg/m³
const GRAVITY = 9.81;

// ── const: AIRCRAFT_DEFS ──
// m/s²

// ============================================================
// AIRCRAFT DEFINITIONS
// ============================================================
const AIRCRAFT_DEFS = {
  fighter: {
    name: "F-16 Fighter",
    mass: 9500, // kg
    wingArea: 27.87, // m²
    wingspan: 9.96, // m
    maxThrust: 129000, // N (afterburner)
    cruiseThrust: 45000, // N
    dragCoeff: 0.028, // base profile drag
    inducedDragFactor: 0.045,
    liftCurveSlope: 5.7, // per radian
    stallAngle: 16 * DEG2RAD,
    maxAoA: 22 * DEG2RAD,
    rollRate: 2.5, // rad/s per unit input
    pitchRate: 1.8,
    yawRate: 1.2,
    rollDamping: 3.0,
    pitchDamping: 2.5,
    yawDamping: 2.0,
    throttleResponse: 0.8, // fraction of max thrust at idle
    maxG: 9.0,
    color: 0x4a9eff,
    accentColor: 0xff6b35,
  },
  bomber: {
    name: "B-2 Bomber",
    mass: 170000,
    wingArea: 470,
    wingspan: 52.4,
    maxThrust: 200000,
    cruiseThrust: 80000,
    dragCoeff: 0.022,
    inducedDragFactor: 0.035,
    liftCurveSlope: 4.8,
    stallAngle: 14 * DEG2RAD,
    maxAoA: 18 * DEG2RAD,
    rollRate: 0.8,
    pitchRate: 0.6,
    yawRate: 0.5,
    rollDamping: 5.0,
    pitchDamping: 4.0,
    yawDamping: 3.5,
    throttleResponse: 0.75,
    maxG: 2.5,
    color: 0x3a3a3a,
    accentColor: 0xff4444,
  },
  glider: {
    name: "Sailplane",
    mass: 500,
    wingArea: 12.0,
    wingspan: 15.0,
    maxThrust: 0,
    cruiseThrust: 0,
    dragCoeff: 0.018,
    inducedDragFactor: 0.030,
    liftCurveSlope: 6.2,
    stallAngle: 12 * DEG2RAD,
    maxAoA: 16 * DEG2RAD,
    rollRate: 1.5,
    pitchRate: 1.0,
    yawRate: 0.8,
    rollDamping: 4.0,
    pitchDamping: 3.0,
    yawDamping: 2.5,
    throttleResponse: 0,
    maxG: 5.0,
    color: 0xffffff,
    accentColor: 0x4a9eff,
  },
};

// ── const: TERRAIN_SIZE ──
// ============================================================
// TERRAIN CONFIG
// ============================================================
const TERRAIN_SIZE = 80000;

// ── const: TERRAIN_RES ──
// meters
const TERRAIN_RES = 512;

// ── const: TERRAIN_CHUNK_SIZE ──
// vertices
const TERRAIN_CHUNK_SIZE = 4000;

// ── const: RUNWAY_LENGTH ──
// meters per LOD chunk
const RUNWAY_LENGTH = 3000;

// ── const: RUNWAY_WIDTH ──
// meters
const RUNWAY_WIDTH = 60;

// ── const: RUNWAY_START ──
// meters
const RUNWAY_START = -RUNWAY_LENGTH / 2;

// ── const: RUNWAY_END ──
const RUNWAY_END = RUNWAY_LENGTH / 2;

// ── function: createAircraft ──
// ============================================================
// AIRCRAFT PHYSICS ENGINE
// ============================================================
function createAircraft(type) {
  const def = AIRCRAFT_DEFS[type];
  return {
    type: type,
    def: def,
    pos: new THREE.Vector3(0, 50, 0),
    vel: new THREE.Vector3(0, 0, 0),
    rot: new THREE.Euler(0, 0, 0, 'YXZ'),
    angularVel: new THREE.Vector3(0, 0, 0),
    throttle: 0,
    targetThrottle: 0,
    pitchInput: 0,
    rollInput: 0,
    yawInput: 0,
    aoa: 0,
    sideslip: 0,
    dynamicPressure: 0,
    lift: 0,
    drag: 0,
    thrust: 0,
    gForce: 1,
    isStalled: false,
    stallProgress: 0,
    gearDown: false,
    gearExtension: 0,
    brake: false,
    rpm: 0,
    engineTemp: 20,
    speedBrake: 0,
    lastTime: 0,
    trail: [],
    crashed: false,
  };
}

// ── function: resetAircraft ──
function resetAircraft(aircraft) {
  aircraft.pos.set(0, 5, 0);
  aircraft.vel.set(0, 0, 0);
  aircraft.rot.set(0, 0, 0);
  aircraft.angularVel.set(0, 0, 0);
  aircraft.throttle = 0;
  aircraft.targetThrottle = 0;
  aircraft.pitchInput = 0;
  aircraft.rollInput = 0;
  aircraft.yawInput = 0;
  aircraft.gearDown = false;
  aircraft.gearExtension = 0;
  aircraft.brake = false;
  aircraft.speedBrake = 0;
  aircraft.crashed = false;
  aircraft.trail = [];
}

// ── function: createAircraftMesh ──
// ============================================================
// AIRCRAFT 3D MODEL CREATION
// ============================================================
function createAircraftMesh(type) {
  const group = new THREE.Group();
  const bodyColor = type === 'fighter' ? 0x4a9eff : type === 'bomber' ? 0x5a5a5a : 0xeeeeee;
  const accentColor = type === 'fighter' ? 0xff6b35 : type === 'bomber' ? 0xff4444 : 0x4a9eff;

  if (type === 'fighter') {
    const bodyGeo = new THREE.ConeGeometry(0.8, 12, 8);
    bodyGeo.rotateX(Math.PI / 2);
    const bodyMat = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.6, roughness: 0.3 });
    group.add(new THREE.Mesh(bodyGeo, bodyMat));

    const cockpitGeo = new THREE.SphereGeometry(0.7, 8, 6, 0, Math.PI * 2, 0, Math.PI / 2);
    cockpitGeo.scale(0.8, 0.5, 1.2);
    const cockpitMat = new THREE.MeshStandardMaterial({ color: 0x88ccff, metalness: 0.9, roughness: 0.1, transparent: true, opacity: 0.7 });
    const cockpit = new THREE.Mesh(cockpitGeo, cockpitMat);
    cockpit.position.set(0, 0.5, 2);
    group.add(cockpit);

    const wingGeo = new THREE.BoxGeometry(10, 0.15, 2.5);
    const wingMat = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.5, roughness: 0.4 });
    const wings = new THREE.Mesh(wingGeo, wingMat);
    wings.position.set(0, 0, -1);
    group.add(wings);

    for (const side of [-1, 1]) {
      const tipGeo = new THREE.CylinderGeometry(0.05, 0.05, 1.5, 6);
      tipGeo.rotateZ(side * Math.PI / 2);
      const tipMat = new THREE.MeshStandardMaterial({ color: accentColor });
      const tip = new THREE.Mesh(tipGeo, tipMat);
      tip.position.set(side * 5.2, 0, -1);
      group.add(tip);
    }

    const tailGeo = new THREE.BoxGeometry(4, 0.1, 1.5);
    const tailMat = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.5, roughness: 0.4 });
    group.add(new THREE.Mesh(tailGeo, tailMat).translateZ(-5.5));

    const vStabGeo = new THREE.BoxGeometry(0.1, 2.5, 1.8);
    const vStabMat = new THREE.MeshStandardMaterial({ color: accentColor, metalness: 0.5, roughness: 0.4 });
    const vStab = new THREE.Mesh(vStabGeo, vStabMat);
    vStab.position.set(0, 1.2, -5.5);
    group.add(vStab);

    const nozzleGeo = new THREE.CylinderGeometry(0.5, 0.7, 1, 8);
    nozzleGeo.rotateX(Math.PI / 2);
    const nozzleMat = new THREE.MeshStandardMaterial({ color: 0x222222, metalness: 0.8, roughness: 0.2 });
    const nozzle = new THREE.Mesh(nozzleGeo, nozzleMat);
    nozzle.position.set(0, 0, -6);
    group.add(nozzle);

    const glowGeo = new THREE.SphereGeometry(0.4, 8, 8);
    const glowMat = new THREE.MeshBasicMaterial({ color: 0xff6600, transparent: true, opacity: 0.6 });
    const glow = new THREE.Mesh(glowGeo, glowMat);
    glow.position.set(0, 0, -6.5);
    glow.name = 'engineGlow';
    group.add(glow);

  } else if (type === 'bomber') {
    const bodyGeo = new THREE.BoxGeometry(3, 1.5, 14);
    const bodyMat = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.4, roughness: 0.6 });
    group.add(new THREE.Mesh(bodyGeo, bodyMat));

    const noseGeo = new THREE.SphereGeometry(1.5, 8, 8, 0, Math.PI, 0, Math.PI / 2);
    noseGeo.rotateX(-Math.PI / 2);
    noseGeo.scale(1, 0.5, 1.5);
    const nose = new THREE.Mesh(noseGeo, bodyMat);
    nose.position.set(0, 0, 7);
    group.add(nose);

    const wingShape = new THREE.Shape();
    wingShape.moveTo(0, 0);
    wingShape.lineTo(12, -8);
    wingShape.lineTo(12, -10);
    wingShape.lineTo(0, -6);
    wingShape.lineTo(-12, -10);
    wingShape.lineTo(-12, -8);
    wingShape.lineTo(0, 0);
    const wingGeo = new THREE.ExtrudeGeometry(wingShape, { depth: 0.2, bevelEnabled: false });
    const wingMat = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.4, roughness: 0.6 });
    const wings = new THREE.Mesh(wingGeo, wingMat);
    wings.rotation.x = -Math.PI / 2;
    wings.position.set(0, -0.5, 0);
    group.add(wings);

    for (const side of [-1, 1]) {
      const podGeo = new THREE.CylinderGeometry(0.8, 0.8, 4, 8);
      podGeo.rotateX(Math.PI / 2);
      const podMat = new THREE.MeshStandardMaterial({ color: 0x333333, metalness: 0.6, roughness: 0.3 });
      const pod = new THREE.Mesh(podGeo, podMat);
      pod.position.set(side * 4, -0.3, 1);
      group.add(pod);

      const glowGeo = new THREE.SphereGeometry(0.5, 8, 8);
      const glowMat = new THREE.MeshBasicMaterial({ color: 0xff6600, transparent: true, opacity: 0.6 });
      const glow = new THREE.Mesh(glowGeo, glowMat);
      glow.position.set(side * 4, -0.3, -1);
      glow.name = 'engineGlow';
      group.add(glow);
    }

    for (const side of [-1, 1]) {
      const finGeo = new THREE.BoxGeometry(0.15, 2, 3);
      const finMat = new THREE.MeshStandardMaterial({ color: accentColor, metalness: 0.4, roughness: 0.6 });
      const fin = new THREE.Mesh(finGeo, finMat);
      fin.position.set(side * 1.2, 1, -5);
      group.add(fin);
    }

  } else if (type === 'glider') {
    const bodyGeo = new THREE.ConeGeometry(0.4, 8, 6);
    bodyGeo.rotateX(Math.PI / 2);
    const bodyMat = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.3, roughness: 0.7 });
    group.add(new THREE.Mesh(bodyGeo, bodyMat));

    const cockpitGeo = new THREE.SphereGeometry(0.35, 6, 6, 0, Math.PI * 2, 0, Math.PI / 2);
    cockpitGeo.scale(0.6, 0.5, 1);
    const cockpitMat = new THREE.MeshStandardMaterial({ color: 0x88ccff, metalness: 0.9, roughness: 0.1, transparent: true, opacity: 0.7 });
    const cockpit = new THREE.Mesh(cockpitGeo, cockpitMat);
    cockpit.position.set(0, 0.3, 1);
    group.add(cockpit);

    const wingGeo = new THREE.BoxGeometry(15, 0.08, 1.2);
    const wingMat = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.3, roughness: 0.7 });
    group.add(new THREE.Mesh(wingGeo, wingMat).translateY(0).translateZ(-0.5));

    const tailGeo = new THREE.BoxGeometry(2.5, 0.08, 0.8);
    const tailMat = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.3, roughness: 0.7 });
    group.add(new THREE.Mesh(tailGeo, tailMat).translateZ(-3.5));

    const vStabGeo = new THREE.BoxGeometry(0.08, 1.2, 0.8);
    const vStabMat = new THREE.MeshStandardMaterial({ color: accentColor, metalness: 0.3, roughness: 0.7 });
    const vStab = new THREE.Mesh(vStabGeo, vStabMat);
    vStab.position.set(0, 0.6, -3.5);
    group.add(vStab);

    for (const side of [-1, 1]) {
      const weightGeo = new THREE.SphereGeometry(0.15, 6, 6);
      const weightMat = new THREE.MeshStandardMaterial({ color: accentColor });
      const weight = new THREE.Mesh(weightGeo, weightMat);
      weight.position.set(side * 7.5, 0, -0.5);
      group.add(weight);
    }
  }

  group.traverse(function(child) {
    if (child.isMesh) { child.castShadow = true; child.receiveShadow = true; }
  });

  return group;
}

// ── let: audioCtx ──
// ============================================================
// AUDIO ENGINE (Web Audio API)
// ============================================================
let audioCtx = null;

// ── let: masterGain ──
let masterGain = null;

// ── let: engineGain ──
let engineGain = null;

// ── let: windGain ──
let windGain = null;

// ── let: engineOsc ──
let engineOsc = null;

// ── let: engineNoise ──
let engineNoise = null;

// ── let: windNoise ──
let windNoise = null;

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

  // Engine sound: oscillator + noise
  engineGain = audioCtx.createGain();
  engineGain.gain.value = 0;
  engineGain.connect(masterGain);

  engineOsc = audioCtx.createOscillator();
  engineOsc.type = 'sawtooth';
  engineOsc.frequency.value = 80;
  engineOsc.connect(engineGain);
  engineOsc.start();

  // Low-pass filter for engine
  const engineFilter = audioCtx.createBiquadFilter();
  engineFilter.type = 'lowpass';
  engineFilter.frequency.value = 800;
  engineFilter.Q.value = 2;
  engineOsc.connect(engineFilter);
  engineFilter.connect(engineGain);

  // Engine noise
  engineNoise = audioCtx.createBufferSource();
  const bufferSize = audioCtx.sampleRate * 2;
  const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
  const data = buffer.getChannelData(0);
  for (let i = 0; i < bufferSize; i++) {
    data[i] = (Math.random() * 2 - 1) * 0.3;
  }
  engineNoise.buffer = buffer;
  engineNoise.loop = true;

  const noiseFilter = audioCtx.createBiquadFilter();
  noiseFilter.type = 'bandpass';
  noiseFilter.frequency.value = 300;
  noiseFilter.Q.value = 1;

  const noiseGain = audioCtx.createGain();
  noiseGain.gain.value = 0.15;

  engineNoise.connect(noiseFilter);
  noiseFilter.connect(noiseGain);
  noiseGain.connect(engineGain);
  engineNoise.start();

  // Wind noise
  windGain = audioCtx.createGain();
  windGain.gain.value = 0;
  windGain.connect(masterGain);

  windNoise = audioCtx.createBufferSource();
  const windBuffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
  const windData = windBuffer.getChannelData(0);
  for (let i = 0; i < bufferSize; i++) {
    windData[i] = (Math.random() * 2 - 1);
  }
  windNoise.buffer = windBuffer;
  windNoise.loop = true;

  const windFilter = audioCtx.createBiquadFilter();
  windFilter.type = 'lowpass';
  windFilter.frequency.value = 500;
  windFilter.Q.value = 0.5;

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

// ── function: updateAudio ──
function updateAudio(aircraft, speed) {
  if (!audioCtx) return;

  const throttle = aircraft.throttle;
  const rpm = aircraft.rpm / 100;
  const alt = Math.max(0, aircraft.pos.y);
  const airDensity = Math.exp(-alt / 8500);

  // Engine pitch based on RPM
  engineOsc.frequency.setTargetAtTime(60 + rpm * 200, audioCtx.currentTime, 0.05);

  // Engine volume based on throttle and air density
  const engineVol = throttle * (0.3 + airDensity * 0.7);
  engineGain.gain.setTargetAtTime(engineVol, audioCtx.currentTime, 0.05);

  // Wind noise based on speed
  const windVol = Math.min(0.3, speed * 0.003) * airDensity;
  windGain.gain.setTargetAtTime(windVol, audioCtx.currentTime, 0.1);

  // Filter cutoff based on speed
  const engineFilter = engineOsc.context.createBiquadFilter();
}

// ── function: createEnemyAI ──
// ============================================================
// DOGFIGHT ENEMY AI
// ============================================================
function createEnemyAI() {
  return {
    pos: new THREE.Vector3(500, 300, 500),
    vel: new THREE.Vector3(-10, 0, -10),
    rot: new THREE.Euler(0, 0, 0, 'YXZ'),
    targetPos: new THREE.Vector3(),
    alive: true,
    shootCooldown: 0,
    maneuvers: [],
    currentManeuver: 0,
    maneuverTimer: 0,
  };
}

// ── function: updateEnemyAI ──
function updateEnemyAI(enemy, player, dt) {
  if (!enemy.alive) return;

  enemy.maneuverTimer += dt;
  if (enemy.maneuverTimer > 3 + Math.random() * 4) {
    enemy.maneuverTimer = 0;
    // Pick new target
    const angle = Math.random() * Math.PI * 2;
    const dist = 400 + Math.random() * 600;
    const alt = 100 + Math.random() * 500;
    enemy.targetPos.set(
      player.pos.x + Math.cos(angle) * dist,
      alt,
      player.pos.z + Math.sin(angle) * dist
    );
  }

  // Move toward target
  const toTarget = enemy.targetPos.clone().sub(enemy.pos);
  const distToTarget = toTarget.length();
  if (distToTarget > 10) {
    toTarget.normalize();
    const speed = 30 + Math.random() * 10;
    enemy.vel.x += toTarget.x * speed * dt;
    enemy.vel.y += toTarget.y * speed * dt * 0.5;
    enemy.vel.z += toTarget.z * speed * dt;
  }

  // Avoid terrain
  const groundH = getTerrainHeight(enemy.pos.x, enemy.pos.z);
  if (enemy.pos.y < groundH + 50) {
    enemy.vel.y += 5 * dt;
  }

  // Clamp speed
  const maxSpeed = 60;
  const currentSpeed = enemy.vel.length();
  if (currentSpeed > maxSpeed) {
    enemy.vel.multiplyScalar(maxSpeed / currentSpeed);
  }

  // Update position
  enemy.pos.x += enemy.vel.x * dt;
  enemy.pos.y += enemy.vel.y * dt;
  enemy.pos.z += enemy.vel.z * dt;

  // Align with velocity
  const lookTarget = enemy.pos.clone().add(enemy.vel);
  enemy.rot.setFromVectorMatrix(new THREE.Matrix4().lookAt(enemy.pos, lookTarget, new THREE.Vector3(0, 1, 0)));

  // Shoot at player if close enough
  const distToPlayer = enemy.pos.distanceTo(player.pos);
  enemy.shootCooldown -= dt;
  if (distToPlayer < 800 && enemy.shootCooldown <= 0) {
    enemy.shootCooldown = 2 + Math.random() * 3;
    return 'shoot';
  }

  return null;
}

// ── function: createEnemyMesh ──
function createEnemyMesh() {
  const group = new THREE.Group();
  const bodyMat = new THREE.MeshStandardMaterial({ color: 0x884444, metalness: 0.4, roughness: 0.5 });

  const bodyGeo = new THREE.ConeGeometry(0.6, 8, 6);
  bodyGeo.rotateX(Math.PI / 2);
  group.add(new THREE.Mesh(bodyGeo, bodyMat));

  const wingGeo = new THREE.BoxGeometry(7, 0.1, 2);
  const wingMat = new THREE.MeshStandardMaterial({ color: 0x663333, metalness: 0.4, roughness: 0.5 });
  group.add(new THREE.Mesh(wingGeo, wingMat).translateZ(-0.5));

  const vStabGeo = new THREE.BoxGeometry(0.1, 2, 1.5);
  const vStab = new THREE.Mesh(vStabGeo, bodyMat);
  vStab.position.set(0, 1, -3.5);
  group.add(vStab);

  const glowGeo = new THREE.SphereGeometry(0.3, 6, 6);
  const glowMat = new THREE.MeshBasicMaterial({ color: 0xff4400, transparent: true, opacity: 0.6 });
  const glow = new THREE.Mesh(glowGeo, glowMat);
  glow.position.set(0, 0, -4.5);
  glow.name = 'engineGlow';
  group.add(glow);

  group.traverse(function(child) {
    if (child.isMesh) { child.castShadow = true; child.receiveShadow = true; }
  });

  return group;
}

// ── function: updateHUD ──
// ============================================================
// HUD UPDATE
// ============================================================
function updateHUD(aircraft, missionState) {
  const speed = aircraft.vel.length();
  const speedKts = Math.round(speed * MS2KT);
  const altFt = Math.round(aircraft.pos.y * M2FT);
  const heading = ((aircraft.rot.y * RAD2DEG) % 360 + 360) % 360;
  const vsFt = aircraft.vel.y * M2FT;

  document.getElementById('hudSpeed').textContent = speedKts;
  document.getElementById('hudAlt').textContent = altFt;
  document.getElementById('hudHdg').textContent = String(Math.round(heading)).padStart(3, '0');
  document.getElementById('hudRpm').textContent = Math.round(aircraft.rpm);
  document.getElementById('hudVs').textContent = Math.round(vsFt);
  document.getElementById('hudStall').textContent = aircraft.isStalled ? '!!' : '—';
  document.getElementById('hudStall').style.color = aircraft.isStalled ? '#ff4444' : '#fff';

  // Throttle bar
  document.getElementById('throttleBar').style.width = (aircraft.throttle * 100) + '%';

  // AoA bar
  const aoaPct = Math.min(100, (Math.abs(aircraft.aoa) / (22 * DEG2RAD)) * 100);
  document.getElementById('aoaBar').style.width = aoaPct + '%';
  document.getElementById('aoaBar').style.background = aircraft.isStalled
    ? 'linear-gradient(90deg, #ff4444, #ff0000)'
    : 'linear-gradient(90deg, #4a9eff, #ff6b35)';

  // G-force
  document.getElementById('gForce').textContent = aircraft.gForce.toFixed(1);
  document.getElementById('gForce').style.color = Math.abs(aircraft.gForce) > 7 ? '#ff4444' : '#fff';

  // Attitude indicator
  const horizonLine = document.getElementById('horizonLine');
  const attitudeEl = document.getElementById('hudAttitude');
  const pitchDeg = aircraft.rot.x * RAD2DEG;
  const rollDeg = aircraft.rot.z * RAD2DEG;

  horizonLine.style.transform = `rotate(${-rollDeg}deg) translateY(${pitchDeg * 1.5}px)`;
  attitudeEl.style.transform = `rotate(${-rollDeg}deg)`;

  // Flight path marker
  const fpm = document.getElementById('fpm');
  const flightPathAngle = Math.atan2(aircraft.vel.y, aircraft.vel.length()) * RAD2DEG;
  const speedVector = new THREE.Vector3();
  aircraft.vel.clone().applyAxisAngle(new THREE.Vector3(0, 1, 0), -aircraft.rot.y);
  const lateralSpeed = speedVector.x;
  fpm.style.left = (50 + lateralSpeed * 0.5) + '%';
  fpm.style.top = (50 - flightPathAngle * 0.8) + '%';

  // Mission messages
  if (missionState) {
    const msgEl = document.getElementById('hudMessage');
    if (missionState.message) {
      msgEl.textContent = missionState.message;
      msgEl.style.opacity = '1';
    } else {
      msgEl.style.opacity = '0';
    }
  }
}

// ── let: selectedPlane ──
// ============================================================
// MENU INTERACTION
// ============================================================
let selectedPlane = null;

// ── let: selectedScenario ──
let selectedScenario = null;

// ── function: selectPlane ──
function selectPlane(type) {
  selectedPlane = type;
  document.querySelectorAll('.aircraft-card').forEach(c => c.classList.remove('selected'));
  document.querySelector(`[data-plane="${type}"]`).classList.add('selected');
}

// ── function: selectScenario ──
function selectScenario(type) {
  selectedScenario = type;
  document.querySelectorAll('.scenario-card').forEach(c => c.classList.remove('selected'));
  document.querySelector(`[data-scenario="${type}"]`).classList.add('selected');
}

// ── function: noise2D ──
// ============================================================
// NOISE / TERRAIN GENERATION
// ============================================================
function noise2D(x, z) {
  const n = Math.sin(x * 12.9898 + z * 78.233) * 43758.5453;
  return n - Math.floor(n);
}

// ── function: smoothNoise ──
function smoothNoise(x, z, scale) {
  const sx = x / scale, sz = z / scale;
  const ix = Math.floor(sx), iz = Math.floor(sz);
  const fx = sx - ix, fz = sz - iz;
  const a = noise2D(ix, iz);
  const b = noise2D(ix + 1, iz);
  const c = noise2D(ix, iz + 1);
  const d = noise2D(ix + 1, iz + 1);
  const ux = fx * fx * (3 - 2 * fx);
  const uz = fz * fz * (3 - 2 * fz);
  return a + (b - a) * ux + (c - a) * uz + (a - b - c + d) * ux * uz;
}

// ── function: fbmNoise ──
function fbmNoise(x, z, octaves) {
  let val = 0, amp = 1, freq = 1, max = 0;
  for (let i = 0; i < octaves; i++) {
    val += smoothNoise(x * freq, z * freq, 1) * amp;
    max += amp;
    amp *= 0.5;
    freq *= 2;
  }
  return val / max;
}

// ── function: getBaseTerrainHeight ──
function getBaseTerrainHeight(px, pz) {
  let h = fbmNoise(px, pz, 6) * 2000 - 500;
  h += Math.sin(px * 0.0003) * Math.cos(pz * 0.0004) * 1200;
  h += Math.sin(px * 0.0008 + pz * 0.0006) * 600;
  h += Math.sin(px * 0.002 + 1.3) * Math.cos(pz * 0.0015 + 0.7) * 200;
  return h;
}

// ── function: isRunway ──
function isRunway(px, pz) {
  const zCenter = 0;
  const distToCenter = Math.abs(pz - zCenter);
  return px >= RUNWAY_START && px <= RUNWAY_END && distToCenter <= RUNWAY_WIDTH / 2;
}

// ── function: getTerrainHeight ──
function getTerrainHeight(px, pz) {
  let h = getBaseTerrainHeight(px, pz);

  // Flatten near runway
  const distToRunway = Math.abs(px);
  if (distToRunway < 800) {
    const blend = Math.max(0, 1 - distToRunway / 800);
    const runwayH = getRunwayHeight(px, pz);
    h = h * (1 - blend) + runwayH * blend;
  }

  // Also flatten along Z axis near runway
  const distToZCenter = Math.abs(pz);
  if (distToZCenter < 200 && px >= RUNWAY_START - 400 && px <= RUNWAY_END + 400) {
    const zBlend = Math.max(0, 1 - distToZCenter / 200);
    const runwayH = getRunwayHeight(px, pz);
    h = h * (1 - zBlend) + runwayH * zBlend;
  }

  return h;
}

// ── function: getRunwayHeight ──
function getRunwayHeight(px, pz) {
  const distToCenter = Math.abs(pz);
  if (distToCenter > RUNWAY_WIDTH / 2) return getBaseTerrainHeight(px, pz);

  // Crown shape
  const crown = 1 - Math.pow(distToCenter / (RUNWAY_WIDTH / 2), 2) * 0.02;

  // Slight slope at ends
  let slope = 0;
  if (px < RUNWAY_START + 200) {
    slope = (px - (RUNWAY_START + 200)) / 200 * 0.02;
  } else if (px > RUNWAY_END - 200) {
    slope = (px - (RUNWAY_END - 200)) / 200 * 0.02;
  }

  return getBaseTerrainHeight(px, pz) * crown + slope * 50;
}

// ── function: getTerrainNormal ──
function getTerrainNormal(px, pz) {
  const delta = 1;
  const hL = getBaseTerrainHeight(px - delta, pz);
  const hR = getBaseTerrainHeight(px + delta, pz);
  const hD = getBaseTerrainHeight(px, pz - delta);
  const hU = getBaseTerrainHeight(px, pz + delta);
  return new THREE.Vector3(hL - hR, 2 * delta, hD - hU).normalize();
}

// ── function: startGame ──
function startGame() {
  if (!selectedPlane || !selectedScenario) return;

  document.getElementById('menuOverlay').classList.add('hidden');
  document.getElementById('hud').classList.remove('hidden');

  if (selectedScenario === 'dogfight') {
    document.getElementById('dogfightUI').classList.remove('hidden');
  } else {
    document.getElementById('dogfightUI').classList.add('hidden');
  }

  if (selectedScenario === 'takeoff') {
    document.getElementById('missionUI').classList.remove('hidden');
    document.getElementById('missionObj').textContent = 'Takeoff: Apply throttle, accelerate, rotate at 80kt. Land: Approach at 120kt, flare at 50ft.';
  } else {
    document.getElementById('missionUI').classList.add('hidden');
  }

  initAudio();
  startScenario();
}

// ── function: setupShooting ──
// ============================================================
// SHOOTING HANDLER
// ============================================================
function setupShooting() {
  const canvas = document.getElementById('gameCanvas');
  canvas.addEventListener('mousedown', (e) => {
    if (gameState !== 'playing' || selectedScenario !== 'dogfight') return;
    if (e.button === 0) {
      // Left click - shoot
      const playerForward = new THREE.Vector3(1, 0, 0).applyEuler(playerAircraft.rot);
      const playerPos = playerAircraft.pos.clone();

      for (let i = dogfightEnemies.length - 1; i >= 0; i--) {
        const enemy = dogfightEnemies[i];
        if (!enemy.alive) continue;

        const toEnemy = enemy.pos.clone().sub(playerPos);
        const dist = toEnemy.length();
        toEnemy.normalize();

        const dot = playerForward.dot(toEnemy);
        const angle = Math.acos(Math.min(1, Math.max(-1, dot)));

        if (angle < 0.087 && dist < 2000) {
          enemy.alive = false;
          scene.remove(enemyMeshes[i]);
          enemyMeshes.splice(i, 1);
          dogfightEnemies.splice(i, 1);
          killCount++;
          document.getElementById('killCount').textContent = killCount;
          missionState.message = 'Enemy destroyed! ' + killCount + ' kills';

          if (dogfightEnemies.length === 0) {
            missionState.message = 'All enemies destroyed! Wave complete!';
            setTimeout(() => {
              for (let j = 0; j < 5; j++) {
                const enemy = createEnemyAI();
                const angle2 = (j / 5) * Math.PI * 2;
                const dist2 = 1500 + Math.random() * 1500;
                enemy.pos.set(
                  playerAircraft.pos.x + Math.cos(angle2) * dist2,
                  200 + Math.random() * 400,
                  playerAircraft.pos.z + Math.sin(angle2) * dist2
                );
                enemy.vel.set(-Math.cos(angle2) * 25, 0, -Math.sin(angle2) * 25);
                enemy.targetPos.set(
                  playerAircraft.pos.x + Math.cos(angle2 + Math.PI) * 600,
                  300 + Math.random() * 300,
                  playerAircraft.pos.z + Math.sin(angle2 + Math.PI) * 600
                );
                const mesh = createEnemyMesh();
                mesh.position.copy(enemy.pos);
                scene.add(mesh);
                enemyMeshes.push(mesh);
                dogfightEnemies.push(enemy);
              }
              missionState.message = 'New wave incoming!';
            }, 3000);
          }
          break;
        }
      }
    }
  });
}

// ── function: updatePlayerControls ──
// ============================================================
// PLAYER CONTROLS
// ============================================================
function updatePlayerControls() {
  const aircraft = playerAircraft;
  const def = aircraft.def;

  // Mouse controls (pitch/roll)
  const mouseSensitivity = 0.002;
  aircraft.pitchInput = -mouse.dy * mouseSensitivity;
  aircraft.rollInput = -mouse.dx * mouseSensitivity;

  // Keyboard overrides
  if (keys['w']) aircraft.pitchInput = Math.max(aircraft.pitchInput, -1);
  if (keys['s']) aircraft.pitchInput = Math.min(aircraft.pitchInput, 1);
  if (keys['a']) aircraft.rollInput = Math.max(aircraft.rollInput, -1);
  if (keys['d']) aircraft.rollInput = Math.min(aircraft.rollInput, 1);
  if (keys['q']) aircraft.yawInput = Math.max(aircraft.yawInput, -1);
  if (keys['e']) aircraft.yawInput = Math.min(aircraft.yawInput, 1);

  // Throttle
  if (def.maxThrust > 0) {
    if (keys['shift']) aircraft.targetThrottle = Math.min(1, aircraft.targetThrottle + 0.02);
    if (keys['control']) aircraft.targetThrottle = Math.max(0, aircraft.targetThrottle - 0.02);
  }

  // Brake
  aircraft.brake = keys['f'] || false;

  // Speed brake (dogfight)
  if (keys['g']) {
    aircraft.speedBrake = Math.min(1, aircraft.speedBrake + 0.05);
  } else {
    aircraft.speedBrake = Math.max(0, aircraft.speedBrake - 0.02);
  }

  // Gear toggle (V key)
  if (keys['v']) {
    keys['v'] = false; // prevent repeat
    aircraft.gearDown = !aircraft.gearDown;
  }
  if (aircraft.gearDown) {
    aircraft.gearExtension = Math.min(1, aircraft.gearExtension + 0.05);
  } else {
    aircraft.gearExtension = Math.max(0, aircraft.gearExtension - 0.05);
  }

  // Reset mouse deltas
  mouse.dx = 0;
  mouse.dy = 0;

  // Auto throttle for glider
  if (def.maxThrust === 0) {
    aircraft.targetThrottle = 0;
  }
}

// ── function: updateEngineGlow ──
// ============================================================
// ENGINE GLOW UPDATE
// ============================================================
function updateEngineGlow() {
  if (!playerMesh) return;
  playerMesh.traverse((child) => {
    if (child.name === 'engineGlow' && child.isMesh) {
      const intensity = playerAircraft.throttle;
      child.material.opacity = 0.2 + intensity * 0.6;
      child.scale.setScalar(0.5 + intensity * 1.5);
      child.material.color.setHSL(0.08 - intensity * 0.05, 1, 0.4 + intensity * 0.3);
    }
  });
}

// ── function: updateAircraftPhysics ──
function updateAircraftPhysics(aircraft, dt) {
  const def = aircraft.def;
  aircraft.lastTime = performance.now();

  // Throttle response (smooth)
  aircraft.throttle += (aircraft.targetThrottle - aircraft.throttle) * def.throttleResponse * dt * 60;
  aircraft.throttle = Math.max(0, Math.min(1, aircraft.throttle));
  aircraft.rpm = aircraft.throttle * 100;

  // Engine temperature
  if (aircraft.throttle > 0.1) {
    aircraft.engineTemp += aircraft.throttle * 2 * dt - 0.5 * dt;
    aircraft.engineTemp = Math.min(100, Math.max(20, aircraft.engineTemp));
  } else {
    aircraft.engineTemp -= 3 * dt;
    aircraft.engineTemp = Math.max(20, aircraft.engineTemp);
  }

  // Air density based on altitude
  const altM = Math.max(0, aircraft.pos.y);
  const airDensity = SEA_LEVEL_DENSITY * Math.exp(-altM / 8500);

  // Speed
  const speed = aircraft.vel.length();
  if (speed < 0.001) {
    aircraft.dynamicPressure = 0;
    aircraft.aoa = 0;
    aircraft.sideslip = 0;
    aircraft.lift = 0;
    aircraft.drag = 0;
    aircraft.gForce = 1;
    aircraft.vel.y -= GRAVITY * dt;
    aircraft.pos.y += aircraft.vel.y * dt;
    return;
  }

  const velDir = aircraft.vel.clone().normalize();
  aircraft.dynamicPressure = 0.5 * airDensity * speed * speed;

  // Angle of attack
  const forward = new THREE.Vector3(1, 0, 0);
  forward.applyEuler(aircraft.rot);
  aircraft.aoa = forward.angleTo(velDir);
  const cross = new THREE.Vector3().crossVectors(forward, velDir);
  if (cross.y < 0) aircraft.aoa = -aircraft.aoa;

  // Sideslip
  const lateralVel = new THREE.Vector3().copy(aircraft.vel).applyEuler(new THREE.Euler(0, -aircraft.rot.y, 0));
  aircraft.sideslip = Math.atan2(lateralVel.x, aircraft.vel.length());

  // Stall detection
  const stallThreshold = def.stallAngle;
  aircraft.stallProgress = Math.max(0, (Math.abs(aircraft.aoa) - stallThreshold) / (def.maxAoA - stallThreshold));
  aircraft.isStalled = aircraft.stallProgress > 0;

  // Lift coefficient
  let cl = def.liftCurveSlope * aircraft.aoa;
  if (aircraft.isStalled) {
    cl *= (1 - aircraft.stallProgress * 0.8);
    cl += Math.sin(aircraft.aoa * 3) * aircraft.stallProgress * 0.3;
  }

  aircraft.lift = aircraft.dynamicPressure * def.wingArea * cl;

  // Drag
  let cd = def.dragCoeff;
  if (def.maxThrust > 0) cd += def.inducedDragFactor * cl * cl;
  if (aircraft.isStalled) cd += aircraft.stallProgress * 2;
  cd += aircraft.speedBrake * 0.5;
  aircraft.drag = aircraft.dynamicPressure * def.wingArea * cd;

  // Thrust
  aircraft.thrust = def.maxThrust > 0 ? def.maxThrust * aircraft.throttle : 0;

  // Ground friction
  if (aircraft.gearExtension > 0.5 && speed > 5) {
    const groundH = getTerrainHeight(aircraft.pos.x, aircraft.pos.z);
    if (aircraft.pos.y <= groundH + 2) {
      const frictionForce = aircraft.def.mass * GRAVITY * 0.4 * aircraft.brake * 2;
      aircraft.vel.multiplyScalar(1 - Math.min(1, frictionForce / (aircraft.def.mass * speed) * dt));
    }
  }

  // Forces
  const thrustForce = new THREE.Vector3(aircraft.thrust, 0, 0);
  const dragForce = velDir.clone().negate().multiplyScalar(aircraft.drag);
  const weightForce = new THREE.Vector3(0, -aircraft.def.mass * GRAVITY, 0);

  // Lift direction
  const liftDir = new THREE.Vector3();
  const up = new THREE.Vector3(0, 1, 0);
  const liftPlane = new THREE.Vector3().crossVectors(velDir, up);
  if (liftPlane.length() > 0.001) {
    liftDir.crossVectors(velDir, liftPlane).normalize();
    liftDir.y = Math.abs(liftDir.y) > 0.01 ? Math.sign(liftDir.y) : 1;
  } else {
    liftDir.copy(up);
  }
  const liftForce = liftDir.clone().multiplyScalar(aircraft.lift);

  const totalForce = new THREE.Vector3();
  totalForce.add(thrustForce);
  totalForce.add(liftForce);
  totalForce.add(dragForce);
  totalForce.add(weightForce);

  // Angular acceleration
  const controlEffectiveness = Math.min(1, aircraft.dynamicPressure / 5000);
  const pitchAcc = aircraft.pitchInput * controlEffectiveness * def.pitchRate * 60 * dt;
  const rollAcc = aircraft.rollInput * controlEffectiveness * def.rollRate * 60 * dt;
  const yawAcc = aircraft.yawInput * controlEffectiveness * def.yawRate * 60 * dt;

  aircraft.angularVel.x += pitchAcc;
  aircraft.angularVel.y += yawAcc;
  aircraft.angularVel.z += rollAcc;

  const dampingFactor = Math.exp(-dt * 2);
  aircraft.angularVel.x *= dampingFactor;
  aircraft.angularVel.y *= dampingFactor;
  aircraft.angularVel.z *= dampingFactor;

  // G-force
  const gMag = totalForce.length() / aircraft.def.mass;
  aircraft.gForce = gMag / GRAVITY;
  if (Math.abs(aircraft.gForce) > def.maxG) {
    aircraft.gForce = Math.sign(aircraft.gForce) * def.maxG;
  }

  // Stall effects
  if (aircraft.isStalled) {
    const sr = 1 - aircraft.stallProgress * 0.7;
    aircraft.angularVel.x *= sr;
    aircraft.angularVel.z *= sr;
    aircraft.angularVel.y *= (1 - aircraft.stallProgress * 0.3);
  }

  // Update rotation
  aircraft.rot.x += aircraft.angularVel.x * dt;
  aircraft.rot.y += aircraft.angularVel.y * dt;
  aircraft.rot.z += aircraft.angularVel.z * dt;
  aircraft.rot.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, aircraft.rot.x));

  // Translation
  const accel = totalForce.clone().divideScalar(aircraft.def.mass);
  aircraft.vel.x += accel.x * dt;
  aircraft.vel.y += accel.y * dt;
  aircraft.vel.z += accel.z * dt;

  aircraft.pos.x += aircraft.vel.x * dt;
  aircraft.pos.y += aircraft.vel.y * dt;
  aircraft.pos.z += aircraft.vel.z * dt;

  // Ground collision
  const groundH = getTerrainHeight(aircraft.pos.x, aircraft.pos.z);
  if (aircraft.pos.y < groundH + 1) {
    aircraft.pos.y = groundH + 1;
    if (aircraft.vel.y < -5) aircraft.crashed = true;
    aircraft.vel.y = Math.max(0, aircraft.vel.y);
    const normal = getTerrainNormal(aircraft.pos.x, aircraft.pos.z);
    const proj = new THREE.Vector3().copy(forward).projectOnPlane(normal);
    if (proj.length() > 0.01) {
      const m = new THREE.Matrix4().lookAt(new THREE.Vector3(), proj.normalize(), normal);
      aircraft.rot.setFromRotationMatrix(m);
    }
  }

  // Ground effect
  if (aircraft.pos.y < 100 && aircraft.pos.y > 0) {
    aircraft.lift *= (1 + Math.max(0, 1 - aircraft.pos.y / 100) * 0.15);
  }
}

// ── function: createTerrainMesh ──
// ============================================================
// TERRAIN MESH CREATION (Improved)
// ============================================================
function createTerrainMesh() {
  const size = TERRAIN_SIZE;
  const segments = TERRAIN_RES;
  const geo = new THREE.PlaneGeometry(size, size, segments, segments);
  geo.rotateX(-Math.PI / 2);

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

  for (let i = 0; i < positions.count; i++) {
    const x = positions.getX(i);
    const z = positions.getZ(i);
    const h = getTerrainHeight(x, z);
    positions.setY(i, h);

    // Color based on height with smooth transitions
    const normH = (h + 500) / 2500;
    let r, g, b;
    if (isRunway(x, z)) {
      r = 0.22; g = 0.22; b = 0.22;
    } else if (h < -150) {
      r = 0.05 + normH * 0.1; g = 0.15 + normH * 0.2; b = 0.4 + normH * 0.2;
    } else if (normH < 0.1) {
      r = 0.15 + normH; g = 0.45 + normH * 0.5; b = 0.1;
    } else if (normH < 0.25) {
      r = 0.2 + normH * 0.5; g = 0.45 + normH * 0.3; b = 0.15;
    } else if (normH < 0.45) {
      r = 0.35 + normH * 0.3; g = 0.35 + normH * 0.2; b = 0.2;
    } else if (normH < 0.65) {
      r = 0.5 + normH * 0.2; g = 0.48 + normH * 0.1; b = 0.45;
    } else {
      r = 0.85 + normH * 0.1; g = 0.85 + normH * 0.1; b = 0.88 + normH * 0.05;
    }
    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,
    metalness: 0.05,
    roughness: 0.85,
    flatShading: false,
  });

  const mesh = new THREE.Mesh(geo, mat);
  mesh.receiveShadow = true;
  return mesh;
}

// ── function: createRunwayMesh ──
// ============================================================
// RUNWAY MESH (Improved with lights)
// ============================================================
function createRunwayMesh() {
  const group = new THREE.Group();

  // Main runway surface
  const rwGeo = new THREE.PlaneGeometry(RUNWAY_LENGTH, RUNWAY_WIDTH);
  rwGeo.rotateX(-Math.PI / 2);
  const rwMat = new THREE.MeshStandardMaterial({ color: 0x2a2a2a, roughness: 0.9 });
  const rw = new THREE.Mesh(rwGeo, rwMat);
  rw.position.set(0, 0.15, 0);
  group.add(rw);

  // Runway edge lights
  const lightMat = new THREE.MeshBasicMaterial({ color: 0x44ff44 });
  const lightMatWhite = new THREE.MeshBasicMaterial({ color: 0xffffff });
  const lightMatAmber = new THREE.MeshBasicMaterial({ color: 0xffaa00 });

  for (let x = RUNWAY_START; x <= RUNWAY_END; x += 50) {
    for (const side of [-1, 1]) {
      const z = side * RUNWAY_WIDTH / 2;
      const mat = (x < RUNWAY_START + 200 || x > RUNWAY_END - 200) ? lightMatAmber : lightMat;
      const geo = new THREE.SphereGeometry(0.3, 6, 6);
      const mesh = new THREE.Mesh(geo, mat);
      mesh.position.set(x, 0.3, z);
      group.add(mesh);
    }
  }

  // Threshold lights (bright white)
  for (const endX of [RUNWAY_START + 10, RUNWAY_END - 10]) {
    for (let z = -RUNWAY_WIDTH / 2 + 5; z <= RUNWAY_WIDTH / 2 - 5; z += 10) {
      const geo = new THREE.SphereGeometry(0.5, 6, 6);
      const mesh = new THREE.Mesh(geo, lightMatWhite);
      mesh.position.set(endX, 0.4, z);
      group.add(mesh);
    }
  }

  // Center line markings
  for (let x = RUNWAY_START + 50; x < RUNWAY_END - 50; x += 40) {
    const dashGeo = new THREE.PlaneGeometry(20, 1.5);
    dashGeo.rotateX(-Math.PI / 2);
    const dashMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.8 });
    const dash = new THREE.Mesh(dashGeo, dashMat);
    dash.position.set(x, 0.18, 0);
    group.add(dash);
  }

  // Touchdown zone markings
  for (const endX of [RUNWAY_START + 150, RUNWAY_END - 150]) {
    for (let z = -15; z <= 15; z += 10) {
      const markGeo = new THREE.PlaneGeometry(4, 3);
      markGeo.rotateX(-Math.PI / 2);
      const markMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.7 });
      const mark = new THREE.Mesh(markGeo, markMat);
      mark.position.set(endX, 0.19, z);
      group.add(mark);
    }
  }

  // Aimpoint marking
  for (const endX of [RUNWAY_START + 300, RUNWAY_END - 300]) {
    for (const z of [-5, 5]) {
      const markGeo = new THREE.PlaneGeometry(6, 2);
      markGeo.rotateX(-Math.PI / 2);
      const markMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.7 });
      const mark = new THREE.Mesh(markGeo, markMat);
      mark.position.set(endX, 0.19, z);
      group.add(mark);
    }
  }

  // Edge lines
  for (const side of [-1, 1]) {
    const edgeGeo = new THREE.PlaneGeometry(RUNWAY_LENGTH, 0.8);
    edgeGeo.rotateX(-Math.PI / 2);
    const edgeMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.6 });
    const edge = new THREE.Mesh(edgeGeo, edgeMat);
    edge.position.set(0, 0.17, side * RUNWAY_WIDTH / 2);
    group.add(edge);
  }

  // Runway approach lights (bright)
  for (const endX of [RUNWAY_START - 100, RUNWAY_END + 100]) {
    for (let z = -30; z <= 30; z += 15) {
      const geo = new THREE.SphereGeometry(0.6, 6, 6);
      const mesh = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({ color: 0xffffff }));
      mesh.position.set(endX, 0.5, z);
      group.add(mesh);
    }
  }

  return group;
}

// ── main ──
// ============================================================
// MAIN: INITIALIZATION AND GAME LOOP
// ============================================================
let scene, camera, renderer;
let terrainMesh, runwayMesh;
let playerAircraft, playerMesh;
let enemyMeshes = [];
let missionState = null;
let gameState = 'menu';
let lastFrameTime = 0;
let keys = {};
let mouse = { x: 0, y: 0, dx: 0, dy: 0 };
let isPointerLocked = false;
let killCount = 0;
let turnCount = 0;
let dogfightEnemies = [];
let cloudMeshes = [];
let stars = null;
let sunLight, ambientLight;
let skyMesh;

function init() {
  scene = new THREE.Scene();
  scene.fog = new THREE.FogExp2(0x87CEEB, 0.00008);

  camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100000);

  const canvas = document.getElementById('gameCanvas');
  renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
  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.1;

  ambientLight = new THREE.AmbientLight(0x404060, 0.6);
  scene.add(ambientLight);

  sunLight = new THREE.DirectionalLight(0xfff4e0, 1.5);
  sunLight.position.set(5000, 8000, 3000);
  sunLight.castShadow = true;
  sunLight.shadow.mapSize.width = 2048;
  sunLight.shadow.mapSize.height = 2048;
  sunLight.shadow.camera.near = 100;
  sunLight.shadow.camera.far = 30000;
  sunLight.shadow.camera.left = -5000;
  sunLight.shadow.camera.right = 5000;
  sunLight.shadow.camera.top = 5000;
  sunLight.shadow.camera.bottom = -5000;
  scene.add(sunLight);

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

  createSky();
  createClouds();
  createStars();

  terrainMesh = createTerrainMesh();
  scene.add(terrainMesh);

  runwayMesh = createRunwayMesh();
  scene.add(runwayMesh);

  // Sun visual
  const sunGeo = new THREE.SphereGeometry(200, 16, 16);
  const sunMat = new THREE.MeshBasicMaterial({ color: 0xfff8e0 });
  const sunMesh = new THREE.Mesh(sunGeo, sunMat);
  sunMesh.position.copy(sunLight.position);
  scene.add(sunMesh);

  setupInput();
  setupShooting();
  window.addEventListener('resize', onResize);

  lastFrameTime = performance.now();
  requestAnimationFrame(gameLoop);
}

function createSky() {
  const skyGeo = new THREE.SphereGeometry(50000, 32, 32);
  const skyMat = new THREE.ShaderMaterial({
    uniforms: {
      topColor: { value: new THREE.Color(0x0044aa) },
      bottomColor: { value: new THREE.Color(0x87CEEB) },
      offset: { value: 20 },
      exponent: { value: 0.4 },
    },
    vertexShader: `
      varying vec3 vWorldPosition;
      void main() {
        vec4 worldPosition = modelMatrix * vec4(position, 1.0);
        vWorldPosition = worldPosition.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);
}

function createClouds() {
  for (let i = 0; i < 80; i++) {
    const cloudGroup = new THREE.Group();
    const numPuffs = 3 + Math.floor(Math.random() * 5);
    for (let j = 0; j < numPuffs; j++) {
      const puffGeo = new THREE.SphereGeometry(30 + Math.random() * 50, 8, 6);
      const puffMat = new THREE.MeshStandardMaterial({
        color: 0xffffff, transparent: true, opacity: 0.7,
        roughness: 1, metalness: 0,
      });
      const puff = new THREE.Mesh(puffGeo, puffMat);
      puff.position.set(
        (Math.random() - 0.5) * 80, (Math.random() - 0.5) * 15,
        (Math.random() - 0.5) * 60
      );
      puff.scale.y = 0.4;
      cloudGroup.add(puff);
    }
    cloudGroup.position.set(
      (Math.random() - 0.5) * 40000,
      800 + Math.random() * 2000,
      (Math.random() - 0.5) * 40000
    );
    scene.add(cloudGroup);
    cloudMeshes.push(cloudGroup);
  }
}

function createStars() {
  const starGeo = new THREE.BufferGeometry();
  const starPositions = [];
  for (let i = 0; i < 3000; i++) {
    const theta = Math.random() * Math.PI * 2;
    const phi = Math.random() * Math.PI;
    const r = 45000;
    starPositions.push(r * Math.sin(phi) * Math.cos(theta), r * Math.cos(phi), r * Math.sin(phi) * Math.sin(theta));
  }
  starGeo.setAttribute('position', new THREE.Float32BufferAttribute(starPositions, 3));
  stars = new THREE.Points(starGeo, new THREE.PointsMaterial({ color: 0xffffff, size: 2, transparent: true, opacity: 0.8 }));
  scene.add(stars);
}

function setupInput() {
  const canvas = document.getElementById('gameCanvas');
  canvas.addEventListener('click', () => {
    if (gameState === 'playing') canvas.requestPointerLock();
  });
  document.addEventListener('pointerlockchange', () => {
    isPointerLocked = document.pointerLockElement === canvas;
  });
  document.addEventListener('keydown', (e) => {
    keys[e.key.toLowerCase()] = true;
    if (e.key.toLowerCase() === 'r' && gameState === 'crashed') restartGame();
  });
  document.addEventListener('keyup', (e) => { keys[e.key.toLowerCase()] = false; });
  document.addEventListener('mousemove', (e) => {
    if (isPointerLocked) { mouse.dx += e.movementX; mouse.dy += e.movementY; }
  });
}

function onResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

function startScenario() {
  const aircraft = createAircraft(selectedPlane);
  playerAircraft = aircraft;
  playerMesh = createAircraftMesh(selectedPlane);
  scene.add(playerMesh);

  if (selectedScenario === 'takeoff') {
    aircraft.pos.set(RUNWAY_START, getRunwayHeight(RUNWAY_START, 0) + 1, 0);
    aircraft.vel.set(0, 0, 0);
    aircraft.targetThrottle = 0;
    missionState = {
      phase: 'takeoff',
      message: 'Press Shift to apply throttle. Rotate at 80kt!',
      takeoffSpeed: 80 * KT2MS,
      targetAlt: 500,
      approachAlt: 300,
      approachSpeed: 120 * KT2MS,
    };
  } else if (selectedScenario === 'dogfight') {
    aircraft.pos.set(0, 200, 0);
    aircraft.vel.set(20, 0, 0);
    missionState = {
      phase: 'dogfight',
      message: 'Lock on red enemies and shoot! (Left Click)',
      kills: 0,
    };
    killCount = 0;
    turnCount = 0;
    dogfightEnemies = [];
    for (let i = 0; i < 5; i++) {
      const enemy = createEnemyAI();
      const angle = (i / 5) * Math.PI * 2;
      const dist = 1000 + Math.random() * 2000;
      enemy.pos.set(Math.cos(angle) * dist, 200 + Math.random() * 400, Math.sin(angle) * dist);
      enemy.vel.set(-Math.cos(angle) * 20, 0, -Math.sin(angle) * 20);
      enemy.targetPos.set(
        playerAircraft.pos.x + Math.cos(angle + Math.PI) * 800,
        300 + Math.random() * 300,
        playerAircraft.pos.z + Math.sin(angle + Math.PI) * 800
      );
      const mesh = createEnemyMesh();
      mesh.position.copy(enemy.pos);
      scene.add(mesh);
      enemyMeshes.push(mesh);
      dogfightEnemies.push(enemy);
    }
  }

  gameState = 'playing';
  document.getElementById('hud').classList.remove('hidden');
}

function restartGame() {
  if (!scene || !playerAircraft) return;
  if (playerMesh) { scene.remove(playerMesh); playerMesh = null; }
  enemyMeshes.forEach(m => scene.remove(m));
  enemyMeshes = [];
  dogfightEnemies = [];

  resetAircraft(playerAircraft);
  playerAircraft.pos.set(RUNWAY_START, getRunwayHeight(RUNWAY_START, 0) + 1, 0);
  gameState = 'playing';
  missionState.phase = 'takeoff';
  missionState.message = 'Restarted. Apply throttle!';
}

function updateDogfight() {
  for (let i = dogfightEnemies.length - 1; i >= 0; i--) {
    const enemy = dogfightEnemies[i];
    if (!enemy.alive) continue;

    const action = updateEnemyAI(enemy, playerAircraft, 1/60);
    if (action === 'shoot') {
      const dist = enemy.pos.distanceTo(playerAircraft.pos);
      if (dist < 600 && Math.random() < 0.1) {
        missionState.message = 'WARNING: Incoming fire!';
      }
    }

    if (enemyMeshes[i]) {
      enemyMeshes[i].position.copy(enemy.pos);
      enemyMeshes[i].rotation.copy(enemy.rot);
    }
  }

  const absRoll = Math.abs(playerAircraft.rot.z);
  if (absRoll > 0.5) {
    turnCount += 0.01;
    document.getElementById('turnCount').textContent = Math.round(turnCount);
  }
}

function updateTakeoffLanding() {
  const ms = missionState;
  const speed = playerAircraft.vel.length();
  const speedKts = speed * MS2KT;
  const alt = playerAircraft.pos.y;

  if (ms.phase === 'takeoff') {
    if (speedKts > ms.takeoffSpeed && alt > 2) {
      ms.phase = 'climb';
      ms.message = 'Climb to 500ft, then circle for approach.';
    } else if (speedKts < ms.takeoffSpeed) {
      ms.message = 'Speed: ' + Math.round(speedKts) + 'kt. Keep accelerating!';
    }
  } else if (ms.phase === 'climb') {
    if (alt > ms.targetAlt) {
      ms.phase = 'circle';
      ms.message = 'Reach altitude. Turn to head back to runway.';
    }
    ms.message = 'Alt: ' + Math.round(alt * M2FT) + 'ft. Climb to 500ft.';
  } else if (ms.phase === 'circle') {
    const distToRunway = Math.sqrt(playerAircraft.pos.x ** 2 + playerAircraft.pos.z ** 2);
    if (distToRunway < 2000 && alt > ms.approachAlt) {
      ms.phase = 'approach';
      ms.message = 'Begin descent. Approach at 120kt, 500ft AGL.';
    }
    ms.message = 'Head to runway. Speed: ' + Math.round(speedKts) + 'kt.';
  } else if (ms.phase === 'approach') {
    if (alt < ms.approachAlt) {
      ms.phase = 'final';
      ms.message = 'Final approach! Flare at 50ft!';
    }
    ms.message = 'Descend to 120kt approach speed. Alt: ' + Math.round(alt * M2FT) + 'ft.';
  } else if (ms.phase === 'final') {
    if (alt < 50) {
      ms.phase = 'flare';
      ms.message = 'FLARE! Pull up!';
    }
    ms.message = 'Alt: ' + Math.round(alt * M2FT) + 'ft. Flare at 50ft!';
  } else if (ms.phase === 'flare') {
    if (alt < 10 && speedKts < 60) {
      ms.phase = 'landed';
      ms.message = 'Touchdown! Press R to restart.';
    }
    ms.message = 'Flare! Pull up gently!';
  } else if (ms.phase === 'landed') {
    ms.message = 'Landed successfully! Press R to fly again.';
  }

  if (ms.phase === 'approach' && playerAircraft.def.maxThrust > 0) {
    playerAircraft.targetThrottle = 0.3;
  }
}

function updateCamera() {
  const aircraft = playerAircraft;
  const speed = aircraft.vel.length();
  const camDist = 15 + speed * 0.1;
  const camHeight = 5 + speed * 0.05;

  const camOffset = new THREE.Vector3(-camDist, camHeight, 0);
  camOffset.applyEuler(aircraft.rot);
  const targetCamPos = aircraft.pos.clone().add(camOffset);
  camera.position.lerp(targetCamPos, 0.08);

  const lookAhead = aircraft.vel.clone().multiplyScalar(0.3);
  const targetLook = aircraft.pos.clone().add(lookAhead);
  camera.lookAt(targetLook);
}

function gameLoop(timestamp) {
  requestAnimationFrame(gameLoop);

  const dt = Math.min((timestamp - lastFrameTime) / 1000, 0.05);
  lastFrameTime = timestamp;

  if (gameState !== 'playing') {
    renderer.render(scene, camera);
    return;
  }

  updatePlayerControls();
  updateAircraftPhysics(playerAircraft, dt);

  playerMesh.position.copy(playerAircraft.pos);
  playerMesh.rotation.copy(playerAircraft.rot);

  updateEngineGlow();
  updateCamera();
  updateAudio(playerAircraft, playerAircraft.vel.length());

  if (selectedScenario === 'takeoff') {
    updateTakeoffLanding();
  } else if (selectedScenario === 'dogfight') {
    updateDogfight();
  }

  updateHUD(playerAircraft, missionState);

  if (skyMesh) {
    const alt = playerAircraft.pos.y;
    const skyDarkness = Math.max(0, Math.min(1, alt / 15000));
    skyMesh.material.uniforms.topColor.value.setHSL(0.6, 0.8, 0.6 - skyDarkness * 0.5);
    skyMesh.material.uniforms.bottomColor.value.setHSL(0.55, 0.6, 0.7 - skyDarkness * 0.4);
  }

  if (stars) {
    const alt = playerAircraft.pos.y;
    stars.material.opacity = Math.max(0, Math.min(1, (alt - 5000) / 10000));
  }

  scene.fog.color.setHSL(0.55, 0.3, 0.7 - Math.max(0, Math.min(0.5, playerAircraft.pos.y / 20000)));

  if (playerAircraft.crashed) {
    gameState = 'crashed';
    missionState.message = 'CRASHED! Press R to restart.';
    updateHUD(playerAircraft, missionState);
  }

  renderer.render(scene, camera);
}

// Initialize on load
init();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-35b-a3b","provider":"lmstudio","persona":"composer","sessionId":"6748abbd-d84b-4589-8e83-9fe2b5cf5288","tokensIn":2271826,"tokensOut":52460,"tokensTotal":2324286,"turns":49,"toolCalls":58,"failedToolCalls":5,"timestamp":"2026-07-20T22:27:14.939Z"} -->