← Flight Simulator results

Flight Simulator

Qwen 3.6 27B MTP GGUF · typescript

◈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 81,620 bytes · SHA-256 a34f01763ba6
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Apex Aero — 3D Flight Simulator</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/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: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif; }
#app { position: relative; width: 100%; height: 100%; }
#game-canvas { display: block; width: 100%; height: 100%; }

/* ─── Menu Overlay ─── */
#menu-overlay {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  background: radial-gradient(ellipse at center, rgba(10,20,40,0.95) 0%, rgba(0,5,15,0.98) 100%);
  display: flex; align-items: center; justify-content: center;
  z-index: 100;
  transition: opacity 0.5s;
}
#menu-overlay.hidden { opacity: 0; pointer-events: none; }
#menu-panel {
  max-width: 900px; width: 90%; padding: 40px;
  background: rgba(10,25,50,0.85);
  border: 1px solid rgba(60,120,200,0.3);
  border-radius: 12px;
  backdrop-filter: blur(10px);
  text-align: center;
}
#menu-panel h1 { font-size: 3em; color: #4fc3f7; text-shadow: 0 0 30px rgba(79,195,247,0.5); margin-bottom: 8px; letter-spacing: 4px; }
.subtitle { color: #78909c; font-size: 1.1em; margin-bottom: 30px; letter-spacing: 2px; }
#menu-panel h2 { color: #b0bec5; font-size: 1.2em; margin: 20px 0 15px; text-transform: uppercase; letter-spacing: 2px; }

.aircraft-cards, .scenario-cards {
  display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-bottom: 10px;
}
.ac-card, .sc-card {
  background: rgba(20,40,70,0.7);
  border: 2px solid rgba(60,120,200,0.2);
  border-radius: 8px; padding: 16px; width: 260px;
  cursor: pointer; transition: all 0.3s;
}
.ac-card:hover, .sc-card:hover { border-color: rgba(79,195,247,0.6); background: rgba(30,60,100,0.7); }
.ac-card.selected, .sc-card.selected { border-color: #4fc3f7; background: rgba(40,80,130,0.8); box-shadow: 0 0 20px rgba(79,195,247,0.3); }
.ac-card h3, .sc-card h3 { color: #4fc3f7; font-size: 1.1em; margin-bottom: 6px; }
.ac-card p, .sc-card p { color: #90a4ae; font-size: 0.85em; line-height: 1.4; }
.ac-stats { display: flex; justify-content: space-between; margin-top: 10px; font-size: 0.75em; color: #607d8b; }
.ac-stats span { background: rgba(0,0,0,0.3); padding: 2px 8px; border-radius: 4px; }

.btn-primary {
  background: linear-gradient(135deg, #1565c0, #0d47a1);
  color: white; border: none; padding: 14px 40px; font-size: 1.1em;
  border-radius: 6px; cursor: pointer; margin-top: 25px;
  letter-spacing: 2px; text-transform: uppercase; font-weight: bold;
  transition: all 0.3s;
}
.btn-primary:hover { background: linear-gradient(135deg, #1976d2, #1565c0); transform: scale(1.05); }

/* ─── HUD ─── */
#hud {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 10; display: none;
}
#hud.active { display: block; }
#hud-top {
  position: absolute; top: 15px; left: 50%; transform: translateX(-50%);
  text-align: center; color: #4fc3f7; font-size: 0.8em; letter-spacing: 1px;
}
#hud-aircraft-name { font-size: 1.2em; font-weight: bold; }
#hud-scenario-name { color: #78909c; font-size: 0.85em; }

.hud-left, #hud-right {
  position: absolute; top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; gap: 4px;
}
#hud-left { left: 20px; }
#hud-right { right: 20px; text-align: right; }
.hud-item {
  background: rgba(0,10,30,0.5); border: 1px solid rgba(79,195,247,0.15);
  border-radius: 4px; padding: 6px 12px; min-width: 120px;
  display: flex; align-items: center; gap: 8px;
}
.hud-label { color: #607d8b; font-size: 0.7em; letter-spacing: 1px; min-width: 30px; }
.hud-value { color: #4fc3f7; font-size: 1em; font-weight: bold; font-family: 'Courier New', monospace; min-width: 50px; }
.hud-unit { color: #546e7a; font-size: 0.7em; }

#hud-bottom {
  position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%);
  text-align: center;
}
#hud-controls-hint {
  color: rgba(120,144,156,0.6); font-size: 0.7em; margin-top: 5px; letter-spacing: 1px;
}
#hud-status { color: #4fc3f7; font-size: 0.85em; min-height: 1.2em; }

#crosshair {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
  color: rgba(79,195,247,0.5); font-size: 24px; font-weight: bold;
  pointer-events: none; z-index: 11;
}
#message-overlay {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none; z-index: 20;
}
.msg-box {
  background: rgba(0,10,30,0.9); border: 2px solid #4fc3f7; border-radius: 12px;
  padding: 30px 50px; text-align: center; color: #4fc3f7; font-size: 1.5em;
  animation: fadeIn 0.3s;
}
@keyframes fadeIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }

/* Pitch Ladder */
#hud-pitch-ladder {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 200px; height: 150px; pointer-events: none;
}
</style>
</head>
<body>
<div id="app">
  <canvas id="game-canvas"></canvas>
  <div id="menu-overlay">
    <div id="menu-panel">
      <h1>🚀 APEX AERO</h1>
      <p class="subtitle">Flight Simulator</p>
      <div id="aircraft-select">
        <h2>Select Aircraft</h2>
        <div class="aircraft-cards" id="aircraft-cards"></div>
      </div>
      <div id="scenario-select">
        <h2>Select Scenario</h2>
        <div class="scenario-cards" id="scenario-cards"></div>
      </div>
      <button id="start-btn" class="btn-primary">LAUNCH SIMULATION</button>
    </div>
  </div>
  <div id="hud">
    <div id="hud-top">
      <div id="hud-aircraft-name"></div>
      <div id="hud-scenario-name"></div>
    </div>
    <div id="hud-left">
      <div class="hud-item"><span class="hud-label">ALT</span><span class="hud-value" id="hud-alt"></span><span class="hud-unit">ft</span></div>
      <div class="hud-item"><span class="hud-label">SPD</span><span class="hud-value" id="hud-spd"></span><span class="hud-unit">kts</span></div>
      <div class="hud-item"><span class="hud-label">VS</span><span class="hud-value" id="hud-vs"></span><span class="hud-unit">ft/s</span></div>
      <div class="hud-item"><span class="hud-label">G</span><span class="hud-value" id="hud-g"></span><span class="hud-unit"></span></div>
    </div>
    <div id="hud-right">
      <div class="hud-item"><span class="hud-label">THR</span><span class="hud-value" id="hud-throttle"></span><span class="hud-unit">%</span></div>
      <div class="hud-item"><span class="hud-label">HDG</span><span class="hud-value" id="hud-hdg"></span><span class="hud-unit">°</span></div>
      <div class="hud-item"><span class="hud-label">PITCH</span><span class="hud-value" id="hud-pitch"></span><span class="hud-unit">°</span></div>
      <div class="hud-item"><span class="hud-label">ROLL</span><span class="hud-value" id="hud-roll"></span><span class="hud-unit">°</span></div>
    </div>
    <div id="hud-bottom">
      <div id="hud-pitch-ladder"></div>
      <div id="hud-status"></div>
      <div id="hud-controls-hint">W/S: Pitch | A/D: Roll | Q/E: Yaw | Shift/Ctrl: Throttle | R: Gear</div>
    </div>
  </div>
  <div id="message-overlay"></div>
  <div id="crosshair">+</div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./types":"src/types.ts","./aircraft":"src/aircraft.ts","./physics":"src/physics.ts","./modeler":"src/modeler.ts","./terrain":"src/terrain.ts","./input":"src/input.ts","./audio":"src/audio.ts","./hud":"src/hud.ts","./scenarios":"src/scenarios.ts","./pitchladder":"src/pitchladder.ts"},"src/aircraft.ts":{"./types":"src/types.ts"},"src/physics.ts":{"./types":"src/types.ts"},"src/modeler.ts":{"./types":"src/types.ts"},"src/terrain.ts":{"./types":"src/types.ts"},"src/hud.ts":{"./types":"src/types.ts"},"src/scenarios.ts":{"./types":"src/types.ts"},"src/pitchladder.ts":{"./types":"src/types.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("./types");
const aircraft_1 = require("./aircraft");
const physics_1 = require("./physics");
const modeler_1 = require("./modeler");
const terrain_1 = require("./terrain");
const input_1 = require("./input");
const audio_1 = require("./audio");
const hud_1 = require("./hud");
const scenarios_1 = require("./scenarios");
const pitchladder_1 = require("./pitchladder");
// ─── Game State ─────────────────────────────────────────────
const gameState = {
    mode: types_1.InputMode.MENU,
    selectedAircraft: 'falcon',
    selectedScenario: 'takeoff',
    flight: null,
    enemies: [],
    destroyedCount: 0,
    totalStartYaw: 0,
    lastTime: 0,
    objectives: [],
    message: '',
    messageTimer: 0,
};
// ─── Three.js Globals ──────────────────────────────────────
let renderer;
let scene;
let camera;
let playerModel = null;
let terrain = null;
let waterPlane = null;
let skyDome = null;
let runway = null;
let clouds = null;
let sunLight;
const input = new input_1.InputManager();
const audio = new audio_1.AudioSystem();
const hud = new hud_1.HUD();
const pitchLadder = new pitchladder_1.PitchLadder();
// Camera chase params
const cameraTarget = { dist: 35, height: 8, pitch: -0.15 };
const cameraSmoothPos = { x: 0, y: 0, z: 0 };
// ─── Menu System ────────────────────────────────────────────
function buildMenu() {
    const cardsContainer = document.getElementById('aircraft-cards');
    cardsContainer.innerHTML = '';
    aircraft_1.AIRCRAFT_KEYS.forEach(key => {
        const spec = aircraft_1.AIRCRAFT[key];
        const card = document.createElement('div');
        card.className = 'ac-card' + (key === gameState.selectedAircraft ? ' selected' : '');
        card.innerHTML = `
      <h3>${spec.name}</h3>
      <p>${spec.description}</p>
      <div class="ac-stats">
        <span>${Math.round(spec.maxSpeed * 1.94)} kts</span>
        <span>Wt: ${spec.weight} kg</span>
        <span>Cl: ${spec.liftCoeff}</span>
      </div>
    `;
        card.onclick = () => {
            gameState.selectedAircraft = key;
            buildMenu();
        };
        cardsContainer.appendChild(card);
    });
    const scContainer = document.getElementById('scenario-cards');
    scContainer.innerHTML = '';
    Object.keys(scenarios_1.SCENARIOS).forEach(key => {
        const sc = scenarios_1.SCENARIOS[key];
        const card = document.createElement('div');
        card.className = 'sc-card' + (key === gameState.selectedScenario ? ' selected' : '');
        card.innerHTML = `
      <h3>${sc.icon} ${sc.name}</h3>
      <p>${sc.description}</p>
    `;
        card.onclick = () => {
            gameState.selectedScenario = key;
            buildMenu();
        };
        scContainer.appendChild(card);
    });
}
function startSimulation() {
    const spec = aircraft_1.AIRCRAFT[gameState.selectedAircraft];
    const scenario = scenarios_1.SCENARIOS[gameState.selectedScenario];
    // Initialize flight state
    gameState.flight = {
        pos: { ...scenario.spawnPos },
        vel: { ...scenario.spawnVel },
        pitch: scenario.spawnOrientation.pitch,
        roll: scenario.spawnOrientation.roll,
        yaw: scenario.spawnOrientation.yaw,
        pitchRate: 0,
        rollRate: 0,
        yawRate: 0,
        throttle: 0,
        rpm: 800,
        gearDown: scenario.id === 'takeoff',
        airspeed: (0, physics_1.vec3Len)(scenario.spawnVel),
        angleOfAttack: 0,
        altitude: scenario.spawnPos.y,
        gForce: 1,
        stall: false,
        groundContact: scenario.id === 'takeoff',
    };
    gameState.totalStartYaw = scenario.spawnOrientation.yaw;
    gameState.destroyedCount = 0;
    gameState.objectives = scenario.objectives.map(o => ({ ...o }));
    gameState.message = 'Simulation started';
    gameState.messageTimer = 3;
    // Spawn enemies for dogfight
    if (scenario.id === 'dogfight') {
        gameState.enemies = (0, scenarios_1.spawnEnemies)(3, gameState.flight.pos);
        gameState.enemies.forEach(e => scene.add(e.mesh));
    }
    else {
        gameState.enemies = [];
    }
    // Update UI
    document.getElementById('menu-overlay').classList.add('hidden');
    document.getElementById('hud').classList.add('active');
    gameState.mode = types_1.InputMode.PLAYING;
    audio.init();
    hud.show();
    pitchLadder.mount();
    pitchLadder.show();
    showStatus('Simulation started. Use W/S/A/D to fly, Shift/Ctrl for throttle, R for gear.');
}
function showStatus(msg) {
    gameState.message = msg;
    gameState.messageTimer = 4;
}
// ─── Scene Setup ────────────────────────────────────────────
function initScene() {
    // Renderer
    renderer = new THREE.WebGLRenderer({
        canvas: document.getElementById('game-canvas'),
        antialias: true,
    });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setClearColor(0x87ceeb);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    // Scene
    scene = new THREE.Scene();
    scene.fog = new THREE.FogExp2(0x88bbee, 0.0008);
    // Camera
    camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 1, 50000);
    camera.position.set(0, 100, 50);
    // ── Lighting ──
    const ambientLight = new THREE.AmbientLight(0x446688, 0.5);
    scene.add(ambientLight);
    const hemiLight = new THREE.HemisphereLight(0x88bbff, 0x445522, 0.6);
    scene.add(hemiLight);
    sunLight = new THREE.DirectionalLight(0xffeedd, 1.2);
    sunLight.position.set(2000, 3000, 1000);
    sunLight.castShadow = true;
    // Shadow map config
    sunLight.shadow.mapSize.width = 2048;
    sunLight.shadow.mapSize.height = 2048;
    sunLight.shadow.camera.near = 10;
    sunLight.shadow.camera.far = 10000;
    sunLight.shadow.camera.left = -2000;
    sunLight.shadow.camera.right = 2000;
    sunLight.shadow.camera.top = 2000;
    sunLight.shadow.camera.bottom = -2000;
    scene.add(sunLight);
    // ── Terrain ──
    terrain = (0, terrain_1.generateTerrain)(8000, 256, 600, 42);
    scene.add(terrain.mesh);
    // ── Water ──
    waterPlane = (0, terrain_1.createWaterPlane)(8000);
    scene.add(waterPlane);
    // ── Sky dome ──
    skyDome = (0, terrain_1.createSkyDome)(25000);
    scene.add(skyDome);
    // ── Clouds ──
    clouds = (0, terrain_1.createClouds)(8000, 200);
    scene.add(clouds);
    // ── Runway ──
    runway = (0, terrain_1.createRunway)(200, 40, { x: 0, y: 0, z: 0 }, 0);
    scene.add(runway);
    // ── Handle resize ──
    window.addEventListener('resize', onResize);
    // ── Start button ──
    document.getElementById('start-btn').addEventListener('click', startSimulation);
    buildMenu();
}
function onResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}
// ─── Update Player Model ────────────────────────────────────
function updatePlayerModel(state, spec) {
    if (!playerModel) {
        playerModel = (0, modeler_1.buildAircraftModel)(spec);
        scene.add(playerModel);
    }
    // Position
    playerModel.position.set(state.pos.x, state.pos.y, state.pos.z);
    // Orientation (Three.js uses different convention)
    // Our pitch: positive = nose up, roll: positive = right wing down, yaw: compass heading
    // Three.js: rotation.x = pitch (but inverted), rotation.y = yaw (inverted), rotation.z = roll (inverted)
    playerModel.rotation.x = -state.pitch;
    playerModel.rotation.y = -state.yaw;
    playerModel.rotation.z = -state.roll;
    // Gear visibility
    const gearGroup = playerModel.getObjectByName('gear');
    if (gearGroup) {
        gearGroup.visible = state.gearDown;
    }
    // Exhaust glow based on throttle
    const exhaust = playerModel.getObjectByName('exhaust');
    if (exhaust && exhaust.material) {
        const mat = exhaust.material;
        mat.opacity = state.throttle * 0.7;
        const hue = 0xff6f00 + Math.floor(state.throttle * 0x880000);
        mat.color.set(hue);
    }
}
// ─── Camera Chase ───────────────────────────────────────────
function updateCamera(state, dt) {
    const { fw, rt, up } = (0, physics_1.orientationVectors)(state.pitch, state.yaw, state.roll);
    // Camera position: behind and slightly above aircraft
    const camDesiredPos = {
        x: state.pos.x - fw.x * cameraTarget.dist + up.x * cameraTarget.height,
        y: state.pos.y - fw.y * cameraTarget.dist + up.y * cameraTarget.height,
        z: state.pos.z - fw.z * cameraTarget.dist + up.z * cameraTarget.height,
    };
    // Smooth camera follow
    const smoothFactor = 1 - Math.exp(-dt * 3);
    cameraSmoothPos.x += (camDesiredPos.x - cameraSmoothPos.x) * smoothFactor;
    cameraSmoothPos.y += (camDesiredPos.y - cameraSmoothPos.y) * smoothFactor;
    cameraSmoothPos.z += (camDesiredPos.z - cameraSmoothPos.z) * smoothFactor;
    camera.position.set(cameraSmoothPos.x, cameraSmoothPos.y, cameraSmoothPos.z);
    // Look at a point slightly ahead of the aircraft
    const lookTarget = {
        x: state.pos.x + fw.x * 20,
        y: state.pos.y + fw.y * 20 + up.y * 2,
        z: state.pos.z + fw.z * 20 + up.z * 2,
    };
    camera.lookAt(new THREE.Vector3(lookTarget.x, lookTarget.y, lookTarget.z));
    // Update sky dome to follow camera
    if (skyDome) {
        skyDome.position.set(state.pos.x, state.pos.y, state.pos.z);
    }
    // Update sun shadow target to follow aircraft
    sunLight.position.set(state.pos.x + 2000, state.pos.y + 3000, state.pos.z + 1000);
    sunLight.target.position.set(state.pos.x, state.pos.y, state.pos.z);
}
// ─── Update Enemies ─────────────────────────────────────────
function updateEnemies(dt, time) {
    gameState.enemies.forEach(enemy => {
        if (!enemy.alive)
            return;
        (0, scenarios_1.updateEnemyAI)(enemy, dt, gameState.flight.pos, time);
        // Update mesh transform
        enemy.mesh.position.set(enemy.pos.x, enemy.pos.y, enemy.pos.z);
        enemy.mesh.rotation.x = -enemy.pitch;
        enemy.mesh.rotation.y = -enemy.yaw;
        enemy.mesh.rotation.z = -enemy.roll;
        // Update health bar
        const healthBar = enemy.mesh.getObjectByName('healthBar');
        if (healthBar) {
            const healthRatio = enemy.health / enemy.maxHealth;
            healthBar.scale.x = Math.max(0.01, healthRatio);
            healthBar.material.color.set(healthRatio > 0.5 ? 0x00ff00 : healthRatio > 0.25 ? 0xffaa00 : 0xff0000);
        }
        // Check if player is close enough for "shooting"
        const dx = gameState.flight.pos.x - enemy.pos.x;
        const dy = gameState.flight.pos.y - enemy.pos.y;
        const dz = gameState.flight.pos.z - enemy.pos.z;
        const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
        if (dist < 80) {
            // Player is "in range" - auto-fire lasers
            enemy.health -= dt * 25;
            if (enemy.health <= 0) {
                enemy.alive = false;
                enemy.mesh.visible = false;
                gameState.destroyedCount++;
                showStatus(`Enemy aircraft destroyed! (${gameState.destroyedCount})`);
            }
        }
    });
}
// ─── Objectives Check ───────────────────────────────────────
function checkObjectives(state) {
    gameState.objectives = (0, scenarios_1.updateObjectives)(state, gameState.objectives, gameState.totalStartYaw);
    // Check if all complete
    const allDone = gameState.objectives.every(o => o.completed);
    if (allDone) {
        const msgEl = document.getElementById('message-overlay');
        if (!msgEl.querySelector('.msg-box')) {
            msgEl.innerHTML = `<div class="msg-box">
        <div>🎉 ALL OBJECTIVES COMPLETE</div>
        <div style="font-size:0.6em;margin-top:10px;color:#90a4ae">Press ESC to return to menu</div>
      </div>`;
        }
    }
}
// ─── Render HUD Objectives ──────────────────────────────────
function updateObjectiveDisplay() {
    const statusEl = document.getElementById('hud-status');
    if (!statusEl)
        return;
    const completed = gameState.objectives.filter(o => o.completed).length;
    const total = gameState.objectives.length;
    const activeObj = gameState.objectives.find(o => !o.completed);
    if (activeObj) {
        statusEl.textContent = `[${completed}/${total}] ${activeObj.text}`;
    }
    else {
        statusEl.textContent = gameState.message;
    }
}
// ─── Return to Menu ─────────────────────────────────────────
function returnToMenu() {
    gameState.mode = types_1.InputMode.MENU;
    // Remove player model
    if (playerModel) {
        scene.remove(playerModel);
        playerModel = null;
    }
    // Remove enemies
    gameState.enemies.forEach(e => {
        if (e.mesh)
            scene.remove(e.mesh);
    });
    gameState.enemies = [];
    // Hide HUD
    document.getElementById('hud').classList.remove('active');
    pitchLadder.hide();
    document.getElementById('menu-overlay').classList.remove('hidden');
    document.getElementById('message-overlay').innerHTML = '';
    buildMenu();
}
// ─── Main Game Loop ─────────────────────────────────────────
function gameLoop(timestamp) {
    requestAnimationFrame(gameLoop);
    const dt = Math.min((timestamp - gameState.lastTime) / 1000, 0.05);
    gameState.lastTime = timestamp;
    if (gameState.mode === types_1.InputMode.PLAYING && gameState.flight) {
        const spec = aircraft_1.AIRCRAFT[gameState.selectedAircraft];
        const flight = gameState.flight;
        // Get input
        const inputState = input.getState();
        // Get terrain height at aircraft position
        const terrainAlt = terrain ? terrain.getHeightAt(flight.pos.x, flight.pos.z) : 0;
        // Physics update
        const newFlight = (0, physics_1.physicsStep)(flight, spec, dt, terrainAlt, inputState);
        Object.assign(flight, newFlight);
        // Check for ESC
        if (inputState.escPressed) {
            returnToMenu();
            return;
        }
        // Update player model
        updatePlayerModel(flight, spec);
        // Update camera
        updateCamera(flight, dt);
        // Update enemies
        updateEnemies(dt, timestamp / 1000);
        // Update audio
        audio.update(flight.throttle, flight.airspeed, flight.stall, flight.altitude);
        // Update HUD
        const scenarioName = scenarios_1.SCENARIOS[gameState.selectedScenario].name;
        let hudMsg = gameState.message;
        if (gameState.messageTimer > 0) {
            gameState.messageTimer -= dt;
        }
        else {
            const activeObj = gameState.objectives.find(o => !o.completed);
            hudMsg = activeObj ? activeObj.text : '';
        }
        if (flight.stall)
            hudMsg = '⚠️ STALL WARNING';
        hud.update(flight, spec.name, scenarioName, hudMsg);
        pitchLadder.render(flight);
        updateObjectiveDisplay();
        // Check objectives
        checkObjectives(flight);
        // Animate water
        if (waterPlane) {
            waterPlane.position.y = 1.5 + Math.sin(timestamp * 0.001) * 0.3;
        }
        // Animate clouds
        if (clouds) {
            const pos = clouds.geometry.attributes.position.array;
            for (let i = 0; i < pos.length; i += 3) {
                pos[i] += dt * 3; // drift east
                if (pos[i] > 4000)
                    pos[i] = -4000;
            }
            clouds.geometry.attributes.position.needsUpdate = true;
        }
    }
    // Render
    renderer.render(scene, camera);
}
// ─── Initialize ─────────────────────────────────────────────
function main() {
    try {
        hud.init();
        initScene();
        gameState.lastTime = performance.now();
        requestAnimationFrame(gameLoop);
    }
    catch (e) {
        window.__INIT_ERROR__ = String(e);
        throw e;
    }
}
// Auto-run
main();
exports.default = main;
};

