← Flight Simulator results

Flight Simulator

thinkingcap qwen3.6 27b · full

Initial view of Flight Simulator

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 57,481 bytes · SHA-256 dc94b8f4c2a3
<!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; }
body { overflow: hidden; background: #000; font-family: 'Segoe UI', Arial, sans-serif; cursor: none; }
canvas { display: block; }

/* Loading Screen */
#loading-screen {
  position: fixed; inset: 0; z-index: 1000;
  background: linear-gradient(135deg, #0a0a2e 0%, #1a1a4e 50%, #0d0d3b 100%);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  transition: opacity 0.8s ease;
}
#loading-screen.hidden { opacity: 0; pointer-events: none; }
#loading-screen h1 {
  font-size: 4em; color: #fff; text-shadow: 0 0 30px rgba(0,150,255,0.8);
  letter-spacing: 8px; margin-bottom: 10px;
}
#loading-screen .subtitle { color: #6af; font-size: 1.2em; letter-spacing: 4px; margin-bottom: 40px; }
#load-bar-outer { width: 300px; height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden; }
#load-bar-inner { width: 0%; height: 100%; background: linear-gradient(90deg, #0af, #f0a); transition: width 0.3s; }

/* Menu Screen */
#menu-screen {
  position: fixed; inset: 0; z-index: 900;
  background: linear-gradient(135deg, rgba(5,5,25,0.97), rgba(15,15,45,0.97));
  display: none; flex-direction: column; align-items: center; justify-content: center;
}
#menu-screen.active { display: flex; }
.menu-title { font-size: 3em; color: #fff; text-shadow: 0 0 20px rgba(0,150,255,0.6); letter-spacing: 6px; margin-bottom: 8px; }
.menu-subtitle { color: #4af; font-size: 1em; letter-spacing: 3px; margin-bottom: 40px; }

.aircraft-select { display: flex; gap: 20px; margin-bottom: 30px; }
.ac-card {
  width: 200px; padding: 20px; border-radius: 12px; cursor: pointer;
  background: rgba(255,255,255,0.05); border: 2px solid rgba(255,255,255,0.1);
  transition: all 0.3s; text-align: center;
}
.ac-card:hover { border-color: #4af; background: rgba(70,170,255,0.1); transform: translateY(-4px); }
.ac-card.selected { border-color: #0f0; background: rgba(0,255,100,0.1); box-shadow: 0 0 20px rgba(0,255,100,0.3); }
.ac-card .ac-icon { font-size: 3em; margin-bottom: 8px; }
.ac-card .ac-name { color: #fff; font-size: 1.1em; font-weight: bold; margin-bottom: 4px; }
.ac-card .ac-type { color: #aaa; font-size: 0.8em; margin-bottom: 12px; }
.ac-stats { text-align: left; }
.ac-stat { display: flex; justify-content: space-between; margin: 3px 0; font-size: 0.75em; color: #ccc; }
.ac-stat-bar { width: 60px; height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden; }
.ac-stat-fill { height: 100%; border-radius: 2px; }

.scenario-select { display: flex; gap: 20px; margin-bottom: 30px; }
.sc-card {
  width: 250px; padding: 20px; border-radius: 12px; cursor: pointer;
  background: rgba(255,255,255,0.05); border: 2px solid rgba(255,255,255,0.1);
  transition: all 0.3s; text-align: center;
}
.sc-card:hover { border-color: #fa4; background: rgba(255,170,70,0.1); transform: translateY(-4px); }
.sc-card.selected { border-color: #fa4; background: rgba(255,170,70,0.15); box-shadow: 0 0 20px rgba(255,170,70,0.3); }
.sc-card .sc-icon { font-size: 2.5em; margin-bottom: 8px; }
.sc-card .sc-name { color: #fff; font-size: 1.1em; font-weight: bold; margin-bottom: 4px; }
.sc-card .sc-desc { color: #aaa; font-size: 0.8em; line-height: 1.4; }

#start-btn {
  padding: 15px 60px; border: none; border-radius: 8px; cursor: pointer;
  background: linear-gradient(90deg, #0af, #f0a); color: #fff; font-size: 1.2em;
  letter-spacing: 3px; font-weight: bold; transition: all 0.3s;
}
#start-btn:hover { transform: scale(1.05); box-shadow: 0 0 30px rgba(0,170,255,0.5); }

.controls-info { color: #666; font-size: 0.75em; margin-top: 20px; text-align: center; line-height: 1.8; }
.controls-info span { color: #aaa; background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 3px; font-size: 0.9em; }

/* HUD */
#hud {
  position: fixed; inset: 0; z-index: 100; pointer-events: none; display: none;
}
#hud.active { display: block; }

.hud-speed {
  position: absolute; left: 20px; top: 50%; transform: translateY(-50%);
  writing-mode: vertical-rl; text-orientation: mixed; color: #0f0; font-size: 1.4em;
  font-family: 'Courier New', monospace; letter-spacing: 2px;
}
.hud-alt {
  position: absolute; right: 20px; top: 50%; transform: translateY(-50%);
  writing-mode: vertical-rl; text-orientation: mixed; color: #0f0; font-size: 1.4em;
  font-family: 'Courier New', monospace; letter-spacing: 2px;
}

.hud-top {
  position: absolute; top: 15px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 30px; color: #0f0; font-family: 'Courier New', monospace; font-size: 0.85em;
}
.hud-top-item { text-align: center; }
.hud-top-label { color: #0a0; font-size: 0.7em; letter-spacing: 1px; }
.hud-top-val { font-size: 1.3em; margin-top: 2px; }

.hud-bottom {
  position: absolute; bottom: 15px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 40px; color: #0f0; font-family: 'Courier New', monospace; font-size: 0.8em;
}

.hud-pitch-ladder {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 300px; height: 200px; pointer-events: none;
}
.pitch-line {
  position: absolute; left: 50%; transform: translateX(-50%);
  height: 1px; background: rgba(0,255,0,0.4);
}
.pitch-label {
  position: absolute; color: rgba(0,255,0,0.6); font-size: 0.7em;
  font-family: 'Courier New', monospace; transform: translateX(-50%);
}

.hud-horizon-line {
  position: absolute; left: 10%; right: 10%; top: 50%; height: 2px;
  background: rgba(0,255,0,0.6); transform-origin: center;
}
.hud-center-dot {
  position: absolute; left: 50%; top: 50%; width: 8px; height: 8px;
  border: 2px solid #0f0; border-radius: 50%; transform: translate(-50%, -50%);
}

.hud-compass {
  position: absolute; top: 15px; left: 50%; transform: translateX(-50%);
  width: 200px; height: 30px; overflow: hidden; text-align: center;
  color: #0f0; font-family: 'Courier New', monospace; font-size: 0.8em;
}

.hud-g-meter {
  position: absolute; right: 60px; top: 50%; transform: translateY(-50%);
  width: 30px; height: 120px; border: 1px solid rgba(0,255,0,0.4);
  border-radius: 4px; overflow: hidden; position: relative;
}
.g-indicator {
  position: absolute; left: 0; right: 0; height: 3px; background: #f00;
  transition: bottom 0.1s;
}

.hud-throttle-bar {
  position: absolute; left: 60px; top: 50%; transform: translateY(-50%);
  width: 12px; height: 120px; border: 1px solid rgba(0,255,0,0.4);
  border-radius: 4px; overflow: hidden; position: relative; background: rgba(0,0,0,0.3);
}
.throttle-fill {
  position: absolute; bottom: 0; left: 0; right: 0; background: linear-gradient(to top, #0f0, #ff0, #f00);
  transition: height 0.1s;
}

.hud-warning {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -80px);
  color: #f00; font-size: 1.5em; font-weight: bold; letter-spacing: 4px;
  text-shadow: 0 0 10px rgba(255,0,0,0.8); display: none;
  animation: blink-warn 0.5s infinite alternate;
}
@keyframes blink-warn { from { opacity: 1; } to { opacity: 0.3; } }

.hud-crosshair {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 40px; height: 40px; display: none;
}
.hud-crosshair.active { display: block; }
.ch-line { position: absolute; background: rgba(255,0,0,0.7); }
.ch-top { top: 0; left: 50%; width: 2px; height: 12px; transform: translateX(-50%); }
.ch-bottom { bottom: 0; left: 50%; width: 2px; height: 12px; transform: translateX(-50%); }
.ch-left { left: 0; top: 50%; width: 12px; height: 2px; transform: translateY(-50%); }
.ch-right { right: 0; top: 50%; width: 12px; height: 2px; transform: translateY(-50%); }

.hud-score {
  position: absolute; top: 60px; right: 20px; color: #fa4; font-family: 'Courier New', monospace;
  font-size: 1em; text-align: right; display: none;
}
.hud-score.active { display: block; }

/* Damage overlay */
.damage-overlay {
  position: fixed; inset: 0; z-index: 200; pointer-events: none;
  border: 0px solid rgba(255,0,0,0); transition: border-width 0.3s, border-color 0.3s;
}
.damage-overlay.hit { border-width: 8px; border-color: rgba(255,0,0,0.6); }

/* Pause overlay */
#pause-overlay {
  position: fixed; inset: 0; z-index: 500; background: rgba(0,0,0,0.7);
  display: none; align-items: center; justify-content: center; flex-direction: column;
}
#pause-overlay.active { display: flex; }
#pause-overlay h2 { color: #fff; font-size: 2em; margin-bottom: 20px; letter-spacing: 4px; }
#pause-overlay p { color: #aaa; font-size: 1em; margin: 5px 0; }

/* Minimap */
.hud-minimap {
  position: absolute; bottom: 60px; right: 20px; width: 120px; height: 120px;
  border: 1px solid rgba(0,255,0,0.4); border-radius: 50%; overflow: hidden;
  background: rgba(0,20,0,0.6);
}
.minimap-dot { position: absolute; width: 4px; height: 4px; border-radius: 50%; }

</style>
</head>
<body>

<!-- Loading Screen -->
<div id="loading-screen">
  <h1>APEX AERO</h1>
  <div class="subtitle">FLIGHT SIMULATOR</div>
  <div id="load-bar-outer"><div id="load-bar-inner"></div></div>
</div>

<!-- Menu Screen -->
<div id="menu-screen">
  <div class="menu-title">APEX AERO</div>
  <div class="menu-subtitle">SELECT YOUR AIRCRAFT & MISSION</div>

  <div class="aircraft-select" id="ac-select"></div>
  <div class="scenario-select" id="sc-select"></div>
  <button id="start-btn">LAUNCH</button>
  <div class="controls-info">
    <span>W/S</span> Pitch &nbsp; <span>A/D</span> Roll &nbsp; <span>Q/E</span> Yaw<br>
    <span>SHIFT</span> Throttle Up &nbsp; <span>CTRL</span> Throttle Down &nbsp; <span>TAB</span> Gear<br>
    <span>F</span> Flaps &nbsp; <span>P</span> Pause &nbsp; <span>R</span> Reset Position
  </div>
</div>

<!-- HUD -->
<div id="hud">
  <div class="hud-speed" id="hud-speed">0 KTS</div>
  <div class="hud-alt" id="hud-alt">0 FT</div>
  <div class="hud-top">
    <div class="hud-top-item"><div class="hud-top-label">HEADING</div><div class="hud-top-val" id="hud-hdg">000°</div></div>
    <div class="hud-top-item"><div class="hud-top-label">VERT SPEED</div><div class="hud-top-val" id="hud-vs">0 FPM</div></div>
    <div class="hud-top-item"><div class="hud-top-label">G-FORCE</div><div class="hud-top-val" id="hud-g">1.0 G</div></div>
    <div class="hud-top-item"><div class="hud-top-label">AOA</div><div class="hud-top-val" id="hud-aoa">0°</div></div>
  </div>
  <div class="hud-bottom">
    <div class="hud-top-item"><div class="hud-top-label">THRUST</div><div class="hud-top-val" id="hud-thrust">0%</div></div>
    <div class="hud-top-item"><div class="hud-top-label">MACH</div><div class="hud-top-val" id="hud-mach">0.00</div></div>
  </div>

  <div class="hud-pitch-ladder" id="pitch-ladder"></div>
  <div class="hud-horizon-line" id="horizon-line"></div>
  <div class="hud-center-dot"></div>

  <div class="hud-g-meter"><div class="g-indicator" id="g-ind"></div></div>
  <div class="hud-throttle-bar"><div class="throttle-fill" id="throttle-fill"></div></div>

  <div class="hud-warning" id="hud-stall">STALL</div>
  <div class="hud-crosshair" id="hud-crosshair">
    <div class="ch-line ch-top"></div><div class="ch-line ch-bottom"></div>
    <div class="ch-line ch-left"></div><div class="ch-line ch-right"></div>
  </div>
  <div class="hud-score" id="hud-score">SCORE: 0</div>
  <canvas class="hud-minimap" id="minimap-canvas" width="120" height="120"></canvas>
</div>

<div class="damage-overlay" id="damage-overlay"></div>

<div id="pause-overlay">
  <h2>PAUSED</h2>
  <p>Press P to resume</p>
  <p>Press ESC for menu</p>
</div>

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

(function() {
'use strict';

// ---- Aircraft Database ----
const AIRCRAFT = [
  {
    id: 'phoenix', name: 'Phoenix MK-I', type: 'Fighter Jet', icon: '✈️',
    color: 0x4488ff, accentColor: 0xff4444,
    stats: { maxSpeed: 650, thrust: 120000, weight: 12000, wingArea: 35,
             liftCoeff: 1.8, dragCoeff: 0.035, turnRate: 2.5, agility: 9 },
    statBars: { speed: 90, thrust: 95, maneuverability: 85, weight: 60 }
  },
  {
    id: 'eagle', name: 'Eagle X-7', type: 'Interceptor', icon: '🦅',
    color: 0x44ff88, accentColor: 0xffaa00,
    stats: { maxSpeed: 520, thrust: 90000, weight: 9000, wingArea: 30,
             liftCoeff: 1.6, dragCoeff: 0.028, turnRate: 3.2, agility: 10 },
    statBars: { speed: 70, thrust: 75, maneuverability: 95, weight: 40 }
  },
  {
    id: 'viper', name: 'Viper Heavy', type: 'Bomber', icon: '🔥',
    color: 0xff6644, accentColor: 0x44aaff,
    stats: { maxSpeed: 380, thrust: 150000, weight: 25000, wingArea: 55,
             liftCoeff: 2.2, dragCoeff: 0.05, turnRate: 1.4, agility: 4 },
    statBars: { speed: 45, thrust: 100, maneuverability: 30, weight: 95 }
  }
];

const SCENARIOS = [
  { id: 'takeoff', name: 'Takeoff & Landing', icon: '🛫', desc: 'Master precision flight. Take off from the runway and land safely.' },
  { id: 'dogfight', name: 'Dogfight', icon: '⚔️', desc: 'Engage enemy aircraft in aerial combat. Destroy targets to score points.' }
];

// ---- Global State ----
let selectedAircraft = 0;
let selectedScenario = 0;
let gameRunning = false;
let paused = false;
let scene, camera, renderer, clock;
let terrainMesh, waterMesh;
let aircraftModel, aircraftGroup;
let physicsState = {};
let audioCtx, engineOsc, windGain, masterGain;
let keys = {};
let enemies = [];
let particles = [];
let score = 0;
let terrainSize = 8000;
let terrainRes = 256;

// ---- Loading ----
function updateLoadBar(pct) {
  document.getElementById('load-bar-inner').style.width = pct + '%';
}

async function init() {
  updateLoadBar(10);
  await sleep(200);

  // Build menu UI
  buildMenu();
  updateLoadBar(30);
  await sleep(200);

  // Init Three.js
  initThreeJS();
  updateLoadBar(50);
  await sleep(200);

  // Generate terrain
  generateTerrain();
  updateLoadBar(75);
  await sleep(200);

  // Setup lighting & sky
  setupLighting();
  updateLoadBar(90);
  await sleep(200);

  // Init audio system
  initAudio();
  updateLoadBar(100);
  await sleep(400);

  document.getElementById('loading-screen').classList.add('hidden');
  document.getElementById('menu-screen').classList.add('active');
}

function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

// ---- Menu Building ----
function buildMenu() {
  const acDiv = document.getElementById('ac-select');
  AIRCRAFT.forEach((ac, i) => {
    const card = document.createElement('div');
    card.className = 'ac-card' + (i === selectedAircraft ? ' selected' : '');
    card.innerHTML = `
      <div class="ac-icon">${ac.icon}</div>
      <div class="ac-name">${ac.name}</div>
      <div class="ac-type">${ac.type}</div>
      <div class="ac-stats">
        ${buildStatBar('Speed', ac.statBars.speed, '#0af')}
        ${buildStatBar('Thrust', ac.statBars.thrust, '#f80')}
        ${buildStatBar('Agility', ac.statBars.maneuverability, '#0f0')}
        ${buildStatBar('Weight', ac.statBars.weight, '#a4f')}
      </div>`;
    card.onclick = () => { selectedAircraft = i; updateAcSelection(); };
    acDiv.appendChild(card);
  });

  const scDiv = document.getElementById('sc-select');
  SCENARIOS.forEach((sc, i) => {
    const card = document.createElement('div');
    card.className = 'sc-card' + (i === selectedScenario ? ' selected' : '');
    card.innerHTML = `
      <div class="sc-icon">${sc.icon}</div>
      <div class="sc-name">${sc.name}</div>
      <div class="sc-desc">${sc.desc}</div>`;
    card.onclick = () => { selectedScenario = i; updateScSelection(); };
    scDiv.appendChild(card);
  });

  document.getElementById('start-btn').onclick = startGame;
}

function buildStatBar(label, val, color) {
  return `<div class="ac-stat"><span>${label}</span><div class="ac-stat-bar"><div class="ac-stat-fill" style="width:${val}%;background:${color}"></div></div></div>`;
}

function updateAcSelection() {
  document.querySelectorAll('.ac-card').forEach((c, i) => c.classList.toggle('selected', i === selectedAircraft));
}
function updateScSelection() {
  document.querySelectorAll('.sc-card').forEach((c, i) => c.classList.toggle('selected', i === selectedScenario));
}

// ---- Three.js Init ----
function initThreeJS() {
  scene = new THREE.Scene();
  scene.fog = new THREE.FogExp2(0x87CEEB, 0.00015);

  camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 1, 30000);
  
  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.0;
  document.body.appendChild(renderer.domElement);

  clock = new THREE.Clock();

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

// ---- Lighting & Sky ----
function setupLighting() {
  const ambient = new THREE.AmbientLight(0x446688, 0.5);
  scene.add(ambient);

  const hemi = new THREE.HemisphereLight(0x87CEEB, 0x3a5f0b, 0.6);
  scene.add(hemi);

  const sun = new THREE.DirectionalLight(0xfff4e0, 1.2);
  sun.position.set(5000, 8000, 3000);
  sun.castShadow = true;
  sun.shadow.mapSize.set(2048, 2048);
  sun.shadow.camera.left = -2000;
  sun.shadow.camera.right = 2000;
  sun.shadow.camera.top = 2000;
  sun.shadow.camera.bottom = -2000;
  sun.shadow.camera.far = 20000;
  scene.add(sun);

  // Sky dome with sun glow
  const skyGeo = new THREE.SphereGeometry(15000, 32, 32);
  const skyMat = new THREE.ShaderMaterial({
    uniforms: {
      topColor: { value: new THREE.Color(0x001845) },
      midColor: { value: new THREE.Color(0x4a90d9) },
      bottomColor: { value: new THREE.Color(0xc8e6ff) },
      sunDirection: { value: new THREE.Vector3(0.4, 0.55, 0.3).normalize() },
      sunGlowColor: { value: new THREE.Color(0xffeedd) },
      offset: { value: 20 },
      exponent: { value: 0.6 }
    },
    vertexShader: `
      varying vec3 vWorldPos;
      void main() {
        vec4 wp = modelMatrix * vec4(position, 1.0);
        vWorldPos = wp.xyz;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }`,
    fragmentShader: `
      uniform vec3 topColor;
      uniform vec3 midColor;
      uniform vec3 bottomColor;
      uniform vec3 sunDirection;
      uniform vec3 sunGlowColor;
      uniform float offset;
      uniform float exponent;
      varying vec3 vWorldPos;
      void main() {
        float h = normalize(vWorldPos + offset).y;
        
        // Three-band gradient
        vec3 sky;
        if (h > 0.5) {
          sky = mix(midColor, topColor, pow((h - 0.5) * 2.0, exponent));
        } else {
          sky = mix(bottomColor, midColor, pow(h * 2.0, exponent));
        }
        
        // Sun glow
        vec3 dir = normalize(vWorldPos);
        float sunDot = max(dot(dir, sunDirection), 0.0);
        float sunGlow = pow(sunDot, 64.0) * 1.5;
        float sunHalo = pow(sunDot, 8.0) * 0.3;
        sky += sunGlowColor * (sunGlow + sunHalo);
        
        gl_FragColor = vec4(clamp(sky, 0.0, 1.0), 1.0);
      }`,
    side: THREE.BackSide,
    depthWrite: false
  });
  scene.add(new THREE.Mesh(skyGeo, skyMat));

  // Clouds
  createClouds();
}

function createClouds() {
  const cloudGroup = new THREE.Group();
  for (let i = 0; i < 60; i++) {
    const size = 200 + Math.random() * 400;
    const geo = new THREE.SphereGeometry(size, 8, 6);
    const mat = new THREE.MeshStandardMaterial({
      color: 0xffffff, transparent: true, opacity: 0.35 + Math.random() * 0.2,
      roughness: 1, metalness: 0
    });
    const cloud = new THREE.Mesh(geo, mat);
    cloud.position.set(
      (Math.random() - 0.5) * terrainSize,
      800 + Math.random() * 2000,
      (Math.random() - 0.5) * terrainSize
    );
    cloud.scale.y = 0.3;
    cloudGroup.add(cloud);
  }
  scene.add(cloudGroup);
}

// ---- Procedural Terrain ----
function generateTerrain() {
  const geo = new THREE.PlaneGeometry(terrainSize, terrainSize, terrainRes, terrainRes);
  geo.rotateX(-Math.PI / 2);

  const positions = geo.attributes.position.array;
  const colors = new Float32Array(positions.length);

  for (let i = 0; i < positions.length; i += 3) {
    const x = positions[i];
    const z = positions[i + 2];
    let h = getTerrainHeight(x, z);
    positions[i + 1] = h;

    // Vertex colors based on height
    const t = Math.max(0, Math.min(1, (h + 50) / 3000));
    if (t < 0.15) {
      colors[i] = 0.76; colors[i+1] = 0.7; colors[i+2] = 0.5; // sand
    } else if (t < 0.35) {
      colors[i] = 0.2 + t; colors[i+1] = 0.45 + t * 0.5; colors[i+2] = 0.15; // grass
    } else if (t < 0.6) {
      colors[i] = 0.35; colors[i+1] = 0.35; colors[i+2] = 0.3; // rock
    } else if (t < 0.8) {
      colors[i] = 0.5; colors[i+1] = 0.48; colors[i+2] = 0.46; // snow-rock
    } else {
      colors[i] = 0.9; colors[i+1] = 0.92; colors[i+2] = 0.95; // snow
    }
  }

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

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

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

  // Water plane
  const waterGeo = new THREE.PlaneGeometry(terrainSize * 2, terrainSize * 2);
  waterGeo.rotateX(-Math.PI / 2);
  const waterMat = new THREE.MeshStandardMaterial({
    color: 0x1a6baa, transparent: true, opacity: 0.7, roughness: 0.1, metalness: 0.3
  });
  waterMesh = new THREE.Mesh(waterGeo, waterMat);
  waterMesh.position.y = -5;
  scene.add(waterMesh);

  // Runway for takeoff scenario
  createRunway();
}

function getTerrainHeight(x, z) {
  let h = 0;
  // Multiple octaves of noise-like terrain for realistic landscape
  h += Math.sin(x * 0.0008 + 0.5) * Math.cos(z * 0.0007 + 1.2) * 600;
  h += Math.sin(x * 0.002 + 1.5) * Math.cos(z * 0.0018 + 2.3) * 350;
  h += Math.sin(x * 0.004 + 0.3) * Math.cos(z * 0.0035 + 0.7) * 150;
  h += Math.sin(x * 0.008 + 2.1) * Math.cos(z * 0.006 + 1.9) * 60;
  h += (Math.sin(x * 0.016) * Math.cos(z * 0.014)) * 25;
  h += (Math.sin(x * 0.03 + z * 0.025)) * 8;

  // Mountain ridges with varying intensity
  const ridge = Math.abs(Math.sin(x * 0.0015 + z * 0.001));
  h += ridge * ridge * 1000;
  
  // Secondary ridge system
  const ridge2 = Math.abs(Math.cos(x * 0.001 - z * 0.0018 + 3.7));
  h += ridge2 * ridge2 * 500;

  return Math.max(-10, h);
}

function createRunway() {
  const rwGeo = new THREE.PlaneGeometry(60, 2000);
  rwGeo.rotateX(-Math.PI / 2);
  const rwMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.9 });
  const runway = new THREE.Mesh(rwGeo, rwMat);
  runway.position.set(0, getTerrainHeight(0, 0) + 1, 0);
  scene.add(runway);

  // Runway markings
  for (let i = -900; i < 950; i += 80) {
    const markGeo = new THREE.PlaneGeometry(4, 30);
    markGeo.rotateX(-Math.PI / 2);
    const markMat = new THREE.MeshStandardMaterial({ color: 0xffffff });
    const mark = new THREE.Mesh(markGeo, markMat);
    mark.position.set(0, getTerrainHeight(0, 0) + 1.5, i);
    scene.add(mark);
  }

  // Runway edge lights
  for (let i = -950; i < 960; i += 40) {
    [-28, 28].forEach(xOff => {
      const lightGeo = new THREE.SphereGeometry(1.5);
      const lightMat = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
      const lightMesh = new THREE.Mesh(lightGeo, lightMat);
      lightMesh.position.set(xOff, getTerrainHeight(xOff, i) + 2, i);
      scene.add(lightMesh);
    });
  }
}

// ---- Aircraft Model Builder ----
function buildAircraftModel(acData) {
  const group = new THREE.Group();
  
  // Per-aircraft visual parameters
  let fuselageLen, fuselageRad, wingSpan, tailHeight, noseLen;
  if (acData.id === 'phoenix') {
    fuselageLen = 16; fuselageRad = 1.2; wingSpan = 8; tailHeight = 5; noseLen = 5;
  } else if (acData.id === 'eagle') {
    fuselageLen = 14; fuselageRad = 1.0; wingSpan = 7; tailHeight = 4.5; noseLen = 6;
  } else { // viper - heavy bomber
    fuselageLen = 20; fuselageRad = 1.8; wingSpan = 12; tailHeight = 6; noseLen = 4;
  }

  // Fuselage with tapered shape
  const fuselageGeo = new THREE.CylinderGeometry(fuselageRad * 0.7, fuselageRad, fuselageLen, 8);
  fuselageGeo.rotateZ(Math.PI / 2);
  const fuselageMat = new THREE.MeshStandardMaterial({
    color: acData.color, roughness: 0.3, metalness: 0.7
  });
  group.add(new THREE.Mesh(fuselageGeo, fuselageMat));

  // Nose cone
  const noseGeo = new THREE.ConeGeometry(fuselageRad, noseLen, 8);
  noseGeo.rotateZ(-Math.PI / 2);
  const noseMesh = new THREE.Mesh(noseGeo, fuselageMat);
  noseMesh.position.x = (fuselageLen + noseLen) / 2;
  group.add(noseMesh);

  // Wings - swept design
  const wingShape = new THREE.Shape();
  wingShape.moveTo(0, 0);
  wingShape.lineTo(-4, -wingSpan);
  wingShape.lineTo(-1.5, -(wingSpan + 1));
  wingShape.lineTo(3, 0);
  wingShape.closePath();

  const wingExtrudeSettings = { depth: 0.3, bevelEnabled: true, bevelThickness: 0.1, bevelSize: 0.1 };
  const wingGeo = new THREE.ExtrudeGeometry(wingShape, wingExtrudeSettings);
  const wingMat = new THREE.MeshStandardMaterial({ color: acData.color, roughness: 0.4, metalness: 0.6 });

  const leftWing = new THREE.Mesh(wingGeo, wingMat);
  leftWing.position.set(-2, -fuselageRad * 1.2, 0);
  group.add(leftWing);

  const rightWing = new THREE.Mesh(wingGeo.clone(), wingMat);
  rightWing.position.set(-2, fuselageRad * 1.2, 0);
  rightWing.rotation.x = Math.PI;
  group.add(rightWing);

  // Wing tip details (navigation lights)
  [-wingSpan - 1, wingSpan + 1].forEach(zOff => {
    const tipGeo = new THREE.SphereGeometry(0.3);
    const tipMat = new THREE.MeshBasicMaterial({ color: zOff > 0 ? 0x00ff00 : 0xff0000 });
    const tip = new THREE.Mesh(tipGeo, tipMat);
    tip.position.set(-1.5, fuselageRad * 1.2, zOff);
    group.add(tip);
  });

  // Tail wings (horizontal stabilizer)
  const tailWingShape = new THREE.Shape();
  tailWingShape.moveTo(0, 0);
  tailWingShape.lineTo(-3, -wingSpan * 0.5);
  tailWingShape.lineTo(-1.5, -(wingSpan * 0.5 + 0.5));
  tailWingShape.lineTo(2, 0);
  tailWingShape.closePath();

  const twGeo = new THREE.ExtrudeGeometry(tailWingShape, { depth: 0.2, bevelEnabled: false });

  const lTail = new THREE.Mesh(twGeo, wingMat);
  lTail.position.set(-fuselageLen * 0.5, -fuselageRad * 0.8, 0);
  group.add(lTail);

  const rTail = new THREE.Mesh(twGeo.clone(), wingMat);
  rTail.position.set(-fuselageLen * 0.5, fuselageRad * 0.8, 0);
  rTail.rotation.x = Math.PI;
  group.add(rTail);

  // Vertical stabilizer
  const vStabShape = new THREE.Shape();
  vStabShape.moveTo(0, 0);
  vStabShape.lineTo(-3, tailHeight);
  vStabShape.lineTo(-1.5, tailHeight + 0.5);
  vStabShape.lineTo(2, 0);
  vStabShape.closePath();

  const vsGeo = new THREE.ExtrudeGeometry(vStabShape, { depth: 0.2, bevelEnabled: false });
  const vStab = new THREE.Mesh(vsGeo, wingMat);
  vStab.position.set(-fuselageLen * 0.5, 0, -fuselageRad * 0.8);
  vStab.rotation.x = Math.PI / 2;
  group.add(vStab);

  // Engine exhaust glow (dual for bomber)
  const numEngines = acData.id === 'viper' ? 2 : 1;
  for (let e = 0; e < numEngines; e++) {
    const zOff = numEngines > 1 ? (e === 0 ? -3 : 3) : 0;
    const exhaustGeo = new THREE.CylinderGeometry(fuselageRad * 0.4, fuselageRad * 0.6, 3, 8);
    exhaustGeo.rotateZ(Math.PI / 2);
    const exhaustMat = new THREE.MeshBasicMaterial({ color: acData.accentColor, transparent: true, opacity: 0.5 });
    const exhaust = new THREE.Mesh(exhaustGeo, exhaustMat);
    exhaust.position.set(-(fuselageLen + 1) / 2 - 1, zOff * 0.3, zOff);
    exhaust.name = 'exhaust';
    group.add(exhaust);

    const exLight = new THREE.PointLight(acData.accentColor, 2, 30);
    exLight.position.set(-(fuselageLen + 1) / 2 - 3, zOff * 0.3, zOff);
    exLight.name = 'exhaustLight';
    group.add(exLight);
  }

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

  // Landing gear (retractable)
  if (acData.id === 'viper') {
    // Triple gear for heavy bomber
    [[-5, -fuselageRad * 1.5, -3], [-5, -fuselageRad * 1.5, 3], [6, -fuselageRad * 1.2, 0]].forEach(pos => {
      const strutGeo = new THREE.CylinderGeometry(0.15, 0.15, 2);
      const strutMat = new THREE.MeshStandardMaterial({ color: 0x444444, metalness: 0.8 });
      const strut = new THREE.Mesh(strutGeo, strutMat);
      strut.position.set(pos[0], pos[1] - 1, pos[2]);
      group.add(strut);
      
      const wheelGeo = new THREE.CylinderGeometry(0.4, 0.4, 0.3, 8);
      const wheelMat = new THREE.MeshStandardMaterial({ color: 0x222222 });
      const wheel = new THREE.Mesh(wheelGeo, wheelMat);
      wheel.position.set(pos[0], pos[1] - 2.1, pos[2]);
      group.add(wheel);
    });
  }

  return group;
}

// ---- Physics Engine ----
function initPhysics() {
  const ac = AIRCRAFT[selectedAircraft];
  const s = ac.stats;
  const startY = selectedScenario === 0 ? getTerrainHeight(0, 0) + 5 : 2000;

  physicsState = {
    position: new THREE.Vector3(0, startY, 0),
    velocity: new THREE.Vector3(selectedScenario === 0 ? 0 : 150, 0, 0),
    quaternion: new THREE.Quaternion(),
    angularVelocity: new THREE.Vector3(0, 0, 0),

    // Control inputs (normalized -1 to 1)
    pitchInput: 0, rollInput: 0, yawInput: 0, throttleInput: 0.5,

    // State flags
    gearDown: true, flapsExtended: false, stalled: false, crashed: false,
    altitude: startY, airspeed: 0, machNumber: 0, gForce: 1, aoa: 0,
    verticalSpeed: 0, heading: 0,

    // Engine state
    throttlePercent: 50, engineRPM: 3000, maxRPM: 12000,

    // Aircraft properties (from selection)
    mass: s.weight / 9.81,
    wingArea: s.wingArea,
    liftCoeffBase: s.liftCoeff,
    dragCoeffBase: s.dragCoeff,
    maxThrust: s.thrust,
    turnRateMax: s.turnRate,
    maxSpeedKnots: s.maxSpeed,

    // Inertia tensor (approximate)
    inertiaX: s.weight * 150,
    inertiaY: s.weight * 300,
    inertiaZ: s.weight * 400,

    dt: 0.016
  };
}

function getAirDensity(altitude) {
  // ISA atmosphere model (simplified)
  const h = Math.max(0, altitude);
  if (h < 11000) return 1.225 * Math.pow(1 - 0.0000225577 * h, 4.256);
  return 0.3639; // above troposphere cap
}

function updatePhysics(dt) {
  if (physicsState.crashed || paused) return;

  const ps = physicsState;
  ps.dt = dt;

  // --- Throttle & Engine Response ---
  const throttleTarget = ps.throttleInput;
  ps.throttlePercent += (throttleTarget * 100 - ps.throttlePercent) * Math.min(1, dt * 3);
  ps.engineRPM = 2000 + (ps.throttlePercent / 100) * (ps.maxRPM - 2000);

  // --- Air properties ---
  const airDensity = getAirDensity(ps.altitude);
  const speedKnots = ps.airspeed;
  const speedMs = speedKnots * 0.514444;
  const vSq = speedMs * speedMs;

  // --- Angle of Attack (approximate) ---
  const forwardDir = new THREE.Vector3(1, 0, 0).applyQuaternion(ps.quaternion);
  const velDir = ps.velocity.clone().normalize();
  if (ps.velocity.length() > 1) {
    ps.aoa = Math.acos(Math.max(-1, Math.min(1, forwardDir.dot(velDir)))) * 180 / Math.PI;
  }

  // --- Lift Calculation ---
  let liftCoeff = ps.liftCoeffBase + (ps.flapsExtended ? 0.5 : 0);
  const aoaRad = ps.aoa * Math.PI / 180;
  liftCoeff *= (1 + 2 * Math.sin(aoaRad));

  // Stall detection
  ps.stalled = false;
  if (ps.aoa > 15 && speedKnots < 80) {
    ps.stalled = true;
    document.getElementById('hud-stall').style.display = 'block';
  } else {
    document.getElementById('hud-stall').style.display = 'none';
  }

  const liftForce = 0.5 * airDensity * vSq * ps.wingArea * liftCoeff;

  // --- Drag Calculation ---
  let dragCoeff = ps.dragCoeffBase + (ps.gearDown ? 0.02 : 0) + (ps.flapsExtended ? 0.015 : 0);
  const inducedDrag = (liftCoeff * liftCoeff) / (Math.PI * 6 * 0.4); // AR=6, e=0.8
  dragCoeff += inducedDrag;

  const dragForce = 0.5 * airDensity * vSq * ps.wingArea * dragCoeff;

  // --- Thrust ---
  const thrustForce = (ps.throttlePercent / 100) * ps.maxThrust;

  // --- Force vectors in aircraft local space ---
  const liftDir = new THREE.Vector3(0, 1, 0).applyQuaternion(ps.quaternion);
  const dragDir = ps.velocity.clone().normalize();
  if (ps.velocity.length() < 0.1) dragDir.set(-forwardDir.x, -forwardDir.y, -forwardDir.z);

  // Gravity
  const gravityForce = new THREE.Vector3(0, -9.81 * ps.mass, 0);

  // Total force
  let totalForce = gravityForce.clone();
  totalForce.addScaledVector(liftDir, liftForce);
  if (ps.velocity.length() > 0.5) {
    totalForce.addScaledVector(dragDir, -dragForce);
  }
  totalForce.addScaledVector(forwardDir, thrustForce);

  // --- Acceleration & velocity update ---
  const accel = totalForce.clone().divideScalar(ps.mass);
  ps.velocity.addScaledVector(accel, dt);

  // Speed limiting (compressibility)
  if (speedKnots > ps.maxSpeedKnots * 1.2) {
    ps.velocity.multiplyScalar(0.995);
  }

  // --- Position update ---
  ps.position.addScaledVector(ps.velocity, dt);

  // --- Angular dynamics (attitude control) ---
  const pitchRate = ps.pitchInput * ps.turnRateMax;
  const rollRate = ps.rollInput * ps.turnRateMax * 1.2;
  const yawRate = ps.yawInput * ps.turnRateMax * 0.5;

  // Apply angular damping
  ps.angularVelocity.x += (pitchRate - ps.angularVelocity.x) * Math.min(1, dt * 4);
  ps.angularVelocity.y += (yawRate - ps.angularVelocity.y) * Math.min(1, dt * 3);
  ps.angularVelocity.z += (rollRate - ps.angularVelocity.z) * Math.min(1, dt * 5);

  // Damping
  ps.angularVelocity.multiplyScalar(Math.pow(0.98, dt * 60));

  // Apply rotation
  const rotQuat = new THREE.Quaternion();
  const pitchQ = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), -ps.angularVelocity.x * dt);
  const rollQ = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), ps.angularVelocity.z * dt);
  const yawQ = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), -ps.angularVelocity.y * dt);

  rotQuat.multiply(pitchQ).multiply(rollQ).multiply(yawQ);
  ps.quaternion.multiply(rotQuat);
  ps.quaternion.normalize();

  // Auto-leveling (subtle)
  const upDir = new THREE.Vector3(0, 1, 0).applyQuaternion(ps.quaternion);
  if (!ps.rollInput && !ps.pitchInput) {
    const levelQ = new THREE.Quaternion().setFromUnitVectors(upDir, new THREE.Vector3(0, 1, 0));
    ps.quaternion.slerp(levelQ, dt * 0.5);
    ps.quaternion.normalize();
  }

  // --- Update derived values ---
  ps.altitude = ps.position.y;
  ps.airspeed = ps.velocity.length() / 0.514444;
  ps.machNumber = speedMs / 340.3;
  ps.verticalSpeed = ps.velocity.y * 196.85; // ft/min

  // Heading (degrees)
  const fwd2D = new THREE.Vector3(forwardDir.x, 0, forwardDir.z).normalize();
  ps.heading = ((Math.atan2(fwd2D.x, fwd2D.z) * 180 / Math.PI) + 360) % 360;

  // G-force (approximate from vertical acceleration)
  const vertAccel = accel.y + 9.81;
  ps.gForce = Math.max(0, 1 + vertAccel / 9.81);

  // --- Terrain collision ---
  const terrainH = getTerrainHeight(ps.position.x, ps.position.z);
  if (ps.altitude < terrainH + 3) {
    if (ps.stalled || ps.verticalSpeed < -20 || speedKnots > 150) {
      crashAircraft();
    } else if (speedKnots < 140 && Math.abs(ps.verticalSpeed) < 600) {
      // Safe landing on ground
      ps.altitude = terrainH + 3;
      ps.position.y = terrainH + 3;
      ps.velocity.y = Math.max(0, ps.velocity.y);
    } else {
      crashAircraft();
    }
  }

  // Water collision
  if (ps.altitude < -5) {
    crashAircraft();
  }

  // --- Update model transform ---
  if (aircraftModel) {
    aircraftModel.position.copy(ps.position);
    aircraftModel.quaternion.copy(ps.quaternion);

    // Exhaust glow based on throttle
    const exhaust = aircraftModel.getObjectByName('exhaust');
    const exLight = aircraftModel.getObjectByName('exhaustLight');
    if (exhaust) {
      exhaust.material.opacity = 0.2 + (ps.throttlePercent / 100) * 0.6;
      exhaust.scale.x = 1 + (ps.throttlePercent / 100) * 2;
    }
    if (exLight) exLight.intensity = ps.throttlePercent / 50;

    // Smoke trail particles at high throttle or during turns
    const turnGForce = Math.abs(ps.gForce - 1);
    if ((ps.throttlePercent > 70 || turnGForce > 1.5) && selectedScenario === 1) {
      const backDir = new THREE.Vector3(-1, 0, 0).applyQuaternion(ps.quaternion);
      const smokePos = ps.position.clone().addScaledVector(backDir, 8);
      
      const smokeGeo = new THREE.SphereGeometry(1.5 + Math.random());
      const smokeMat = new THREE.MeshBasicMaterial({ 
        color: turnGForce > 2 ? 0xff6644 : 0xaaaaaa, 
        transparent: true, opacity: 0.4 
      });
      const smokeMesh = new THREE.Mesh(smokeGeo, smokeMat);
      smokeMesh.position.copy(smokePos);
      scene.add(smokeMesh);

      particles.push({
        mesh: smokeMesh, velocity: backDir.clone().multiplyScalar(-20), life: 3 + Math.random(), isBullet: false
      });
    }
  }

  // --- Dogfight enemy AI ---
  updateEnemies(dt);

  // --- Update audio ---
  updateAudio();
}

function crashAircraft() {
  physicsState.crashed = true;
  const overlay = document.getElementById('damage-overlay');
  overlay.classList.add('hit');
  
  // Explosion particles
  createExplosion(physicsState.position.clone());
  
  setTimeout(() => {
    if (gameRunning) resetPosition();
  }, 2000);
}

function resetPosition() {
  const ac = AIRCRAFT[selectedAircraft];
  const startY = selectedScenario === 0 ? getTerrainHeight(0, 0) + 5 : 2000;
  physicsState.position.set(0, startY, 0);
  physicsState.velocity.set(selectedScenario === 0 ? 0 : 150, 0, 0);
  physicsState.quaternion.identity();
  physicsState.angularVelocity.set(0, 0, 0);
  physicsState.crashed = false;
  physicsState.stalled = false;
  document.getElementById('hud-stall').style.display = 'none';
  document.getElementById('damage-overlay').classList.remove('hit');
}

// ---- Enemy Aircraft (Dogfight) ----
function spawnEnemies() {
  enemies.forEach(e => scene.remove(e.mesh));
  enemies = [];

  for (let i = 0; i < 5; i++) {
    const angle = (i / 5) * Math.PI * 2;
    const dist = 1500 + Math.random() * 1000;
    const enemyData = { ...AIRCRAFT[(selectedAircraft + 1 + i) % 3] };
    
    const mesh = buildAircraftModel(enemyData);
    mesh.position.set(
      physicsState.position.x + Math.cos(angle) * dist,
      1500 + Math.random() * 1000,
      physicsState.position.z + Math.sin(angle) * dist
    );
    
    scene.add(mesh);

    enemies.push({
      mesh: mesh,
      velocity: new THREE.Vector3(80 + Math.random() * 40, 0, (Math.random() - 0.5) * 60),
      health: 100,
      alive: true,
      turnTimer: 0,
      targetHeading: Math.random() * Math.PI * 2,
      shootCooldown: 0
    });
  }
}

function updateEnemies(dt) {
  if (selectedScenario !== 1) return;

  enemies.forEach(enemy => {
    if (!enemy.alive) return;

    // Simple AI: circle around player area with altitude changes
    enemy.turnTimer -= dt;
    if (enemy.turnTimer <= 0) {
      const toPlayer = physicsState.position.clone().sub(enemy.mesh.position);
      enemy.targetHeading = Math.atan2(toPlayer.x, toPlayer.z);
      enemy.turnTimer = 3 + Math.random() * 5;
    }

    // Steer toward target heading
    const fwd = new THREE.Vector3(1, 0, 0).applyQuaternion(enemy.mesh.quaternion);
    const desiredDir = new THREE.Vector3(Math.sin(enemy.targetHeading), 0, Math.cos(enemy.targetHeading));
    
    const cross = new THREE.Vector3().crossVectors(fwd, desiredDir);
    enemy.mesh.quaternion.multiply(
      new THREE.Quaternion().setFromAxisAngle(cross.normalize(), dt * 1.5)
    );

    // Altitude control
    if (enemy.mesh.position.y < 500) {
      const upQ = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), dt * 2);
      enemy.mesh.quaternion.multiply(upQ);
    } else if (enemy.mesh.position.y > 3000) {
      const downQ = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), -dt * 2);
      enemy.mesh.quaternion.multiply(downQ);
    }

    // Move forward
    const speed = 120;
    const moveDir = new THREE.Vector3(1, 0, 0).applyQuaternion(enemy.mesh.quaternion);
    enemy.mesh.position.addScaledVector(moveDir, speed * dt);

    // Shooting at player (occasional)
    enemy.shootCooldown -= dt;
    if (enemy.shootCooldown <= 0) {
      const distToPlayer = enemy.mesh.position.distanceTo(physicsState.position);
      if (distToPlayer < 800) {
        createBullet(enemy.mesh.position.clone(), moveDir, true);
        enemy.shootCooldown = 1.5 + Math.random();
      }
    }

    // Proximity damage to player
    const dist = enemy.mesh.position.distanceTo(physicsState.position);
    if (dist < 20) {
      score = Math.max(0, score - 50);
      document.getElementById('damage-overlay').classList.add('hit');
      setTimeout(() => document.getElementById('damage-overlay').classList.remove('hit'), 300);
    }
  });

  // Respawn dead enemies
  const aliveCount = enemies.filter(e => e.alive).length;
  if (aliveCount < 2) {
    const angle = Math.random() * Math.PI * 2;
    const dist = 2000 + Math.random() * 1000;
    const enemyData = { ...AIRCRAFT[Math.floor(Math.random() * 3)] };
    const mesh = buildAircraftModel(enemyData);
    mesh.position.set(
      physicsState.position.x + Math.cos(angle) * dist,
      2000 + Math.random() * 1000,
      physicsState.position.z + Math.sin(angle) * dist
    );
    scene.add(mesh);
    enemies.push({
      mesh, velocity: new THREE.Vector3(100, 0, 50), health: 100, alive: true,
      turnTimer: 0, targetHeading: Math.random() * Math.PI * 2, shootCooldown: 2
    });
  }
}

// ---- Bullets & Particles ----
function createBullet(pos, dir, isEnemy) {
  const geo = new THREE.SphereGeometry(1);
  const mat = new THREE.MeshBasicMaterial({ color: isEnemy ? 0xff4444 : 0x44ff44 });
  const mesh = new THREE.Mesh(geo, mat);
  mesh.position.copy(pos);
  scene.add(mesh);

  particles.push({
    mesh, velocity: dir.clone().multiplyScalar(500), life: 3, isBullet: true, isEnemy
  });
}

function createExplosion(pos) {
  for (let i = 0; i < 30; i++) {
    const geo = new THREE.SphereGeometry(2 + Math.random() * 4);
    const mat = new THREE.MeshBasicMaterial({
      color: new THREE.Color().setHSL(0.05 + Math.random() * 0.1, 1, 0.5),
      transparent: true, opacity: 1
    });
    const mesh = new THREE.Mesh(geo, mat);
    mesh.position.copy(pos);
    scene.add(mesh);

    particles.push({
      mesh, velocity: new THREE.Vector3(
        (Math.random() - 0.5) * 100,
        Math.random() * 80,
        (Math.random() - 0.5) * 100
      ), life: 2 + Math.random(), isBullet: false
    });
  }
}

function updateParticles(dt) {
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.life -= dt;
    
    if (p.isBullet) {
      p.mesh.position.addScaledVector(p.velocity, dt);

      // Check bullet hits
      if (!p.isEnemy) {
        enemies.forEach(enemy => {
          if (!enemy.alive) return;
          const dist = p.mesh.position.distanceTo(enemy.mesh.position);
          if (dist < 20) {
            enemy.health -= 34;
            if (enemy.health <= 0) {
              enemy.alive = false;
              createExplosion(enemy.mesh.position.clone());
              scene.remove(enemy.mesh);
              score += 100;
            } else {
              // Hit flash
              enemy.mesh.children.forEach(c => {
                if (c.material && !c.name) {
                  const origColor = c.material.color.getHex();
                  c.material.color.set(0xff0000);
                  setTimeout(() => c.material.color.set(origColor), 100);
                }
              });
            }
            p.life = 0;
          }
        });

        // Check if enemy bullet hits player
      } else {
        const dist = p.mesh.position.distanceTo(physicsState.position);
        if (dist < 15) {
          score = Math.max(0, score - 25);
          document.getElementById('damage-overlay').classList.add('hit');
          setTimeout(() => document.getElementById('damage-overlay').classList.remove('hit'), 200);
          p.life = 0;
        }
      }
    } else {
      p.mesh.position.addScaledVector(p.velocity, dt);
      p.velocity.y -= 10 * dt; // gravity on debris
      p.mesh.material.opacity = Math.max(0, p.life / 2);
      p.mesh.scale.multiplyScalar(0.97);
    }

    if (p.life <= 0) {
      scene.remove(p.mesh);
      particles.splice(i, 1);
    }
  }
}

// ---- Audio System ----
function initAudio() {
  try {
    audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    
    // Master gain
    masterGain = audioCtx.createGain();
    masterGain.gain.value = 0.3;
    masterGain.connect(audioCtx.destination);

    // Engine sound - layered oscillators for realism
    engineOsc = [];
    const oscTypes = ['sawtooth', 'square'];
    oscTypes.forEach(type => {
      const osc = audioCtx.createOscillator();
      osc.type = type;
      osc.frequency.value = 80;
      
      const gain = audioCtx.createGain();
      gain.gain.value = 0.15;
      
      // Low-pass filter for muffled engine sound
      const filter = audioCtx.createBiquadFilter();
      filter.type = 'lowpass';
      filter.frequency.value = 400;
      filter.Q.value = 2;

      osc.connect(filter);
      filter.connect(gain);
      gain.connect(masterGain);
      osc.start();
      
      engineOsc.push({ osc, gain, filter });
    });

    // Wind noise (filtered white noise)
    const bufferSize = audioCtx.sampleRate * 2;
    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;
    }

    const windSource = audioCtx.createBufferSource();
    windSource.buffer = windBuffer;
    windSource.loop = true;

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

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

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

    // Stall warning oscillator
    const stallOsc = audioCtx.createOscillator();
    stallOsc.type = 'square';
    stallOsc.frequency.value = 800;
    const stallGain = audioCtx.createGain();
    stallGain.gain.value = 0;
    stallOsc.connect(stallGain);
    stallGain.connect(masterGain);
    stallOsc.start();

    physicsState.stallOsc = stallOsc;
    physicsState.stallGain = stallGain;
  } catch(e) {
    console.warn('Audio init failed:', e);
  }
}