// ── types: globals.d.ts ──
__mods["globals.d.ts"] = function (exports) {};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// ─── Core Types ─────────────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputMode = void 0;
var InputMode;
(function (InputMode) {
    InputMode[InputMode["MENU"] = 0] = "MENU";
    InputMode[InputMode["PLAYING"] = 1] = "PLAYING";
    InputMode[InputMode["PAUSED"] = 2] = "PAUSED";
})(InputMode || (exports.InputMode = InputMode = {}));
};

// ── module: src/aircraft.ts ──
__mods["src/aircraft.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AIRCRAFT_KEYS = exports.AIRCRAFT = void 0;
exports.AIRCRAFT = {
    falcon: {
        name: 'Falcon X-1',
        description: 'Light interceptor. Fast, agile, but fragile. Excellent turn rate.',
        maxSpeed: 180,
        thrust: 28000,
        liftCoeff: 1.4,
        dragCoeff: 0.02,
        wingArea: 15,
        weight: 6500,
        momentOfInertia: 1.0,
        turnRate: 120,
        pitchRate: 60,
        yawRate: 30,
        stallAngle: 0.25,
        fuselageColor: 0x90a4ae,
        wingColor: 0x78909c,
        accentColor: 0xf44336,
        bodyLength: 12,
        wingSpan: 8,
        tailSpan: 5,
        bodyDiameter: 1.2,
    },
    titan: {
        name: 'Titan Heavy',
        description: 'Heavy fighter-bomber. Slow but powerful. Can sustain high-G turns.',
        maxSpeed: 140,
        thrust: 65000,
        liftCoeff: 1.2,
        dragCoeff: 0.035,
        wingArea: 40,
        weight: 22000,
        momentOfInertia: 2.5,
        turnRate: 70,
        pitchRate: 35,
        yawRate: 20,
        stallAngle: 0.3,
        fuselageColor: 0x5d4037,
        wingColor: 0x4e342e,
        accentColor: 0xffa000,
        bodyLength: 20,
        wingSpan: 16,
        tailSpan: 8,
        bodyDiameter: 2.5,
    },
    phantom: {
        name: 'Phantom Stealth',
        description: 'Balanced multi-role. Good all-around performance with stealth profile.',
        maxSpeed: 160,
        thrust: 40000,
        liftCoeff: 1.3,
        dragCoeff: 0.025,
        wingArea: 25,
        weight: 12000,
        momentOfInertia: 1.5,
        turnRate: 90,
        pitchRate: 50,
        yawRate: 25,
        stallAngle: 0.28,
        fuselageColor: 0x37474f,
        wingColor: 0x263238,
        accentColor: 0x76ff03,
        bodyLength: 16,
        wingSpan: 12,
        tailSpan: 6,
        bodyDiameter: 1.8,
    },
};
exports.AIRCRAFT_KEYS = Object.keys(exports.AIRCRAFT);
};