function updateAudio() {
  if (!audioCtx || !engineOsc.length) return;

  const ps = physicsState;
  const rpmFactor = ps.engineRPM / ps.maxRPM;
  
  // Engine pitch based on RPM
  engineOsc.forEach((eo, i) => {
    eo.osc.frequency.value = 60 + rpmFactor * 200 + (i === 1 ? 30 : 0);
    eo.gain.gain.value = 0.05 + rpmFactor * 0.2;
    eo.filter.frequency.value = 200 + rpmFactor * 800;
  });

  // Wind noise based on airspeed
  if (windGain) {
    const windVol = Math.min(1, ps.airspeed / 400);
    windGain.gain.value += (windVol * 0.3 - windGain.gain.value) * 0.1;
  }

  // Stall warning
  if (ps.stallGain) {
    ps.stallGain.gain.value = ps.stalled ? 0.2 : 0;
  }
}

// ---- Camera System ----
function updateCamera(dt) {
  const ps = physicsState;
  
  // Third-person chase camera with smooth follow
  const forwardDir = new THREE.Vector3(1, 0, 0).applyQuaternion(ps.quaternion);
  const upDir = new THREE.Vector3(0, 1, 0).applyQuaternion(ps.quaternion);
  
  const camDist = 40;
  const camHeight = 8;
  
  const desiredPos = ps.position.clone()
    .addScaledVector(forwardDir, -camDist)
    .addScaledVector(new THREE.Vector3(0, 1, 0), camHeight);

  // Smooth camera follow
  camera.position.lerp(desiredPos, Math.min(1, dt * 4));
  
  const lookTarget = ps.position.clone().addScaledVector(forwardDir, 20);
  camera.lookAt(lookTarget);
}