// ── module: src/physics.ts ──
__mods["src/physics.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEG2RAD = void 0;
exports.physicsStep = physicsStep;
exports.vec3 = vec3;
exports.vec3Add = vec3Add;
exports.vec3Scale = vec3Scale;
exports.vec3Dot = vec3Dot;
exports.vec3Len = vec3Len;
exports.vec3Norm = vec3Norm;
exports.vec3Cross = vec3Cross;
exports.airDensityAtAlt = airDensityAtAlt;
exports.orientationVectors = orientationVectors;
// ─── Constants ───────────────────────────────────────────────
const SEA_LEVEL_PRESSURE = 101325; // Pa
const SEA_LEVEL_TEMP = 288.15; // K
const GRAVITY = 9.81; // m/s²
const SEA_LEVEL_DENSITY = 1.225; // kg/m³
const LAPS_RATE = 0.0065; // K/m (troposphere)
const GAS_CONST = 287.05; // J/(kg·K)
const DEG2RAD = Math.PI / 180;
exports.DEG2RAD = DEG2RAD;
// ─── Helper Functions ────────────────────────────────────────
function vec3(x = 0, y = 0, z = 0) { return { x, y, z }; }
function vec3Add(a, b) { return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }; }
function vec3Scale(a, s) { return { x: a.x * s, y: a.y * s, z: a.z * s }; }
function vec3Dot(a, b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
function vec3Len(a) { return Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z); }
function vec3Norm(a) {
    const l = vec3Len(a);
    return l > 1e-8 ? { x: a.x / l, y: a.y / l, z: a.z / l } : vec3(0, 0, 0);
}
function vec3Cross(a, b) {
    return {
        x: a.y * b.z - a.z * b.y,
        y: a.z * b.x - a.x * b.z,
        z: a.x * b.y - a.y * b.x,
    };
}
// Air density at altitude (ISA model, troposphere)
function airDensityAtAlt(altM) {
    if (altM < 0)
        altM = 0;
    const temp = Math.max(SEA_LEVEL_TEMP - LAPS_RATE * altM, 200);
    const ratio = temp / SEA_LEVEL_TEMP;
    return SEA_LEVEL_DENSITY * Math.pow(ratio, 4.255877);
}
// Convert euler angles to forward/right/up unit vectors
function orientationVectors(pitch, yaw, roll) {
    const cosP = Math.cos(pitch), sinP = Math.sin(pitch);
    const cosY = Math.cos(yaw), sinY = Math.sin(yaw);
    const cosR = Math.cos(roll), sinR = Math.sin(roll);
    // Forward direction in aircraft frame, transformed to world
    // Aircraft forward: (0, 0, -1) in local frame
    // Apply rotations: yaw (Y), pitch (X), roll (Z)
    const forward = vec3(-sinY * cosP + cosY * sinP * sinR, cosY * cosP * sinR + sinY * sinP, cosY * cosP * cosR - sinY * sinP * sinR);
    // But simpler approach: standard aerospace sequence
    // yaw around Y, pitch around local X, roll around local Z
    // Let's use proper rotation order: Yaw → Pitch → Roll
    const fw = vec3(cosP * cosY, -sinP, cosP * sinY);
    // Right vector (positive roll = right wing down)
    const rt = vec3(-cosR * sinY + sinR * sinP * cosY, sinR * cosP, -cosR * cosY - sinR * sinP * sinY);
    // Up vector
    const up = vec3Cross(fw, rt);
    return { fw: vec3Norm(fw), rt: vec3Norm(rt), up: vec3Norm(up) };
}
// ─── Physics Step ────────────────────────────────────────────
function physicsStep(state, spec, dt, terrainAlt, input) {
    const { pos, vel, pitch, roll, yaw, throttle, gearDown } = state;
    // ── Air density ──
    const altAboveSea = pos.y;
    const rho = airDensityAtAlt(altAboveSea);
    const dynamicPressure = 0.5 * rho * vel.z * vel.z;
    // ── True airspeed ──
    const speed = vec3Len(vel);
    const speedClamped = Math.max(speed, 0.1);
    // ── Angle of Attack ──
    // AoA is angle between velocity vector and aircraft longitudinal plane
    const { fw, rt, up } = orientationVectors(pitch, yaw, roll);
    const velNorm = vec3Norm(vel);
    const velInFwdPlane = vec3Dot(velNorm, fw);
    const velInUpDir = vec3Dot(velNorm, up);
    let aoa = Math.atan2(-velInUpDir, velInFwdPlane);
    aoa = Math.max(-0.6, Math.min(0.6, aoa)); // clamp
    // ── Sideslip angle ──
    const velInRight = vec3Dot(velNorm, rt);
    const beta = Math.asin(Math.max(-1, Math.min(1, velInRight)));
    // ── Lift ──
    // L = 0.5 * rho * V² * S * Cl
    // Cl depends on AoA: linear up to stall, then drops
    let cl;
    const aoaRad = aoa;
    if (Math.abs(aoaRad) >= spec.stallAngle) {
        // Stall: lift drops dramatically
        const stallExcess = Math.abs(aoaRad) - spec.stallAngle;
        cl = spec.liftCoeff * (1.0 - stallExcess * 3.0);
        cl = Math.max(-0.5, Math.min(spec.liftCoeff, cl));
    }
    else {
        // Linear region with slight nonlinearity
        const aoaRatio = aoaRad / spec.stallAngle;
        cl = spec.liftCoeff * aoaRatio * (1.0 + 0.5 * Math.abs(aoaRatio));
    }
    // Also add lift from roll (banked flight: vertical lift component reduces)
    const liftMagnitude = 0.5 * rho * speedClamped * speedClamped * spec.wingArea * cl;
    const liftDir = vec3Cross(fw, rt); // up in aircraft frame
    // ── Drag ──
    // Total drag = profile drag + induced drag
    // Cd = Cd0 + Cl² / (π * e * AR)
    const aspectRatio = spec.wingSpan * spec.wingSpan / spec.wingArea;
    const efficiency = 0.8; // Oswald efficiency
    const cdInduced = (cl * cl) / (Math.PI * efficiency * aspectRatio);
    const cdTotal = spec.dragCoeff + cdInduced;
    // Speed-dependent profile drag increases
    const speedRatio = speed / spec.maxSpeed;
    const cdAdjusted = cdTotal * (1.0 + speedRatio * speedRatio * 0.5);
    const dragMagnitude = 0.5 * rho * speedClamped * speedClamped * spec.wingArea * cdAdjusted;
    // Ground effect: reduces induced drag near ground
    const heightAboveTerrain = pos.y - terrainAlt;
    const groundEffectFactor = heightAboveTerrain < spec.wingSpan
        ? 1.0 - (1.0 - heightAboveTerrain / spec.wingSpan) * 0.3
        : 1.0;
    // ── Thrust ──
    // Throttle ramps up/down gradually
    const throttleRate = 0.5; // per second
    let targetThrottle = throttle + input.throttleCmd * dt * throttleRate;
    targetThrottle = Math.max(0, Math.min(1, targetThrottle));
    // Throttle decay when no input
    if (Math.abs(input.throttleCmd) < 0.01) {
        // Keep current throttle
    }
    const currentThrottle = targetThrottle;
    const thrustForce = spec.thrust * currentThrottle;
    // ── Build Force Vectors ──
    const gravityForce = vec3(0, -spec.weight * GRAVITY, 0);
    // Lift acts perpendicular to velocity in the aircraft's lift plane
    const liftVector = vec3Scale(liftDir, liftMagnitude);
    // Drag acts opposite to velocity
    const dragDir = speed > 0.01 ? vec3Scale(vec3Norm(vel), -1) : vec3(0, 0, 0);
    const dragVector = vec3Scale(dragDir, dragMagnitude * groundEffectFactor);
    // Thrust acts along aircraft forward axis
    const thrustVector = vec3Scale(fw, thrustForce);
    // Total force
    const totalForce = vec3Add(vec3Add(vec3Add(thrustVector, liftVector), dragVector), gravityForce);
    // ── Acceleration (F = ma) ──
    const accel = vec3Scale(totalForce, 1.0 / spec.weight);
    // ── Integrate velocity & position ──
    const newVel = vec3Add(vel, vec3Scale(accel, dt));
    const newPos = vec3Add(pos, vec3Scale(newVel, dt));
    // ── Angular Motion ──
    // Angular rates based on input, scaled by aircraft response
    const pitchTarget = -input.pitch * spec.pitchRate * DEG2RAD; // positive pitch input → nose up (negative pitch angle change)
    const rollTarget = input.roll * spec.turnRate * DEG2RAD;
    const yawTarget = input.yaw * spec.yawRate * DEG2RAD;
    // Smooth angular rate transitions (inertia)
    const rateResponseTime = spec.momentOfInertia * 0.5;
    const rateDt = dt / rateResponseTime;
    const rateSmooth = 1.0 - Math.exp(-rateDt);
    const newPitchRate = state.pitchRate + (pitchTarget - state.pitchRate) * rateSmooth;
    const newRollRate = state.rollRate + (rollTarget - state.rollRate) * rateSmooth;
    const newYawRate = state.yawRate + (yawTarget - state.yawRate) * rateSmooth;
    // Update orientation
    let newPitch = pitch + newPitchRate * dt;
    let newRoll = roll + newRollRate * dt;
    let newYaw = yaw + newYawRate * dt;
    // Clamp pitch to avoid flip
    newPitch = Math.max(-Math.PI * 0.45, Math.min(Math.PI * 0.45, newPitch));
    // Auto-dampen extreme roll
    if (Math.abs(newRoll) > Math.PI * 0.45) {
        newRoll *= 0.98;
    }
    // ── Stall effects ──
    const isStalling = Math.abs(aoa) >= spec.stallAngle && speed > 1;
    if (isStalling) {
        // Add random pitch perturbation on stall
        newPitch += (Math.random() - 0.5) * dt * 2.0;
    }
    // ── G-force ──
    const gForce = vec3Len(vec3Add(accel, vec3(0, GRAVITY, 0))) / GRAVITY;
    // ── Ground collision ──
    const terrainHeight = terrainAlt;
    let groundContact = false;
    const aircraftRadius = spec.bodyDiameter * 0.5;
    if (newPos.y < terrainHeight + aircraftRadius) {
        // Hard ground collision
        const impactSpeed = Math.abs(newVel.y);
        if (gearDown && impactSpeed < 8) {
            // Safe landing / ground roll
            newPos.y = terrainHeight + aircraftRadius;
            newVel.y = Math.min(newVel.y, 0);
            // Ground friction
            const friction = 0.9;
            newVel.x *= friction;
            newVel.z *= friction;
            groundContact = true;
        }
        else if (impactSpeed > 15) {
            // Crash - bounce violently
            newVel.y = -newVel.y * 0.3;
            newPos.y = terrainHeight + aircraftRadius + impactSpeed * 0.5;
            newPitch += (Math.random() - 0.5) * 0.5;
            newRoll += (Math.random() - 0.5) * 0.5;
        }
        else {
            // Soft collision
            newPos.y = terrainHeight + aircraftRadius;
            newVel.y = Math.min(newVel.y * 0.5, 0);
            groundContact = true;
        }
    }
    // ── Gear ──
    let newGearDown = gearDown;
    if (input.gearToggle) {
        newGearDown = !gearDown;
    }
    // Auto-retract gear at high speed
    if (speed > 50 && !groundContact) {
        newGearDown = false;
    }
    // ── RPM ──
    const rpm = currentThrottle * 12000 + 800;
    // ── Build new state ──
    return {
        pos: newPos,
        vel: newVel,
        pitch: newPitch,
        roll: newRoll,
        yaw: newYaw,
        pitchRate: newPitchRate,
        rollRate: newRollRate,
        yawRate: newYawRate,
        throttle: currentThrottle,
        rpm,
        gearDown: newGearDown,
        airspeed: speed,
        angleOfAttack: aoa,
        altitude: pos.y - terrainHeight,
        gForce,
        stall: isStalling,
        groundContact,
    };
}
};