// ---- HUD Update ----
function updateHUD() {
  const ps = physicsState;
  
  document.getElementById('hud-speed').textContent = Math.round(ps.airspeed) + ' KTS';
  document.getElementById('hud-alt').textContent = Math.round(ps.altitude * 3.281) + ' FT';
  document.getElementById('hud-hdg').textContent = String(Math.round(ps.heading)).padStart(3, '0') + '°';
  document.getElementById('hud-vs').textContent = (ps.verticalSpeed > 0 ? '+' : '') + Math.round(ps.verticalSpeed) + ' FPM';
  document.getElementById('hud-g').textContent = ps.gForce.toFixed(1) + ' G';
  document.getElementById('hud-aoa').textContent = ps.aoa.toFixed(1) + '°';
  document.getElementById('hud-thrust').textContent = Math.round(ps.throttlePercent) + '%';
  document.getElementById('hud-mach').textContent = ps.machNumber.toFixed(2);

  // G-meter indicator
  const gInd = document.getElementById('g-ind');
  const gPct = Math.max(0, Math.min(1, (ps.gForce - 1) / 4));
  gInd.style.bottom = (50 + gPct * 50) + '%';

  // Throttle bar
  document.getElementById('throttle-fill').style.height = ps.throttlePercent + '%';

  // Horizon line rotation
  const horizonLine = document.getElementById('horizon-line');
  const rightDir = new THREE.Vector3(0, 0, 1).applyQuaternion(ps.quaternion);
  const rollAngle = Math.atan2(rightDir.x, rightDir.z) * 180 / Math.PI;
  horizonLine.style.transform = `rotate(${-rollAngle}deg)`;

  // Score display (dogfight only)
  if (selectedScenario === 1) {
    document.getElementById('hud-score').textContent = 'SCORE: ' + score;
  }

  // Minimap
  drawMinimap();
}

function drawMinimap() {
  const canvas = document.getElementById('minimap-canvas');
  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, 120, 120);

  // Background
  ctx.fillStyle = 'rgba(0,30,0,0.8)';
  ctx.fillRect(0, 0, 120, 120);

  const cx = 60, cy = 60;
  const scale = 0.04;

  // Draw terrain contour hints
  ctx.strokeStyle = 'rgba(0,255,0,0.15)';
  for (let r = 15; r < 60; r += 15) {
    ctx.beginPath();
    ctx.arc(cx, cy, r, 0, Math.PI * 2);
    ctx.stroke();
  }

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

  // Direction indicator
  const fwdAngle = (physicsState.heading - 90) * Math.PI / 180;
  ctx.strokeStyle = '#0f0';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(cx, cy);
  ctx.lineTo(cx + Math.sin(fwdAngle) * 10, cy - Math.cos(fwdAngle) * 10);
  ctx.stroke();

  // Enemy dots (dogfight)
  if (selectedScenario === 1) {
    enemies.forEach(e => {
      if (!e.alive) return;
      const dx = (e.mesh.position.x - physicsState.position.x) * scale;
      const dz = (e.mesh.position.z - physicsState.position.z) * scale;
      if (Math.abs(dx) < 55 && Math.abs(dz) < 55) {
        ctx.fillStyle = '#f00';
        ctx.beginPath();
        ctx.arc(cx + dx, cy + dz, 2.5, 0, Math.PI * 2);
        ctx.fill();
      }
    });
  }

  // Border circle
  ctx.strokeStyle = 'rgba(0,255,0,0.4)';
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.arc(cx, cy, 58, 0, Math.PI * 2);
  ctx.stroke();
}