// ── module: src/modeler.ts ──
__mods["src/modeler.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAircraftModel = buildAircraftModel;
// Build a 3D aircraft model from spec parameters
function buildAircraftModel(spec) {
    const group = new THREE.Group();
    const fuselageMat = new THREE.MeshStandardMaterial({
        color: spec.fuselageColor,
        metalness: 0.6,
        roughness: 0.3,
    });
    const wingMat = new THREE.MeshStandardMaterial({
        color: spec.wingColor,
        metalness: 0.5,
        roughness: 0.4,
    });
    const accentMat = new THREE.MeshStandardMaterial({
        color: spec.accentColor,
        metalness: 0.3,
        roughness: 0.5,
    });
    const glassMat = new THREE.MeshStandardMaterial({
        color: 0x80deea,
        metalness: 0.1,
        roughness: 0.1,
        transparent: true,
        opacity: 0.6,
    });
    const darkMat = new THREE.MeshStandardMaterial({
        color: 0x263238,
        metalness: 0.8,
        roughness: 0.2,
    });
    const { bodyLength, wingSpan, tailSpan, bodyDiameter } = spec;
    const bl = bodyLength;
    const ws = wingSpan;
    const ts = tailSpan;
    const bd = bodyDiameter;
    // ── Fuselage (cylinder along local X axis) ──
    const fuselageGeo = new THREE.PlaneGeometry(bd * 1.1, bl, 8, 32);
    // We'll use a lathe approach: build fuselage from profile
    // Actually, simpler: just use cylinder scaled
    const fuselage = new THREE.Mesh(new THREE.CylinderGeometry(bd * 0.45, bd * 0.25, bl, 12, 1), fuselageMat);
    fuselage.rotation.z = Math.PI / 2;
    fuselage.position.set(0, 0, 0);
    group.add(fuselage);
    // Nose cone (pointed)
    const nose = new THREE.Mesh(new THREE.ConeGeometry(bd * 0.45, bl * 0.35, 12), fuselageMat);
    nose.rotation.z = -Math.PI / 2;
    nose.position.set(bl * 0.5 + bl * 0.15, 0, 0);
    group.add(nose);
    // Cockpit canopy
    const canopy = new THREE.Mesh(new THREE.SphereGeometry(bd * 0.5, 8, 6, 0, Math.PI * 2, 0, Math.PI / 2), glassMat);
    canopy.position.set(bl * 0.15, bd * 0.4, 0);
    canopy.scale.set(1.2, 0.7, 1.0);
    group.add(canopy);
    // ── Wings ──
    // Main wing: swept back
    const wingHalfSpan = ws / 2;
    const wingChord = ws * 0.18;
    const wingThickness = bd * 0.12;
    // Left wing
    const leftWing = new THREE.Mesh(new THREE.BoxGeometry(wingChord, wingThickness, wingHalfSpan), wingMat);
    leftWing.position.set(-bl * 0.05, 0, -wingHalfSpan / 2 - 0.3);
    // Sweep
    leftWing.rotation.y = -0.2;
    group.add(leftWing);
    // Right wing
    const rightWing = new THREE.Mesh(new THREE.BoxGeometry(wingChord, wingThickness, wingHalfSpan), wingMat);
    rightWing.position.set(-bl * 0.05, 0, wingHalfSpan / 2 + 0.3);
    rightWing.rotation.y = -0.2;
    group.add(rightWing);
    // Wing tips (colored accent)
    const tipGeo = new THREE.BoxGeometry(wingChord * 0.3, wingThickness * 1.5, 0.3);
    const leftTip = new THREE.Mesh(tipGeo, accentMat);
    leftTip.position.set(-bl * 0.05 - wingChord * 0.2, 0, -wingHalfSpan - 0.3);
    group.add(leftTip);
    const rightTip = new THREE.Mesh(tipGeo, accentMat);
    rightTip.position.set(-bl * 0.05 - wingChord * 0.2, 0, wingHalfSpan + 0.3);
    group.add(rightTip);
    // ── Tail ──
    // Horizontal stabilizer
    const hTail = new THREE.Mesh(new THREE.BoxGeometry(wingChord * 0.6, wingThickness * 0.7, ts), wingMat);
    hTail.position.set(-bl * 0.4, bd * 0.2, 0);
    group.add(hTail);
    // Vertical stabilizer
    const vTailHeight = bl * 0.25;
    const vTail = new THREE.Mesh(new THREE.BoxGeometry(wingChord * 0.7, vTailHeight, wingThickness * 1.5), wingMat);
    vTail.position.set(-bl * 0.45, vTailHeight / 2 + bd * 0.1, 0);
    group.add(vTail);
    // V-tail accent stripe
    const vStripe = new THREE.Mesh(new THREE.BoxGeometry(0.1, vTailHeight * 0.8, wingThickness * 2), accentMat);
    vStripe.position.set(-bl * 0.45, vTailHeight / 2 + bd * 0.1, 0);
    group.add(vStripe);
    // ── Engine nacelles ──
    const nacelleLen = bl * 0.3;
    const nacelleRad = bd * 0.3;
    for (const side of [-1, 1]) {
        const nacelle = new THREE.Mesh(new THREE.CylinderGeometry(nacelleRad, nacelleRad * 0.7, nacelleLen, 8), darkMat);
        nacelle.rotation.z = Math.PI / 2;
        nacelle.position.set(-bl * 0.15, -bd * 0.15, side * (wingHalfSpan * 0.5 + 0.5));
        group.add(nacelle);
        // Engine intake
        const intake = new THREE.Mesh(new THREE.TorusGeometry(nacelleRad * 0.8, 0.08, 6, 8), darkMat);
        intake.position.set(-bl * 0.15 + nacelleLen / 2, -bd * 0.15, side * (wingHalfSpan * 0.5 + 0.5));
        intake.rotation.y = Math.PI / 2;
        group.add(intake);
    }
    // ── Landing gear (visible when deployed) ──
    const gearGroup = new THREE.Group();
    gearGroup.name = 'gear';
    const gearMat = new THREE.MeshStandardMaterial({ color: 0x424242, metalness: 0.8, roughness: 0.3 });
    // Nose gear
    const noseStrut = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, bd * 0.8, 6), gearMat);
    noseStrut.position.set(bl * 0.2, -bd * 0.8, 0);
    gearGroup.add(noseStrut);
    const noseWheel = new THREE.Mesh(new THREE.SphereGeometry(0.15, 8, 8), darkMat);
    noseWheel.position.set(bl * 0.2, -bd * 1.2, 0);
    gearGroup.add(noseWheel);
    // Main gear
    for (const side of [-1, 1]) {
        const mainStrut = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, bd * 1.0, 6), gearMat);
        mainStrut.position.set(-bl * 0.05, -bd * 1.0, side * wingHalfSpan * 0.3);
        gearGroup.add(mainStrut);
        const mainWheel = new THREE.Mesh(new THREE.SphereGeometry(0.2, 8, 8), darkMat);
        mainWheel.position.set(-bl * 0.05, -bd * 1.5, side * wingHalfSpan * 0.3);
        gearGroup.add(mainWheel);
    }
    gearGroup.visible = false;
    group.add(gearGroup);
    // ── Exhaust glow ──
    const exhaustMat = new THREE.MeshBasicMaterial({
        color: 0xff6f00,
        transparent: true,
        opacity: 0.4,
    });
    const exhaust = new THREE.Mesh(new THREE.ConeGeometry(nacelleRad * 0.6, 1.5, 8), exhaustMat);
    exhaust.rotation.z = Math.PI / 2;
    exhaust.name = 'exhaust';
    // We'll place it on one nacelle (visual only)
    exhaust.position.set(-bl * 0.15 - nacelleLen / 2 - 0.3, -bd * 0.15, 0);
    group.add(exhaust);
    // ── Navigation lights ──
    const redLight = new THREE.Mesh(new THREE.SphereGeometry(0.1, 6, 6), new THREE.MeshBasicMaterial({ color: 0xff0000 }));
    redLight.position.set(-bl * 0.1, 0.2, -wingHalfSpan - 0.3);
    group.add(redLight);
    const greenLight = new THREE.Mesh(new THREE.SphereGeometry(0.1, 6, 6), new THREE.MeshBasicMaterial({ color: 0x00ff00 }));
    greenLight.position.set(-bl * 0.1, 0.2, wingHalfSpan + 0.3);
    group.add(greenLight);
    // ── Scale model to scene-appropriate size ──
    // Models are built at ~1:1 scale of the aircraft dimensions
    // We keep them as-is; the scene will position them accordingly
    return group;
}
};