// ---- Input Handling ----
function setupInput() {
  document.addEventListener('keydown', e => {
    keys[e.code] = true;
    
    if (e.code === 'KeyP' && gameRunning) {
      paused = !paused;
      document.getElementById('pause-overlay').classList.toggle('active', paused);
    }
    if (e.code === 'Escape' && gameRunning && paused) {
      endGame();
    }
    if (e.code === 'KeyR' && gameRunning) resetPosition();
    if (e.code === 'Tab') { e.preventDefault(); physicsState.gearDown = !physicsState.gearDown; }
    if (e.code === 'KeyF') physicsState.flapsExtended = !physicsState.flapsExtended;
  });

  document.addEventListener('keyup', e => { keys[e.code] = false; });

  // Mouse for camera look offset (optional)
  let mouseX = window.innerWidth / 2, mouseY = window.innerHeight / 2;
  document.addEventListener('mousemove', e => {
    if (!gameRunning || paused) return;
    mouseX = e.clientX;
    mouseY = e.clientY;
  });

  // Click to shoot (dogfight)
  document.addEventListener('mousedown', e => {
    if (!gameRunning || paused || selectedScenario !== 1) return;
    const fwd = new THREE.Vector3(1, 0, 0).applyQuaternion(physicsState.quaternion);
    createBullet(physicsState.position.clone().addScaledVector(fwd, 5), fwd, false);
  });

  // Resume audio context on interaction
  document.addEventListener('click', () => {
    if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
  }, { once: true });
}

function processInput() {
  const ps = physicsState;
  
  // Pitch
  let pitchTarget = 0;
  if (keys['KeyW']) pitchTarget = -1;
  if (keys['KeyS']) pitchTarget = 1;
  ps.pitchInput += (pitchTarget - ps.pitchInput) * Math.min(1, 0.2);

  // Roll
  let rollTarget = 0;
  if (keys['KeyA']) rollTarget = 1;
  if (keys['KeyD']) rollTarget = -1;
  ps.rollInput += (rollTarget - ps.rollInput) * Math.min(1, 0.2);

  // Yaw
  let yawTarget = 0;
  if (keys['KeyQ']) yawTarget = 1;
  if (keys['KeyE']) yawTarget = -1;
  ps.yawInput += (yawTarget - ps.yawInput) * Math.min(1, 0.2);

  // Throttle
  let throttleDelta = 0;
  if (keys['ShiftLeft'] || keys['ShiftRight']) throttleDelta = 0.8;
  if (keys['ControlLeft'] || keys['ControlRight']) throttleDelta = -0.8;
  ps.throttleInput = Math.max(0, Math.min(1, ps.throttleInput + throttleDelta * 0.02));

  // Decay inputs when released
  if (!keys['KeyW'] && !keys['KeyS']) ps.pitchInput *= 0.95;
  if (!keys['KeyA'] && !keys['KeyD']) ps.rollInput *= 0.95;
  if (!keys['KeyQ'] && !keys['KeyE']) ps.yawInput *= 0.95;
}