// ── module: src/terrain.ts ──
__mods["src/terrain.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateTerrain = generateTerrain;
exports.createWaterPlane = createWaterPlane;
exports.createSkyDome = createSkyDome;
exports.createRunway = createRunway;
exports.createClouds = createClouds;
// ─── Simplex-like noise for terrain ─────────────────────────
// Fast hash-based pseudo-random
function hash(x, y) {
    let h = x * 374761393 + y * 668265263;
    h = (h ^ (h >> 13)) * 1274126177;
    return (h ^ (h >> 16)) / 4294967296.0;
}
function smoothNoise(x, y) {
    const ix = Math.floor(x);
    const iy = Math.floor(y);
    const fx = x - ix;
    const fy = y - iy;
    const sx = fx * fx * (3 - 2 * fx);
    const sy = fy * fy * (3 - 2 * fy);
    const n00 = hash(ix, iy);
    const n10 = hash(ix + 1, iy);
    const n01 = hash(ix, iy + 1);
    const n11 = hash(ix + 1, iy + 1);
    const nx0 = n00 + (n10 - n00) * sx;
    const nx1 = n01 + (n11 - n01) * sx;
    return nx0 + (nx1 - nx0) * sy;
}
function fbm(x, y, octaves) {
    let value = 0;
    let amplitude = 1;
    let frequency = 1;
    let maxValue = 0;
    for (let i = 0; i < octaves; i++) {
        value += amplitude * smoothNoise(x * frequency, y * frequency);
        maxValue += amplitude;
        amplitude *= 0.5;
        frequency *= 2.0;
    }
    return value / maxValue;
}
function generateTerrain(size, resolution, maxHeight, seed) {
    const halfSize = size / 2;
    const segments = resolution;
    const vertsPerRow = segments + 1;
    // Build geometry
    const geo = new THREE.PlaneGeometry(size, size, segments, segments);
    // Rotate to lie flat on XZ plane
    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];
        // Multi-octave noise for realistic terrain
        const nx = (x + seed * 100) * 0.002;
        const nz = (z + seed * 100) * 0.002;
        let height = fbm(nx, nz, 6) * maxHeight;
        // Add ridges
        const ridge = 1.0 - Math.abs(fbm(nx * 2.5 + 5, nz * 2.5 + 5, 4) * 2 - 1);
        height += ridge * ridge * maxHeight * 0.4;
        // Flatten near center for runway area
        const distFromCenter = Math.sqrt(x * x + z * z);
        const runwayRadius = halfSize * 0.15;
        if (distFromCenter < runwayRadius) {
            const flattenFactor = distFromCenter / runwayRadius;
            height *= flattenFactor * flattenFactor;
            height = Math.min(height, 2);
        }
        // Water areas (low height)
        if (height < 5) {
            height = Math.min(height, 2);
        }
        positions[i + 1] = height;
        // Vertex colors based on height
        const h = Math.max(0, height) / maxHeight;
        if (height < 3) {
            // Water/sand
            colors[i] = 0.4;
            colors[i + 1] = 0.55;
            colors[i + 2] = 0.3;
        }
        else if (height < maxHeight * 0.3) {
            // Grass
            colors[i] = 0.25;
            colors[i + 1] = 0.45;
            colors[i + 2] = 0.15;
        }
        else if (height < maxHeight * 0.6) {
            // Rock
            colors[i] = 0.4;
            colors[i + 1] = 0.4;
            colors[i + 2] = 0.35;
        }
        else if (height < maxHeight * 0.8) {
            // Dark rock
            colors[i] = 0.3;
            colors[i + 1] = 0.3;
            colors[i + 2] = 0.28;
        }
        else {
            // Snow
            colors[i] = 0.9;
            colors[i + 1] = 0.9;
            colors[i + 2] = 0.92;
        }
    }
    geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
    geo.computeVertexNormals();
    const mat = new THREE.MeshStandardMaterial({
        vertexColors: true,
        roughness: 0.85,
        metalness: 0.05,
        flatShading: true,
        side: THREE.DoubleSide,
    });
    const mesh = new THREE.Mesh(geo, mat);
    mesh.receiveShadow = true;
    // Store height data for collision
    const heightData = new Float32Array(vertsPerRow * vertsPerRow);
    for (let iz = 0; iz <= segments; iz++) {
        for (let ix = 0; ix <= segments; ix++) {
            const idx = iz * vertsPerRow + ix;
            const worldX = -halfSize + (ix / segments) * size;
            const worldZ = -halfSize + (iz / segments) * size;
            const localIdx = iz * vertsPerRow + ix;
            heightData[localIdx] = positions[localIdx * 3 + 1];
        }
    }
    // Height lookup function
    const getHeightAt = (x, z) => {
        // Map world coord to grid index
        const gridX = ((x + halfSize) / size) * segments;
        const gridZ = ((z + halfSize) / size) * segments;
        const ix0 = Math.max(0, Math.min(segments - 1, Math.floor(gridX)));
        const iz0 = Math.max(0, Math.min(segments - 1, Math.floor(gridZ)));
        const ix1 = Math.min(segments, ix0 + 1);
        const iz1 = Math.min(segments, iz0 + 1);
        const fx = gridX - ix0;
        const fz = gridZ - iz0;
        const h00 = heightData[iz0 * vertsPerRow + ix0];
        const h10 = heightData[iz0 * vertsPerRow + ix1];
        const h01 = heightData[iz1 * vertsPerRow + ix0];
        const h11 = heightData[iz1 * vertsPerRow + ix1];
        const h0 = h00 * (1 - fx) + h10 * fx;
        const h1 = h01 * (1 - fx) + h11 * fx;
        return h0 * (1 - fz) + h1 * fz;
    };
    return {
        mesh,
        getWidth: () => size,
        getDepth: () => size,
        getHeightAt,
    };
}
// ─── Water plane ────────────────────────────────────────────
function createWaterPlane(size) {
    const geo = new THREE.PlaneGeometry(size, size);
    geo.rotateX(-Math.PI / 2);
    const mat = new THREE.MeshPhongMaterial({
        color: 0x1a5276,
        transparent: true,
        opacity: 0.6,
        shininess: 100,
        specular: 0x87ceeb,
    });
    const water = new THREE.Mesh(geo, mat);
    water.position.y = 1.5; // Slightly above absolute zero
    return water;
}
// ─── Sky dome ───────────────────────────────────────────────
function createSkyDome(radius) {
    const geo = new THREE.SphereGeometry(radius, 32, 16);
    // Gradient material
    const mat = new THREE.ShaderMaterial({
        uniforms: {
            topColor: { value: new THREE.Color(0x0044aa) },
            bottomColor: { value: new THREE.Color(0x88ccff) },
            horizonColor: { value: new THREE.Color(0xddeeff) },
            offset: { value: 20 },
            exponent: { value: 0.4 },
        },
        vertexShader: `
      varying vec3 vWorldPosition;
      void main() {
        vec4 worldPos = modelMatrix * vec4(position, 1.0);
        vWorldPosition = worldPos.xyz;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    `,
        fragmentShader: `
      uniform vec3 topColor;
      uniform vec3 bottomColor;
      uniform vec3 horizonColor;
      uniform float offset;
      uniform float exponent;
      varying vec3 vWorldPosition;
      void main() {
        float h = normalize(vWorldPosition + offset).y;
        float t = max(pow(max(h, 0.0), exponent), 0.0);
        vec3 col = mix(horizonColor, topColor, smoothstep(0.2, 1.0, t));
        col = mix(bottomColor, col, smoothstep(0.0, 0.3, t));
        gl_FragColor = vec4(col, 1.0);
      }
    `,
        side: THREE.BackSide,
        depthWrite: false,
    });
    return new THREE.Mesh(geo, mat);
}
// ─── Runway ─────────────────────────────────────────────────
function createRunway(length, width, pos, heading) {
    const group = new THREE.Group();
    // Runway surface
    const runwayGeo = new THREE.PlaneGeometry(length, width);
    runwayGeo.rotateX(-Math.PI / 2);
    const runwayMat = new THREE.MeshStandardMaterial({
        color: 0x333333,
        roughness: 0.9,
        metalness: 0.0,
    });
    const runway = new THREE.Mesh(runwayGeo, runwayMat);
    group.add(runway);
    // Runway markings (center line dashes)
    const dashMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
    const numDashes = Math.floor(length / 8);
    for (let i = 0; i < numDashes; i++) {
        const dashGeo = new THREE.PlaneGeometry(3, 0.4);
        dashGeo.rotateX(-Math.PI / 2);
        const dash = new THREE.Mesh(dashGeo, dashMat);
        dash.position.set(-length / 2 + 4 + i * 8, 0.02, 0);
        group.add(dash);
    }
    // Edge lines
    for (const side of [-1, 1]) {
        const edgeGeo = new THREE.PlaneGeometry(length, 0.3);
        edgeGeo.rotateX(-Math.PI / 2);
        const edge = new THREE.Mesh(edgeGeo, dashMat);
        edge.position.set(0, 0.02, side * (width / 2 - 0.5));
        group.add(edge);
    }
    // Threshold markings
    const thresholdCount = 6;
    const thresholdSpacing = width / (thresholdCount + 1);
    for (const end of [-1, 1]) {
        for (let i = 0; i < thresholdCount; i++) {
            const stripGeo = new THREE.PlaneGeometry(4, 0.8);
            stripGeo.rotateX(-Math.PI / 2);
            const strip = new THREE.Mesh(stripGeo, dashMat);
            strip.position.set(end * (length / 2 - 5), 0.02, -thresholdSpacing + i * thresholdSpacing);
            group.add(strip);
        }
    }
    group.position.set(pos.x, 3, pos.z);
    group.rotation.y = heading;
    return group;
}
// ─── Cloud particles ────────────────────────────────────────
function createClouds(size, count) {
    const positions = new Float32Array(count * 3);
    for (let i = 0; i < count; i++) {
        positions[i * 3] = (Math.random() - 0.5) * size;
        positions[i * 3 + 1] = 80 + Math.random() * 120;
        positions[i * 3 + 2] = (Math.random() - 0.5) * size;
    }
    const geo = new THREE.BufferGeometry();
    geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    const mat = new THREE.PointsMaterial({
        color: 0xffffff,
        size: 30,
        transparent: true,
        opacity: 0.4,
        sizeAttenuation: true,
    });
    return new THREE.Points(geo, mat);
}
};

// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
// ─── Input Manager ──────────────────────────────────────────
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputManager = void 0;
class InputManager {
    constructor() {
        this.keys = new Set();
        this._state = {
            pitch: 0, roll: 0, yaw: 0, throttleCmd: 0, gearToggle: false, escPressed: false,
        };
        this._prevGearToggle = false;
        window.addEventListener('keydown', (e) => {
            this.keys.add(e.code);
            if (e.code === 'KeyR')
                this._prevGearToggle = false;
            if (e.code === 'Escape')
                this._state.escPressed = true;
        });
        window.addEventListener('keyup', (e) => {
            this.keys.delete(e.code);
            this._state.escPressed = false;
        });
    }
    getState() {
        const state = this._state;
        // Pitch
        state.pitch = 0;
        if (this.keys.has('KeyW'))
            state.pitch -= 1;
        if (this.keys.has('KeyS'))
            state.pitch += 1;
        // Roll
        state.roll = 0;
        if (this.keys.has('KeyA'))
            state.roll -= 1;
        if (this.keys.has('KeyD'))
            state.roll += 1;
        // Yaw
        state.yaw = 0;
        if (this.keys.has('KeyQ'))
            state.yaw -= 1;
        if (this.keys.has('KeyE'))
            state.yaw += 1;
        // Throttle
        state.throttleCmd = 0;
        if (this.keys.has('ShiftLeft') || this.keys.has('ShiftRight'))
            state.throttleCmd += 1;
        if (this.keys.has('ControlLeft') || this.keys.has('ControlRight'))
            state.throttleCmd -= 1;
        // Gear toggle
        if (this.keys.has('KeyR') && !this._prevGearToggle) {
            state.gearToggle = true;
        }
        else {
            state.gearToggle = false;
        }
        if (this.keys.has('KeyR'))
            this._prevGearToggle = true;
        return state;
    }
}
exports.InputManager = InputManager;
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
// ─── Procedural Audio System ────────────────────────────────
// Generates engine noise, wind, stall warnings, etc. from Web Audio API
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioSystem = void 0;
class AudioSystem {
    constructor() {
        this.ctx = null;
        this.engineOsc = null;
        this.engineGain = null;
        this.windNoise = null;
        this.windGain = null;
        this.stallOsc = null;
        this.stallGain = null;
        this.masterGain = null;
        this.initialized = false;
    }
    init() {
        if (this.initialized)
            return;
        try {
            this.ctx = new AudioContext();
            this.masterGain = this.ctx.createGain();
            this.masterGain.gain.value = 0.4;
            this.masterGain.connect(this.ctx.destination);
            // ── Engine sound: layered oscillators for realism ──
            // Low-frequency rumble
            const engineOsc1 = this.ctx.createOscillator();
            engineOsc1.type = 'sawtooth';
            engineOsc1.frequency.value = 55;
            // Mid harmonic
            const engineOsc2 = this.ctx.createOscillator();
            engineOsc2.type = 'square';
            engineOsc2.frequency.value = 110;
            // High whine (turbine)
            const engineOsc3 = this.ctx.createOscillator();
            engineOsc3.type = 'sine';
            engineOsc3.frequency.value = 440;
            // Engine gain (volume controlled by throttle)
            this.engineGain = this.ctx.createGain();
            this.engineGain.gain.value = 0;
            // Filter for engine tone
            const engineFilter = this.ctx.createBiquadFilter();
            engineFilter.type = 'lowpass';
            engineFilter.frequency.value = 800;
            engineFilter.Q.value = 2;
            engineOsc1.connect(this.engineGain);
            engineOsc2.connect(this.engineGain);
            engineOsc3.connect(this.engineGain);
            this.engineGain.connect(engineFilter);
            engineFilter.connect(this.masterGain);
            engineOsc1.start();
            engineOsc2.start();
            engineOsc3.start();
            this.engineOsc = engineOsc1; // reference for frequency updates
            // ── Wind noise ──
            const bufferSize = this.ctx.sampleRate * 2;
            const windBuffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
            const windData = windBuffer.getChannelData(0);
            for (let i = 0; i < bufferSize; i++) {
                windData[i] = (Math.random() * 2 - 1) * 0.5;
            }
            this.windNoise = this.ctx.createBufferSource();
            this.windNoise.buffer = windBuffer;
            this.windNoise.loop = true;
            this.windGain = this.ctx.createGain();
            this.windGain.gain.value = 0;
            const windFilter = this.ctx.createBiquadFilter();
            windFilter.type = 'bandpass';
            windFilter.frequency.value = 2000;
            windFilter.Q.value = 0.5;
            this.windNoise.connect(windFilter);
            windFilter.connect(this.windGain);
            this.windGain.connect(this.masterGain);
            this.windNoise.start();
            // ── Stall warning oscillator ──
            this.stallOsc = this.ctx.createOscillator();
            this.stallOsc.type = 'square';
            this.stallOsc.frequency.value = 880;
            this.stallGain = this.ctx.createGain();
            this.stallGain.gain.value = 0;
            this.stallOsc.connect(this.stallGain);
            this.stallGain.connect(this.masterGain);
            this.stallOsc.start();
            this.initialized = true;
        }
        catch (e) {
            console.warn('Audio init failed:', e);
        }
    }
    update(throttle, airspeed, stall, altitude) {
        if (!this.initialized || !this.ctx)
            return;
        // Resume context if suspended
        if (this.ctx.state === 'suspended') {
            this.ctx.resume();
        }
        // ── Engine sound update ──
        if (this.engineOsc && this.engineGain) {
            // Frequency scales with throttle + RPM
            const baseFreq = 40 + throttle * 120;
            this.engineOsc.frequency.setTargetAtTime(baseFreq, this.ctx.currentTime, 0.05);
            // Volume scales with throttle
            const engineVol = 0.02 + throttle * 0.25;
            this.engineGain.gain.setTargetAtTime(engineVol, this.ctx.currentTime, 0.1);
        }
        // ── Wind noise ──
        if (this.windGain) {
            // Wind volume based on airspeed
            const windVol = Math.min(airspeed / 200, 1) * 0.15;
            // Altitude effect: less dense air at high altitude = quieter wind
            const altFactor = Math.max(0.2, 1 - altitude / 10000);
            this.windGain.gain.setTargetAtTime(windVol * altFactor, this.ctx.currentTime, 0.1);
        }
        // ── Stall warning ──
        if (this.stallGain) {
            this.stallGain.gain.setTargetAtTime(stall ? 0.08 : 0, this.ctx.currentTime, 0.05);
        }
    }
    setVolume(v) {
        if (this.masterGain) {
            this.masterGain.gain.value = v;
        }
    }
}
exports.AudioSystem = AudioSystem;
};

// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HUD = void 0;
// ─── HUD Updater ────────────────────────────────────────────
class HUD {
    constructor() {
        this.els = {};
        this.active = false;
    }
    init() {
        const ids = ['hud-alt', 'hud-spd', 'hud-vs', 'hud-g', 'hud-throttle',
            'hud-hdg', 'hud-pitch', 'hud-roll', 'hud-aircraft-name',
            'hud-scenario-name', 'hud-status', 'hud-bottom'];
        for (const id of ids) {
            this.els[id] = document.getElementById(id);
        }
    }
    show() { this.active = true; }
    hide() { this.active = false; }
    update(state, aircraftName, scenarioName, message) {
        if (!this.active)
            return;
        const altFt = Math.round(state.altitude * 3.28084);
        const spdKts = Math.round(state.airspeed * 1.94384);
        const vs = state.vel ? Math.round(state.vel.y * 3.28084) : 0;
        const g = state.gForce.toFixed(1);
        const thr = Math.round(state.throttle * 100);
        const hdg = Math.round(((state.yaw % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI) * 180 / Math.PI) % 360;
        const pitchDeg = Math.round(-state.pitch * 180 / Math.PI);
        const rollDeg = Math.round(-state.roll * 180 / Math.PI);
        const set = (id, val) => {
            const el = this.els[id];
            if (el)
                el.textContent = val;
        };
        set('hud-alt', altFt.toString());
        set('hud-spd', spdKts.toString());
        set('hud-vs', vs.toString());
        set('hud-g', g);
        set('hud-throttle', thr.toString());
        set('hud-hdg', hdg.toString());
        set('hud-pitch', pitchDeg.toString());
        set('hud-roll', rollDeg.toString());
        set('hud-aircraft-name', aircraftName);
        set('hud-scenario-name', scenarioName);
        set('hud-status', message);
        // Color warnings
        const spdEl = this.els['hud-spd'];
        if (spdEl) {
            spdEl.style.color = state.stall ? '#f44336' : state.airspeed > 150 ? '#ff9800' : '#4fc3f7';
        }
        const gEl = this.els['hud-g'];
        if (gEl) {
            gEl.style.color = state.gForce > 6 ? '#f44336' : state.gForce > 4 ? '#ff9800' : '#4fc3f7';
        }
    }
}
exports.HUD = HUD;
};