// ---- Game Loop ----
function gameLoop() {
  requestAnimationFrame(gameLoop);
  
  if (!gameRunning) return;
  
  const dt = Math.min(clock.getDelta(), 0.033); // Cap at ~30fps worth of physics
  
  if (!paused) {
    processInput();
    updatePhysics(dt);
    updateCamera(dt);
    updateParticles(dt);
    updateHUD();
  }

  renderer.render(scene, camera);
}

// ---- Start / End Game ----
function startGame() {
  document.getElementById('menu-screen').classList.remove('active');
  document.getElementById('hud').classList.add('active');
  
  gameRunning = true;
  paused = false;
  score = 0;
  
  initPhysics();

  // Build aircraft model
  if (aircraftModel) scene.remove(aircraftModel);
  const acData = AIRCRAFT[selectedAircraft];
  aircraftModel = buildAircraftModel(acData);
  scene.add(aircraftModel);

  // Spawn enemies for dogfight
  if (selectedScenario === 1) {
    spawnEnemies();
    document.getElementById('hud-crosshair').classList.add('active');
    document.getElementById('hud-score').classList.add('active');
  } else {
    document.getElementById('hud-crosshair').classList.remove('active');
    document.getElementById('hud-score').classList.remove('active');
  }

  // Build pitch ladder
  buildPitchLadder();

  clock.start();
}

function endGame() {
  gameRunning = false;
  document.getElementById('hud').classList.remove('active');
  document.getElementById('menu-screen').classList.add('active');
  
  if (aircraftModel) scene.remove(aircraftModel);
  enemies.forEach(e => scene.remove(e.mesh));
  enemies = [];
  particles.forEach(p => scene.remove(p.mesh));
  particles = [];

  // Reset audio
  if (engineOsc.length) {
    engineOsc.forEach(eo => { eo.osc.frequency.value = 80; eo.gain.gain.value = 0.15; });
  }
}

function buildPitchLadder() {
  const ladder = document.getElementById('pitch-ladder');
  ladder.innerHTML = '';
  
  for (let deg = -20; deg <= 20; deg += 5) {
    if (deg === 0) continue;
    const line = document.createElement('div');
    line.className = 'pitch-line';
    const yPos = 100 + deg * 3;
    line.style.top = yPos + '%';
    line.style.width = (Math.abs(deg) % 10 === 0 ? '80px' : '40px');
    ladder.appendChild(line);

    if (deg % 10 === 0) {
      const label = document.createElement('div');
      label.className = 'pitch-label';
      label.style.top = (yPos - 2) + '%';
      label.textContent = deg > 0 ? '+' + deg : deg;
      
      // Left label
      const l1 = label.cloneNode(true);
      l1.style.left = '35%';
      ladder.appendChild(l1);
      
      // Right label
      const l2 = label.cloneNode(true);
      l2.style.right = '35%';
      l2.style.left = 'auto';
      ladder.appendChild(l2);
    }
  }
}

// ---- Boot ----
window.addEventListener('DOMContentLoaded', () => {
  setupInput();
  init().then(() => {
    gameLoop();
  });
});

})();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingcap-qwen3.6-27b","provider":"lmstudio","persona":"full","sessionId":"73d18c4c-8c65-4073-bd9c-eb7f861df27c","tokensIn":824647,"tokensOut":25965,"tokensTotal":850612,"turns":21,"toolCalls":20,"failedToolCalls":0,"timestamp":"2026-07-18T20:25:29.247Z"} -->