// ── module: src/scenarios.ts ──
__mods["src/scenarios.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SCENARIOS = void 0;
exports.spawnEnemies = spawnEnemies;
exports.updateEnemyAI = updateEnemyAI;
exports.updateObjectives = updateObjectives;
exports.SCENARIOS = {
    takeoff: {
        id: 'takeoff',
        name: 'Precision Takeoff & Landing',
        description: 'Master the art of takeoff and landing. Manage airspeed, altitude, and throttle carefully. Watch for stalls on approach.',
        icon: '🛫',
        spawnPos: { x: 0, y: 5, z: 0 },
        spawnOrientation: { pitch: 0, roll: 0, yaw: 0 },
        spawnVel: { x: 0, y: 0, z: 0 },
        objectives: [
            { text: '🛫 Apply full throttle and take off from runway', type: 'speed', target: 60, completed: false },
            { text: '📈 Climb to 1000 ft altitude', type: 'altitude', target: 305, completed: false },
            { text: '🔄 Complete a 360° turn at altitude', type: 'reach', target: 0, completed: false },
            { text: '🛬 Land back on runway safely (gear down, < 60 kts)', type: 'land', target: 30, completed: false },
        ],
    },
    dogfight: {
        id: 'dogfight',
        name: 'High-Speed Dogfight',
        description: 'Engage enemy aircraft in aerial combat. Use speed, altitude, and energy management to gain advantage.',
        icon: '⚔️',
        spawnPos: { x: 0, y: 300, z: 0 },
        spawnOrientation: { pitch: -0.1, roll: 0, yaw: 0 },
        spawnVel: { x: 120, y: 0, z: 0 },
        objectives: [
            { text: '🎯 Chase and engage enemy aircraft', type: 'destroy', target: 3, completed: false },
            { text: '💨 Maintain speed above 80 kts during maneuvers', type: 'speed', target: 41, completed: false },
            { text: '🔄 Execute a high-G turn (4G+)', type: 'reach', target: 0, completed: false },
            { text: '🏆 Outmaneuver and defeat all hostiles', type: 'destroy', target: 0, completed: false },
        ],
    },
};
function spawnEnemies(count, playerPos) {
    const enemies = [];
    for (let i = 0; i < count; i++) {
        const angle = (i / count) * Math.PI * 2;
        const dist = 800 + Math.random() * 400;
        const pos = {
            x: playerPos.x + Math.cos(angle) * dist,
            y: playerPos.y + 100 + Math.random() * 200,
            z: playerPos.z + Math.sin(angle) * dist,
        };
        // Simple enemy model (red hostile)
        const group = new THREE.Group();
        const bodyMat = new THREE.MeshStandardMaterial({ color: 0xc62828, metalness: 0.5, roughness: 0.4 });
        const wingMat = new THREE.MeshStandardMaterial({ color: 0xb71c1c, metalness: 0.5, roughness: 0.4 });
        const accentMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
        // Body
        const body = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 0.5, 10, 8), bodyMat);
        body.rotation.z = Math.PI / 2;
        group.add(body);
        // Wings
        for (const side of [-1, 1]) {
            const wing = new THREE.Mesh(new THREE.BoxGeometry(3, 0.15, 5), wingMat);
            wing.position.set(-0.5, 0, side * 3.5);
            group.add(wing);
            // Nav light
            const light = new THREE.Mesh(new THREE.SphereGeometry(0.15, 6, 6), new THREE.MeshBasicMaterial({ color: side === -1 ? 0xff0000 : 0x00ff00 }));
            light.position.set(-0.5, 0.3, side * 6);
            group.add(light);
        }
        // Tail
        const vTail = new THREE.Mesh(new THREE.BoxGeometry(2, 3, 0.15), bodyMat);
        vTail.position.set(-5, 1.5, 0);
        group.add(vTail);
        // Health bar (a small bar above the aircraft)
        const healthBar = new THREE.Mesh(new THREE.BoxGeometry(6, 0.3, 0.3), new THREE.MeshBasicMaterial({ color: 0x00ff00 }));
        healthBar.position.set(0, 3, 0);
        healthBar.name = 'healthBar';
        group.add(healthBar);
        const hHealthBar = new THREE.Mesh(new THREE.BoxGeometry(6, 0.3, 0.3), new THREE.MeshBasicMaterial({ color: 0x333333 }));
        hHealthBar.position.set(0, 3, -0.1);
        group.add(hHealthBar);
        // Laser beam (hidden by default)
        const laserMat = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.8 });
        const laser = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 20, 4), laserMat);
        laser.rotation.z = Math.PI / 2;
        laser.name = 'laser';
        laser.visible = false;
        group.add(laser);
        enemies.push({
            mesh: group,
            pos,
            vel: { x: 60, y: 0, z: 0 },
            pitch: -0.1,
            roll: 0,
            yaw: Math.PI + angle,
            health: 100,
            maxHealth: 100,
            alive: true,
            speed: 50 + Math.random() * 30,
            turnRadius: 200,
            lastShot: 0,
            state: 'patrol',
            patrolCenter: { ...pos },
            targetPos: { x: pos.x, y: pos.y, z: pos.z },
            shootCooldown: 2 + Math.random() * 2,
        });
    }
    return enemies;
}
function updateEnemyAI(enemy, dt, playerPos, time) {
    if (!enemy.alive)
        return;
    const dx = playerPos.x - enemy.pos.x;
    const dz = playerPos.z - enemy.pos.z;
    const distToPlayer = Math.sqrt(dx * dx + dz * dz);
    const dy = playerPos.y - enemy.pos.y;
    // State machine
    if (distToPlayer < 600) {
        enemy.state = distToPlayer < 200 ? 'attack' : 'chase';
    }
    else if (distToPlayer > 1200) {
        enemy.state = 'patrol';
    }
    switch (enemy.state) {
        case 'patrol': {
            // Circle around patrol center
            const t = time * 0.3;
            const r = enemy.turnRadius;
            enemy.targetPos = {
                x: enemy.patrolCenter.x + Math.cos(t) * r,
                y: enemy.patrolCenter.y + Math.sin(t * 0.5) * 30,
                z: enemy.patrolCenter.z + Math.sin(t) * r,
            };
            break;
        }
        case 'chase': {
            // Steer toward player with lag
            enemy.targetPos = {
                x: playerPos.x + dx * 0.3,
                y: playerPos.y + dy * 0.3,
                z: playerPos.z + dz * 0.3,
            };
            break;
        }
        case 'attack': {
            // Try to get behind player, shoot
            enemy.targetPos = {
                x: playerPos.x + dx * 0.5,
                y: playerPos.y + dy * 0.3 + 20,
                z: playerPos.z + dz * 0.5,
            };
            // Shoot at player
            if (time - enemy.lastShot > enemy.shootCooldown) {
                enemy.lastShot = time;
                // Visual laser
                const laser = enemy.mesh.getObjectByName('laser');
                if (laser) {
                    laser.visible = true;
                    laser.position.set(5, 0, 0);
                    // Point toward player
                    const angle = Math.atan2(dz, dx);
                    laser.parent.rotation.y = angle;
                    setTimeout(() => { if (laser)
                        laser.visible = false; }, 150);
                }
            }
            break;
        }
    }
    // Move toward target
    const tdx = enemy.targetPos.x - enemy.pos.x;
    const tdy = enemy.targetPos.y - enemy.pos.y;
    const tdz = enemy.targetPos.z - enemy.pos.z;
    const tDist = Math.sqrt(tdx * tdx + tdy * tdy + tdz * tdz);
    if (tDist > 1) {
        const targetVelX = (tdx / tDist) * enemy.speed;
        const targetVelY = (tdy / tDist) * enemy.speed;
        const targetVelZ = (tdz / tDist) * enemy.speed;
        // Smooth velocity transition
        const lerp = 1 - Math.exp(-dt * 0.8);
        enemy.vel.x += (targetVelX - enemy.vel.x) * lerp;
        enemy.vel.y += (targetVelY - enemy.vel.y) * lerp;
        enemy.vel.z += (targetVelZ - enemy.vel.z) * lerp;
        // Update orientation to face velocity direction
        enemy.yaw = Math.atan2(enemy.vel.z, enemy.vel.x);
        enemy.pitch = Math.atan2(-enemy.vel.y, Math.sqrt(enemy.vel.x * enemy.vel.x + enemy.vel.z * enemy.vel.z));
        enemy.roll = Math.sin(time * 2) * 0.1; // slight banking
    }
    // Apply velocity
    enemy.pos.x += enemy.vel.x * dt;
    enemy.pos.y += enemy.vel.y * dt;
    enemy.pos.z += enemy.vel.z * dt;
    // Keep above terrain
    enemy.pos.y = Math.max(enemy.pos.y, 50);
}
function updateObjectives(state, objectives, prevYaw) {
    const completed = objectives.filter(o => o.completed).length;
    objectives.forEach((obj, idx) => {
        if (obj.completed)
            return;
        switch (obj.type) {
            case 'speed':
                if (state.airspeed >= (obj.target || 0))
                    obj.completed = true;
                break;
            case 'altitude':
                if (state.altitude >= (obj.target || 0))
                    obj.completed = true;
                break;
            case 'land':
                if (state.groundContact && state.airspeed < (obj.target || 30) && state.gearDown)
                    obj.completed = true;
                break;
            case 'reach':
                // 360 turn or G check
                if (idx === 2 && Math.abs(state.gForce) >= 4)
                    obj.completed = true;
                break;
        }
    });
    // Check all completed
    if (completed === objectives.length) {
        // All objectives complete
    }
    return objectives;
}
};

// ── module: src/pitchladder.ts ──
__mods["src/pitchladder.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PitchLadder = void 0;
class PitchLadder {
    constructor() {
        this.active = false;
        this.canvas = document.createElement('canvas');
        this.canvas.width = 400;
        this.canvas.height = 300;
        this.canvas.style.position = 'absolute';
        this.canvas.style.top = '50%';
        this.canvas.style.left = '50%';
        this.canvas.style.transform = 'translate(-50%, -50%)';
        this.canvas.style.pointerEvents = 'none';
        this.canvas.style.zIndex = '11';
        this.ctx = this.canvas.getContext('2d');
    }
    mount() {
        const container = document.getElementById('hud-pitch-ladder');
        if (container) {
            container.appendChild(this.canvas);
            container.style.position = 'absolute';
            container.style.top = '50%';
            container.style.left = '50%';
            container.style.transform = 'translate(-50%, -50%)';
            container.style.width = '400px';
            container.style.height = '300px';
            container.style.pointerEvents = 'none';
        }
    }
    show() { this.active = true; }
    hide() { this.active = false; }
    render(state) {
        if (!this.active)
            return;
        const ctx = this.ctx;
        const w = this.canvas.width;
        const h = this.canvas.height;
        ctx.clearRect(0, 0, w, h);
        // Convert pitch offset to pixels
        const pitchOffsetPx = state.pitch * (180 / Math.PI) * (h / 40);
        const rollAngle = -state.roll;
        ctx.save();
        ctx.translate(w / 2, h / 2);
        ctx.rotate(rollAngle);
        ctx.translate(0, pitchOffsetPx);
        // Draw pitch ladder
        ctx.strokeStyle = 'rgba(79, 195, 247, 0.5)';
        ctx.fillStyle = 'rgba(79, 195, 247, 0.8)';
        ctx.lineWidth = 1;
        ctx.font = '10px monospace';
        ctx.textAlign = 'center';
        const pitchMarks = [-20, -15, -10, -5, 0, 5, 10, 15, 20];
        const pxPerDegree = h / 40;
        for (const deg of pitchMarks) {
            const y = -deg * pxPerDegree;
            if (y < -h / 2 || y > h / 2)
                continue;
            if (deg === 0) {
                // Horizon line (full width)
                ctx.strokeStyle = 'rgba(79, 195, 247, 0.7)';
                ctx.lineWidth = 2;
                ctx.beginPath();
                ctx.moveTo(-w / 2, y);
                ctx.lineTo(w / 2, y);
                ctx.stroke();
            }
            else if (deg % 10 === 0) {
                // Major marks
                ctx.strokeStyle = 'rgba(79, 195, 247, 0.4)';
                ctx.lineWidth = 1;
                ctx.beginPath();
                ctx.moveTo(-60, y);
                ctx.lineTo(60, y);
                ctx.stroke();
                ctx.fillText(deg.toString(), -70, y + 4);
                ctx.fillText(deg.toString(), 70, y + 4);
            }
            else {
                // Minor marks
                ctx.strokeStyle = 'rgba(79, 195, 247, 0.3)';
                ctx.lineWidth = 1;
                ctx.beginPath();
                ctx.moveTo(-30, y);
                ctx.lineTo(30, y);
                ctx.stroke();
            }
        }
        // Roll indicator arc
        ctx.strokeStyle = 'rgba(79, 195, 247, 0.3)';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.arc(0, -pitchOffsetPx, 80, Math.PI, Math.PI * 2);
        ctx.stroke();
        // Roll pointer at top
        ctx.fillStyle = 'rgba(79, 195, 247, 0.8)';
        ctx.beginPath();
        ctx.moveTo(0, -h / 2 + 10);
        ctx.lineTo(-5, -h / 2 + 5);
        ctx.lineTo(5, -h / 2 + 5);
        ctx.fill();
        // Aircraft wings/bolts
        ctx.strokeStyle = 'rgba(255, 200, 50, 0.9)';
        ctx.lineWidth = 2;
        // Left bolt
        ctx.beginPath();
        ctx.moveTo(-80, 0);
        ctx.lineTo(-30, 0);
        ctx.lineTo(-20, 8);
        ctx.stroke();
        // Right bolt
        ctx.beginPath();
        ctx.moveTo(80, 0);
        ctx.lineTo(30, 0);
        ctx.lineTo(20, 8);
        ctx.stroke();
        // Center dot
        ctx.fillStyle = 'rgba(255, 200, 50, 0.9)';
        ctx.beginPath();
        ctx.arc(0, 0, 3, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
    }
}
exports.PitchLadder = PitchLadder;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.6-27B-MTP-GGUF","provider":"brigid","persona":"typescript","sessionId":"6fafba1b-65c0-4807-bf65-11b0890fc6c5","tokensIn":3733012,"tokensOut":43279,"tokensTotal":3776291,"cost":0,"turns":82,"toolCalls":82,"failedToolCalls":3,"timestamp":"2026-08-02T07:55:54.209Z"} -->