← Flight Simulator results

Flight Simulator

thinkingcap qwen3.6 27b · typescript

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 65,174 bytes · SHA-256 161386b10820
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Apex Aero - Flight Simulator</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { overflow: hidden; background: #000; font-family: 'Courier New', monospace; color: #fff; }
#canvas-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }

/* HUD */
#hud { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10; }
.hud-item { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; font-size: 13px; text-shadow: 0 0 8px rgba(0,255,0,.5); }
.hud-label { color: #0f0; font-weight: bold; min-width: 70px; }
.hud-unit { color: #0a0; font-size: 11px; }
#hud-top-left { position: absolute; top: 20px; left: 20px; }
#hud-top-right { position: absolute; top: 20px; right: 20px; text-align: right; }
#hud-bottom-left { position: absolute; bottom: 60px; left: 20px; }
#hud-bottom-right { position: absolute; bottom: 60px; right: 20px; text-align: right; }
#hud-center { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 40px; height: 40px; border: 1px solid rgba(0,255,0,.3); border-radius: 50%; }
#hud-bottom-center { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); text-align: center; }

/* Menu Overlay */
#menu-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 100; background: linear-gradient(135deg, #0a0a2e 0%, #1a1a4e 50%, #0d0d3d 100%); display: flex; align-items: center; justify-content: center; }
#menu-content { text-align: center; max-width: 800px; padding: 40px; }
#menu-content h1 { font-size: 64px; letter-spacing: 20px; color: #fff; text-shadow: 0 0 30px rgba(0,150,255,.8), 0 0 60px rgba(0,100,255,.4); margin-bottom: 5px; }
.subtitle { font-size: 18px; color: #6af; letter-spacing: 8px; margin-bottom: 30px; }
#menu-content h2 { font-size: 20px; color: #6cf; margin: 20px 0 15px; letter-spacing: 4px; }

/* Aircraft Cards */
.aircraft-cards, .scenario-cards { display: flex; gap: 15px; justify-content: center; flex-wrap: wrap; margin-bottom: 20px; }
.ac-card, .sc-card { background: rgba(0,30,60,.7); border: 2px solid #2a4a8a; border-radius: 10px; padding: 15px; width: 220px; cursor: pointer; transition: all .3s; }
.ac-card:hover, .sc-card:hover { border-color: #6cf; background: rgba(0,50,100,.7); transform: translateY(-3px); }
.ac-card.selected, .sc-card.selected { border-color: #0f0; box-shadow: 0 0 20px rgba(0,255,0,.4); }
.ac-card h3, .sc-card h3 { color: #6cf; margin-bottom: 8px; font-size: 16px; }
.ac-card p, .sc-card p { font-size: 11px; color: #aac; line-height: 1.5; text-align: left; }
.stat-bar { height: 4px; background: #234; border-radius: 2px; margin-top: 3px; }
.stat-fill { height: 100%; background: linear-gradient(90deg, #0af, #0f6); border-radius: 2px; }

/* Start Button */
#start-btn { background: linear-gradient(180deg, #0a6, #084); color: #fff; border: none; padding: 15px 50px; font-size: 20px; letter-spacing: 6px; cursor: pointer; border-radius: 5px; margin-top: 20px; transition: all .3s; font-family: 'Courier New', monospace; }
#start-btn:hover { background: linear-gradient(180deg, #0c8, #0a6); box-shadow: 0 0 30px rgba(0,255,100,.5); transform: scale(1.05); }

/* Controls Info */
#controls-info { margin-top: 25px; padding: 15px; background: rgba(0,20,40,.6); border-radius: 8px; font-size: 12px; color: #8ac; line-height: 1.8; }

/* Message Overlay */
#message-overlay { position: fixed; top: 30%; left: 50%; transform: translate(-50%, -50%); z-index: 50; font-size: 24px; color: #f44; text-shadow: 0 0 20px rgba(255,0,0,.8); letter-spacing: 3px; }
</style>
</head>
<body>
<div id="canvas-container"></div><div id="hud" style="display:none"><div id="hud-top-left"><div class="hud-item"><span class="hud-label">AIRSPEED</span><span id="hud-airspeed">0</span> <span class="hud-unit">KTS</span></div><div class="hud-item"><span class="hud-label">ALTITUDE</span><span id="hud-altitude">0</span> <span class="hud-unit">FT</span></div></div><div id="hud-top-right"><div class="hud-item"><span class="hud-label">HEADING</span><span id="hud-heading">000</span> <span class="hud-unit">°</span></div><div class="hud-item"><span class="hud-label">VERT SPEED</span><span id="hud-vspeed">0</span> <span class="hud-unit">FT/M</span></div></div><div id="hud-bottom-left"><div class="hud-item"><span class="hud-label">THROTTLE</span><span id="hud-throttle">0</span><span class="hud-unit">%</span></div><div class="hud-item"><span class="hud-label">G-FORCE</span><span id="hud-gforce">1.0</span> <span class="hud-unit">G</span></div></div><div id="hud-bottom-right"><div class="hud-item"><span class="hud-label">PITCH</span><span id="hud-pitch">0.0</span> <span class="hud-unit">°</span></div><div class="hud-item"><span class="hud-label">ROLL</span><span id="hud-roll">0.0</span> <span class="hud-unit">°</span></div></div><div id="hud-center"></div><div id="hud-bottom-center"><div class="hud-item"><span class="hud-label">AOA</span><span id="hud-aoa">0.0</span> <span class="hud-unit">°</span></div><div class="hud-item"><span class="hud-label">DRAG</span><span id="hud-drag">0</span> <span class="hud-unit">N</span></div></div></div><div id="menu-overlay"><div id="menu-content"><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">LAUNCH SIMULATION</button><div id="controls-info"><h3>Controls</h3><p><b>W/S</b> Pitch Down/Up &nbsp; <b>A/D</b> Roll Left/Right &nbsp; <b>Q/E</b> Yaw Left/Right</p><p><b>Shift/Ctrl</b> Throttle Up/Down &nbsp; <b>T</b> Toggle Trim &nbsp; <b>R</b> Reset Position</p></div></div></div><div id="message-overlay" style="display:none"><span id="message-text"></span></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./aircraft":"src/aircraft.ts","./game":"src/game.ts","./renderer":"src/renderer.ts","./input":"src/input.ts","./audio":"src/audio.ts"},"src/aircraft.ts":{"./types":"src/types.ts"},"src/physics.ts":{"./types":"src/types.ts"},"src/input.ts":{"./types":"src/types.ts"},"src/models.ts":{"./types":"src/types.ts"},"src/renderer.ts":{"./types":"src/types.ts","./terrain":"src/terrain.ts","./models":"src/models.ts"},"src/game.ts":{"./types":"src/types.ts","./physics":"src/physics.ts","./renderer":"src/renderer.ts","./input":"src/input.ts","./audio":"src/audio.ts","./models":"src/models.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 aircraft_1 = require("./aircraft");
const game_1 = require("./game");
const renderer_1 = require("./renderer");
const input_1 = require("./input");
const audio_1 = require("./audio");
let renderer;
let input;
let audio;
let gameManager;
let animFrameId = 0;
let selectedAircraftIndex = 0;
let selectedScenarioIndex = 0;
function init() {
    // Build UI
    buildMenu();
    // Initialize systems
    renderer = new renderer_1.Renderer();
    input = new input_1.InputManager();
    audio = new audio_1.AudioSystem();
    gameManager = new game_1.GameManager(renderer, input, audio);
    // Setup terrain (always)
    renderer.setupTerrain();
    // Handle window resize
    window.addEventListener('resize', () => {
        renderer.resize();
    });
    // Start button handler
    document.getElementById('start-btn').addEventListener('click', startGame);
    // Reset key handler
    window.addEventListener('keydown', (e) => {
        if (e.key.toLowerCase() === 'r' && gameManager.running && !gameManager.gameOver) {
            resetPosition();
        }
    });
}
function buildMenu() {
    const aircraftCards = document.getElementById('aircraft-cards');
    for (let i = 0; i < aircraft_1.AIRCRAFT_LIST.length; i++) {
        const ac = aircraft_1.AIRCRAFT_LIST[i];
        const card = document.createElement('div');
        card.className = 'ac-card' + (i === selectedAircraftIndex ? ' selected' : '');
        card.innerHTML = `
      <h3>${ac.name}</h3>
      <p>${ac.description}</p>
      <p style="margin-top:8px">
        Speed: ${bar(ac.maxThrust / ac.mass, 15)}<br/>
        Agility: ${bar(ac.rollAuthority * 20, 100)}<br/>
        Stability: ${bar((1 - ac.cd0) * 100, 100)}
      </p>
    `;
        card.addEventListener('click', () => {
            selectedAircraftIndex = i;
            updateSelection();
        });
        aircraftCards.appendChild(card);
    }
    const scenarioCards = document.getElementById('scenario-cards');
    for (let i = 0; i < game_1.SCENARIOS.length; i++) {
        const sc = game_1.SCENARIOS[i];
        const card = document.createElement('div');
        card.className = 'sc-card' + (i === selectedScenarioIndex ? ' selected' : '');
        card.innerHTML = `
      <h3>${sc.name}</h3>
      <p>${sc.description}</p>
    `;
        card.addEventListener('click', () => {
            selectedScenarioIndex = i;
            updateSelection();
        });
        scenarioCards.appendChild(card);
    }
}
function bar(value, max) {
    const pct = Math.min(100, (value / max) * 100);
    return `<div class="stat-bar"><div class="stat-fill" style="width:${pct}%"></div></div>`;
}
function updateSelection() {
    const acCards = document.querySelectorAll('.ac-card');
    acCards.forEach((c, i) => c.classList.toggle('selected', i === selectedAircraftIndex));
    const scCards = document.querySelectorAll('.sc-card');
    scCards.forEach((c, i) => c.classList.toggle('selected', i === selectedScenarioIndex));
}
function startGame() {
    // Hide menu
    document.getElementById('menu-overlay').style.display = 'none';
    // Show HUD
    document.getElementById('hud').style.display = 'block';
    // Init audio on user gesture
    audio.init();
    audio.resume();
    // Start game
    const profile = aircraft_1.AIRCRAFT_LIST[selectedAircraftIndex];
    const scenario = game_1.SCENARIOS[selectedScenarioIndex];
    gameManager.start(profile, scenario);
    if (scenario.type === 'takeoff') {
        renderer.setupRunway();
    }
    // Start render loop
    lastTime = performance.now();
    gameLoop(performance.now());
}
let lastTime;
function gameLoop(timestamp) {
    const dt = (timestamp - lastTime) / 1000;
    lastTime = timestamp;
    if (gameManager.running && !gameManager.gameOver) {
        gameManager.update();
        gameManager.updateHUD();
    }
    else if (gameManager.gameOver) {
        // Show end screen after a delay
        setTimeout(() => {
            showEndScreen();
        }, 3000);
        gameManager.running = false;
    }
    animFrameId = requestAnimationFrame(gameLoop);
}
function resetPosition() {
    if (gameManager.scenario.type === 'takeoff') {
        gameManager.state.position.set(0, 12, -800);
        gameManager.state.velocity.set(0, 0, 0);
        gameManager.state.orientation.setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI);
        gameManager.state.angularVelocity.set(0, 0, 0);
        gameManager.state.throttle = 0.25;
    }
    else {
        gameManager.state.position.set(0, gameManager.scenario.initialAltitude, 0);
        gameManager.state.velocity.set(80, 5, 0);
        gameManager.state.orientation.identity();
        gameManager.state.angularVelocity.set(0, 0, 0);
        gameManager.state.throttle = 0.6;
    }
}
function showEndScreen() {
    const overlay = document.getElementById('menu-overlay');
    if (overlay) {
        overlay.style.display = 'flex';
        const content = document.getElementById('menu-content');
        content.innerHTML = `
      <h1>${gameManager.message}</h1>
      <p class="subtitle" style="margin-top:20px">Flight Time: ${Math.round((performance.now() - lastTime) / 1000)}s</p>
      <button id="restart-btn" style="background:linear-gradient(180deg,#0a6,#084);color:#fff;border:none;padding:15px 50px;font-size:20px;letter-spacing:6px;cursor:pointer;border-radius:5px;margin-top:30px;font-family:'Courier New',monospace">FLY AGAIN</button>
    `;
        document.getElementById('restart-btn').addEventListener('click', () => {
            location.reload();
        });
    }
    // Hide HUD
    document.getElementById('hud').style.display = 'none';
}
// Start initialization
init();
};

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

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
};

// ── module: src/aircraft.ts ──
__mods["src/aircraft.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AIRCRAFT_LIST = exports.mig21 = exports.a10 = exports.f16 = void 0;
exports.getAircraftById = getAircraftById;
// F-16 Fighting Falcon - High performance fighter
exports.f16 = {
    name: 'F-16 Fighting Falcon',
    description: 'High-thrust multirole fighter. Excellent maneuverability and acceleration.',
    color: 0x5a7a4a,
    accentColor: 0xffaa00,
    mass: 9200,
    wingArea: 27.87,
    referenceLength: 15.0,
    cl0: 0.0,
    cla: 6.5,
    cd0: 0.015,
    k: 0.04,
    alphaStall: Math.PI / 9, // ~20 degrees
    clMax: 2.0,
    maxThrust: 128000,
    minThrottle: 0.15,
    engineResponseRate: 3.0,
    inertiaX: 4500,
    inertiaY: 65000,
    inertiaZ: 68000,
    pitchAuthority: 1.2,
    rollAuthority: 1.8,
    yawAuthority: 0.6,
    modelScale: 1.0,
};
// A-10 Thunderbolt II - Close air support gunship
exports.a10 = {
    name: 'A-10 Thunderbolt II',
    description: 'Heavy gunship. Slow but extremely stable and forgiving at low speeds.',
    color: 0x4a5a3a,
    accentColor: 0xff6600,
    mass: 10300,
    wingArea: 47.5,
    referenceLength: 16.2,
    cl0: 0.0,
    cla: 5.8,
    cd0: 0.025,
    k: 0.035,
    alphaStall: Math.PI / 7, // ~25 degrees
    clMax: 2.4,
    maxThrust: 101000,
    minThrottle: 0.2,
    engineResponseRate: 2.0,
    inertiaX: 6000,
    inertiaY: 85000,
    inertiaZ: 90000,
    pitchAuthority: 0.8,
    rollAuthority: 1.0,
    yawAuthority: 0.4,
    modelScale: 1.2,
};
// MiG-21 Fishbed - Lightweight interceptor
exports.mig21 = {
    name: 'MiG-21 Fishbed',
    description: 'Lightweight interceptor. Fast and agile but unforgiving at low speeds.',
    color: 0x6a7a8a,
    accentColor: 0xff3333,
    mass: 5900,
    wingArea: 23.1,
    referenceLength: 14.5,
    cl0: 0.0,
    cla: 7.0,
    cd0: 0.018,
    k: 0.05,
    alphaStall: Math.PI / 12, // ~15 degrees
    clMax: 1.6,
    maxThrust: 69700,
    minThrottle: 0.25,
    engineResponseRate: 4.0,
    inertiaX: 2800,
    inertiaY: 35000,
    inertiaZ: 37000,
    pitchAuthority: 1.5,
    rollAuthority: 2.2,
    yawAuthority: 0.8,
    modelScale: 0.9,
};
exports.AIRCRAFT_LIST = [exports.f16, exports.a10, exports.mig21];
function getAircraftById(id) {
    return exports.AIRCRAFT_LIST[id] || exports.f16;
}
};

// ── module: src/physics.ts ──
__mods["src/physics.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAtmosphere = getAtmosphere;
exports.updatePhysics = updatePhysics;
exports.updateThrottle = updateThrottle;
exports.createInitialState = createInitialState;
const GRAVITY = 9.81; // m/s²
const SEA_LEVEL_DENSITY = 1.225; // kg/m³
const SCALE_HEIGHT = 8500; // meters (atmospheric scale height)
// Compute atmospheric properties at given altitude
function getAtmosphere(altitude) {
    const h = Math.max(0, altitude);
    const density = SEA_LEVEL_DENSITY * Math.exp(-h / SCALE_HEIGHT);
    const temperature = 288.15 - 6.5e-3 * h; // ISA model (troposphere)
    const pressure = 101325 * Math.exp(-h / SCALE_HEIGHT);
    return { density, temperature: Math.max(temperature, 216.65), pressure };
}
// Compute lift force in body frame (positive Z is up in aircraft frame)
function computeLift(profile, state, atm) {
    const vSq = state.velocity.lengthSq();
    if (vSq < 0.01)
        return 0;
    let alpha = state.angleOfAttack;
    // Clamp angle of attack for lift calculation
    let cl = profile.cl0 + profile.cla * alpha;
    if (Math.abs(alpha) > profile.alphaStall) {
        // Post-stall: lift drops off
        const stallFactor = Math.cos((alpha - Math.sign(alpha) * profile.alphaStall) * 2);
        cl *= Math.max(0.3, stallFactor);
    }
    cl = Math.max(-profile.clMax * 0.5, Math.min(profile.clMax, cl));
    return 0.5 * atm.density * vSq * profile.wingArea * cl;
}
// Compute drag force (opposite to velocity direction)
function computeDrag(profile, state, atm) {
    const vSq = state.velocity.lengthSq();
    if (vSq < 0.01)
        return 0;
    const v = Math.sqrt(vSq);
    // Profile drag (parasite drag)
    const cdProfile = profile.cd0 * (1 + 20 * Math.pow(state.angleOfAttack, 2));
    // Induced drag (proportional to lift squared)
    let alpha = state.angleOfAttack;
    let cl = profile.cl0 + profile.cla * alpha;
    if (Math.abs(alpha) > profile.alphaStall) {
        const stallFactor = Math.cos((alpha - Math.sign(alpha) * profile.alphaStall) * 2);
        cl *= Math.max(0.3, stallFactor);
    }
    cl = Math.max(-profile.clMax * 0.5, Math.min(profile.clMax, cl));
    const cdInduced = profile.k * cl * cl;
    const totalCd = cdProfile + cdInduced;
    return 0.5 * atm.density * vSq * profile.wingArea * totalCd;
}
// Compute thrust force along aircraft forward axis
function computeThrust(profile, state) {
    // Thrust decreases slightly with altitude (less air for combustion)
    const altFactor = Math.max(0.5, Math.exp(-state.altitude / (SCALE_HEIGHT * 2)));
    return profile.maxThrust * state.throttle * altFactor;
}
// Update aircraft physics for one timestep
function updatePhysics(profile, state, controls, dt, terrainAltitude) {
    // Clamp dt to prevent instability
    dt = Math.min(dt, 0.05);
    const atm = getAtmosphere(state.altitude);
    // --- Thrust ---
    const thrustMag = computeThrust(profile, state);
    const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(state.orientation);
    const thrustForce = forward.multiplyScalar(thrustMag);
    // --- Gravity ---
    const gravityForce = new THREE.Vector3(0, -GRAVITY * profile.mass, 0);
    // --- Lift (perpendicular to velocity in the plane of symmetry) ---
    const liftMag = computeLift(profile, state, atm);
    let liftDir;
    if (state.velocity.lengthSq() < 0.1) {
        liftDir = new THREE.Vector3(0, 1, 0); // straight up when stationary
    }
    else {
        const velNorm = state.velocity.clone().normalize();
        const bodyUp = new THREE.Vector3(0, 1, 0).applyQuaternion(state.orientation);
        // Lift is perpendicular to velocity, in the plane of symmetry
        liftDir = bodyUp.clone().sub(velNorm.clone().multiplyScalar(bodyUp.dot(velNorm))).normalize();
        if (liftDir.lengthSq() < 0.001)
            liftDir.set(0, 1, 0);
    }
    const liftForce = liftDir.multiplyScalar(liftMag);
    // --- Drag (opposite to velocity) ---
    const dragMag = computeDrag(profile, state, atm);
    let dragForce;
    if (state.velocity.lengthSq() < 0.1) {
        dragForce = new THREE.Vector3(0, 0, 0);
    }
    else {
        dragForce = state.velocity.clone().normalize().multiplyScalar(-dragMag);
    }
    // --- Total force ---
    const totalForce = thrustForce.add(liftForce).add(dragForce).add(gravityForce);
    // --- Update velocity (F = ma) ---
    const acceleration = totalForce.multiplyScalar(1 / profile.mass);
    state.velocity.add(acceleration.multiplyScalar(dt));
    // --- Ground collision ---
    if (state.position.y <= terrainAltitude + 2) {
        state.position.y = terrainAltitude + 2;
        if (state.velocity.y < 0) {
            const impactSpeed = Math.abs(state.velocity.y);
            if (impactSpeed > 15) {
                // Hard crash
                state.velocity.set(0, 0, 0);
                return { dragForce: dragMag };
            }
            // Bounce/dampen
            state.velocity.y *= -0.3;
        }
        state.onGround = true;
        // Ground friction
        const groundFriction = new THREE.Vector3(-state.velocity.x * 2, 0, -state.velocity.z * 2);
        state.velocity.add(groundFriction.multiplyScalar(dt));
    }
    else {
        state.onGround = false;
    }
    // --- Update position ---
    state.position.add(state.velocity.clone().multiplyScalar(dt));
    // --- Angular dynamics (control surface response) ---
    const airspeedFactor = Math.min(1, state.airspeed / 50);
    const speedDamp = Math.max(0.2, airspeedFactor);
    // Target angular rates from controls
    const targetRollRate = controls.roll * profile.rollAuthority * speedDamp;
    const targetPitchRate = -controls.pitch * profile.pitchAuthority * speedDamp;
    const targetYawRate = controls.yaw * profile.yawAuthority * speedDamp;
    // Smooth angular acceleration (inertia)
    const rollAccel = (targetRollRate - state.angularVelocity.x) / Math.max(0.1, profile.inertiaX / 5000);
    const pitchAccel = (targetPitchRate - state.angularVelocity.y) / Math.max(0.1, profile.inertiaY / 5000);
    const yawAccel = (targetYawRate - state.angularVelocity.z) / Math.max(0.1, profile.inertiaZ / 5000);
    // Damping on angular velocity
    const damping = 0.92;
    state.angularVelocity.x = state.angularVelocity.x * damping + rollAccel * dt;
    state.angularVelocity.y = state.angularVelocity.y * damping + pitchAccel * dt;
    state.angularVelocity.z = state.angularVelocity.z * damping + yawAccel * dt;
    // --- Update orientation from angular velocity ---
    const angVelMag = state.angularVelocity.length();
    if (angVelMag > 0.001) {
        const axis = state.angularVelocity.clone().normalize();
        const angle = angVelMag * dt;
        const deltaQuat = new THREE.Quaternion().setFromAxisAngle(axis, angle);
        state.orientation.multiply(deltaQuat);
        state.orientation.normalize();
    }
    // --- Compute derived quantities ---
    const velNorm = state.velocity.clone().normalize();
    const bodyForward = new THREE.Vector3(0, 0, -1).applyQuaternion(state.orientation);
    // Angle of attack: angle between velocity vector and aircraft plane of symmetry
    const cosAoA = Math.max(-1, Math.min(1, velNorm.dot(bodyForward)));
    state.angleOfAttack = Math.acos(cosAoA) * (velNorm.y > bodyForward.y ? -1 : 1);
    // Sideslip angle
    const bodyRight = new THREE.Vector3(1, 0, 0).applyQuaternion(state.orientation);
    state.sideslipAngle = Math.asin(Math.max(-1, Math.min(1, velNorm.dot(bodyRight))));
    // Airspeed
    state.airspeed = state.velocity.length();
    // G-force (total acceleration / gravity)
    const totalAccelMag = acceleration.length();
    state.gForce = 1 + totalAccelMag / GRAVITY;
    // Stall detection
    state.isStalled = Math.abs(state.angleOfAttack) > profile.alphaStall && state.airspeed < 60;
    return { dragForce: dragMag };
}
// Update throttle based on controls
function updateThrottle(state, controls, dt, maxRate) {
    if (controls.throttleUp) {
        state.throttle = Math.min(1.0, state.throttle + maxRate * dt);
    }
    else if (controls.throttleDown) {
        state.throttle = Math.max(0.05, state.throttle - maxRate * dt);
    }
}
// Initialize aircraft state
function createInitialState(position) {
    return {
        position: position.clone(),
        velocity: new THREE.Vector3(0, 0, 0),
        orientation: new THREE.Quaternion(),
        angularVelocity: new THREE.Vector3(0, 0, 0),
        throttle: 0.25,
        angleOfAttack: 0,
        sideslipAngle: 0,
        airspeed: 0,
        altitude: position.y,
        gForce: 1,
        isStalled: false,
        onGround: true,
    };
}
};

// ── module: src/terrain.ts ──
__mods["src/terrain.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TerrainGenerator = void 0;
exports.createTerrainMesh = createTerrainMesh;
exports.createWater = createWater;
exports.createRunway = createRunway;
// Simplex-like noise for terrain generation
class Noise {
    constructor(seed = 42) {
        this.perm = new Array(512);
        const p = new Array(256);
        for (let i = 0; i < 256; i++)
            p[i] = i;
        // Shuffle with seed
        let s = seed;
        for (let i = 255; i > 0; i--) {
            s = (s * 16807 + 0) % 2147483647;
            const j = s % (i + 1);
            [p[i], p[j]] = [p[j], p[i]];
        }
        for (let i = 0; i < 512; i++)
            this.perm[i] = p[i & 255];
        this.grad3 = [
            [1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0],
            [1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1],
            [0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1]
        ];
    }
    dot3(g, x, y) {
        return g[0] * x + g[1] * y;
    }
    noise2D(x, y) {
        const F2 = 0.5 * (Math.sqrt(3) - 1);
        const G2 = (3 - Math.sqrt(3)) / 6;
        const s = (x + y) * F2;
        const i = Math.floor(x + s);
        const j = Math.floor(y + s);
        const t = (i + j) * G2;
        const X0 = i - t, Y0 = j - t;
        const x0 = x - X0, y0 = y - Y0;
        let i1, j1;
        if (x0 > y0) {
            i1 = 1;
            j1 = 0;
        }
        else {
            i1 = 0;
            j1 = 1;
        }
        const x1 = x0 - i1 + G2, y1 = y0 - j1 + G2;
        const x2 = x0 - 1 + 2 * G2, y2 = y0 - 1 + 2 * G2;
        const ii = i & 255, jj = j & 255;
        const gi0 = this.perm[ii + this.perm[jj]] % 12;
        const gi1 = this.perm[ii + i1 + this.perm[jj + j1]] % 12;
        const gi2 = this.perm[ii + 1 + this.perm[jj + 1]] % 12;
        let n0, n1, n2;
        let t0 = 0.5 - x0 * x0 - y0 * y0;
        n0 = t0 < 0 ? 0 : (t0 *= t0, t0 * t0 * this.dot3(this.grad3[gi0], x0, y0));
        let t1 = 0.5 - x1 * x1 - y1 * y1;
        n1 = t1 < 0 ? 0 : (t1 *= t1, t1 * t1 * this.dot3(this.grad3[gi1], x1, y1));
        let t2 = 0.5 - x2 * x2 - y2 * y2;
        n2 = t2 < 0 ? 0 : (t2 *= t2, t2 * t2 * this.dot3(this.grad3[gi2], x2, y2));
        return 70 * (n0 + n1 + n2);
    }
    fbm(x, y, octaves = 6) {
        let val = 0, amp = 1, freq = 1, max = 0;
        for (let i = 0; i < octaves; i++) {
            val += this.noise2D(x * freq, y * freq) * amp;
            max += amp;
            amp *= 0.5;
            freq *= 2;
        }
        return val / max;
    }
}
class TerrainGenerator {
    constructor(seed = 42) {
        this.waterLevel = 10;
        this.noise = new Noise(seed);
    }
    getHeight(x, z) {
        const scale = 0.003;
        let h = this.noise.fbm(x * scale, z * scale, 6);
        // Normalize to 0-1 range roughly
        h = (h + 1) / 2;
        // Add ridges
        let ridge = Math.abs(this.noise.fbm(x * scale * 2 + 5.3, z * scale * 2 + 3.7, 4));
        ridge = (1 - ridge) ** 2;
        h = h * 0.6 + ridge * 0.4;
        // Scale to world units
        const baseHeight = h * 500;
        // Flatten near origin for runway area
        const distFromOrigin = Math.sqrt(x * x + z * z);
        if (distFromOrigin < 300) {
            const flatFactor = distFromOrigin / 300;
            return baseHeight * flatFactor + this.waterLevel;
        }
        // Add water level offset
        return Math.max(this.waterLevel, baseHeight);
    }
    isWater(x, z) {
        return this.getHeight(x, z) <= this.waterLevel + 1;
    }
    getTerrainColor(h, x, z) {
        if (h < this.waterLevel + 2)
            return [0.15, 0.3, 0.5]; // water
        if (h < this.waterLevel + 5)
            return [0.76, 0.7, 0.5]; // sand
        const slope = Math.abs(this.noise.fbm(x * 0.01, z * 0.01, 3));
        if (h > 350) {
            // Snow caps
            return [0.9, 0.92, 0.95];
        }
        if (h > 250) {
            // Rocky
            const r = 0.4 + slope * 0.1;
            return [r, r - 0.05, r - 0.1];
        }
        if (slope > 0.3) {
            // Rocky slopes
            return [0.35, 0.32, 0.28];
        }
        // Grass with variation
        const green = 0.3 + h / 1000;
        return [0.2 + slope * 0.1, Math.min(0.6, green), 0.15];
    }
}
exports.TerrainGenerator = TerrainGenerator;
// Build terrain mesh using a grid of quads with vertex colors
function createTerrainMesh(generator, size = 8000, segments = 256) {
    const geometry = new THREE.PlaneGeometry(size, size, segments, segments);
    geometry.rotateX(-Math.PI / 2);
    const positions = geometry.attributes.position;
    if (!positions)
        return null;
    const colors = new Float32Array(positions.count * 3);
    const vertexCount = positions.count;
    for (let i = 0; i < vertexCount; i++) {
        const x = positions.getX(i);
        const z = positions.getZ(i);
        const h = generator.getHeight(x, z);
        positions.setY(i, h);
        const [r, g, b] = generator.getTerrainColor(h, x, z);
        colors[i * 3] = r;
        colors[i * 3 + 1] = g;
        colors[i * 3 + 2] = b;
    }
    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
    geometry.computeVertexNormals();
    const material = new THREE.MeshStandardMaterial({
        vertexColors: true,
        roughness: 0.85,
        metalness: 0.1,
        flatShading: false,
    });
    return new THREE.Mesh(geometry, material);
}
// Create water plane
function createWater(size = 8000) {
    const geometry = new THREE.PlaneGeometry(size, size);
    geometry.rotateX(-Math.PI / 2);
    const material = new THREE.MeshStandardMaterial({
        color: 0x1a5276,
        transparent: true,
        opacity: 0.7,
        roughness: 0.1,
        metalness: 0.3,
    });
    const water = new THREE.Mesh(geometry, material);
    water.position.y = 10;
    return water;
}
// Create runway for takeoff/landing scenario
function createRunway() {
    const group = new THREE.Group();
    // Runway surface
    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.y = 11;
    group.add(runway);
    // Runway markings (center line)
    for (let i = -950; i < 950; i += 40) {
        const markGeo = new THREE.PlaneGeometry(2, 20);
        markGeo.rotateX(-Math.PI / 2);
        const markMat = new THREE.MeshStandardMaterial({ color: 0xffffff });
        const mark = new THREE.Mesh(markGeo, markMat);
        mark.position.set(0, 11.5, i);
        group.add(mark);
    }
    // Edge lights
    for (let z = -980; z <= 980; z += 20) {
        for (const x of [-30, 30]) {
            const lightGeo = new THREE.SphereGeometry(0.5, 6, 6);
            const lightMat = new THREE.MeshStandardMaterial({
                color: 0x00ff88,
                emissive: 0x00ff88,
                emissiveIntensity: 2,
            });
            const light = new THREE.Mesh(lightGeo, lightMat);
            light.position.set(x, 11.5, z);
            group.add(light);
        }
    }
    return group;
}
};

// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputManager = void 0;
class InputManager {
    constructor() {
        this.keys = new Set();
        this.controls = { pitch: 0, roll: 0, yaw: 0, throttleUp: false, throttleDown: false };
        window.addEventListener('keydown', (e) => this.onKeyDown(e));
        window.addEventListener('keyup', (e) => this.onKeyUp(e));
    }
    onKeyDown(e) {
        const key = e.key.toLowerCase();
        this.keys.add(key);
        if (['w', 's', 'a', 'd', 'q', 'e', 'shift', 'control'].includes(key)) {
            e.preventDefault();
        }
    }
    onKeyUp(e) {
        this.keys.delete(e.key.toLowerCase());
    }
    update() {
        const sensitivity = 1.0;
        // Pitch (W/S)
        let pitch = 0;
        if (this.keys.has('w'))
            pitch += sensitivity;
        if (this.keys.has('s'))
            pitch -= sensitivity;
        this.controls.pitch = Math.max(-1, Math.min(1, pitch));
        // Roll (A/D)
        let roll = 0;
        if (this.keys.has('a'))
            roll += sensitivity;
        if (this.keys.has('d'))
            roll -= sensitivity;
        this.controls.roll = Math.max(-1, Math.min(1, roll));
        // Yaw (Q/E)
        let yaw = 0;
        if (this.keys.has('q'))
            yaw += sensitivity;
        if (this.keys.has('e'))
            yaw -= sensitivity;
        this.controls.yaw = Math.max(-1, Math.min(1, yaw));
        // Throttle (Shift/Ctrl)
        this.controls.throttleUp = this.keys.has('shift');
        this.controls.throttleDown = this.keys.has('control') || this.keys.has('ctrl');
    }
}
exports.InputManager = InputManager;
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioSystem = void 0;
class AudioSystem {
    constructor() {
        this.ctx = null;
        this.masterGain = null;
        this.engineOsc1 = null;
        this.engineOsc2 = null;
        this.engineGain = null;
        this.windNoiseSource = null;
        this.windGain = null;
        this.windFilter = 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: two oscillators for richness
            this.engineOsc1 = this.ctx.createOscillator();
            this.engineOsc1.type = 'sawtooth';
            this.engineOsc1.frequency.value = 60;
            this.engineOsc2 = this.ctx.createOscillator();
            this.engineOsc2.type = 'square';
            this.engineOsc2.frequency.value = 30;
            this.engineGain = this.ctx.createGain();
            this.engineGain.gain.value = 0.15;
            const engineFilter = this.ctx.createBiquadFilter();
            engineFilter.type = 'lowpass';
            engineFilter.frequency.value = 400;
            engineFilter.Q.value = 2;
            this.engineOsc1.connect(engineFilter);
            this.engineOsc2.connect(engineFilter);
            engineFilter.connect(this.engineGain);
            this.engineGain.connect(this.masterGain);
            this.engineOsc1.start();
            this.engineOsc2.start();
            // Wind noise (filtered white noise)
            const bufferSize = 2 * this.ctx.sampleRate;
            const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
            const data = buffer.getChannelData(0);
            for (let i = 0; i < bufferSize; i++) {
                data[i] = Math.random() * 2 - 1;
            }
            this.windNoiseSource = this.ctx.createBufferSource();
            this.windNoiseSource.buffer = buffer;
            this.windNoiseSource.loop = true;
            this.windFilter = this.ctx.createBiquadFilter();
            this.windFilter.type = 'bandpass';
            this.windFilter.frequency.value = 800;
            this.windFilter.Q.value = 0.5;
            this.windGain = this.ctx.createGain();
            this.windGain.gain.value = 0;
            this.windNoiseSource.connect(this.windFilter);
            this.windFilter.connect(this.windGain);
            this.windGain.connect(this.masterGain);
            this.windNoiseSource.start();
            this.initialized = true;
        }
        catch (e) {
            console.warn('Audio init failed:', e);
        }
    }
    update(throttle, airspeed, altitude, isStalled) {
        if (!this.initialized || !this.ctx)
            return;
        // Engine pitch and volume based on throttle
        const baseFreq = 40 + throttle * 120;
        const altFactor = Math.max(0.7, Math.exp(-altitude / 8500));
        if (this.engineOsc1) {
            this.engineOsc1.frequency.linearRampToValueAtTime(baseFreq * altFactor, this.ctx.currentTime + 0.05);
        }
        if (this.engineOsc2) {
            this.engineOsc2.frequency.linearRampToValueAtTime(baseFreq * 0.5 * altFactor, this.ctx.currentTime + 0.05);
        }
        if (this.engineGain) {
            const vol = 0.05 + throttle * 0.3;
            this.engineGain.gain.linearRampToValueAtTime(vol, this.ctx.currentTime + 0.05);
        }
        // Wind noise based on airspeed and altitude
        if (this.windGain) {
            const windVol = Math.min(0.4, (airspeed / 200) * (1 + altitude / 5000));
            this.windGain.gain.linearRampToValueAtTime(windVol, this.ctx.currentTime + 0.05);
        }
        if (this.windFilter) {
            const windFreq = 400 + airspeed * 8;
            this.windFilter.frequency.linearRampToValueAtTime(Math.min(3000, windFreq), this.ctx.currentTime + 0.05);
        }
        // Stall warning: add a pulsing tone
        if (isStalled && this.engineOsc1) {
            const t = this.ctx.currentTime;
            this.engineOsc1.frequency.setValueAtTime(800, t);
            this.engineOsc1.frequency.linearRampToValueAtTime(baseFreq * altFactor, t + 0.3);
        }
    }
    resume() {
        if (this.ctx && this.ctx.state === 'suspended') {
            this.ctx.resume();
        }
    }
}
exports.AudioSystem = AudioSystem;
};

// ── module: src/models.ts ──
__mods["src/models.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAircraftModel = buildAircraftModel;
exports.buildEnemyModel = buildEnemyModel;
exports.buildWaypointMarker = buildWaypointMarker;
// Build a 3D model of an aircraft from primitives
function buildAircraftModel(profile) {
    const group = new THREE.Group();
    const s = profile.modelScale;
    const bodyMat = new THREE.MeshStandardMaterial({
        color: profile.color,
        roughness: 0.4,
        metalness: 0.6,
    });
    const accentMat = new THREE.MeshStandardMaterial({
        color: profile.accentColor,
        emissive: profile.accentColor,
        emissiveIntensity: 0.3,
        roughness: 0.3,
        metalness: 0.5,
    });
    const darkMat = new THREE.MeshStandardMaterial({
        color: 0x222222,
        roughness: 0.7,
        metalness: 0.8,
    });
    // Fuselage (main body) - elongated cylinder
    const fuselageGeo = new THREE.CylinderGeometry(0.3 * s, 0.25 * s, 4 * s, 8);
    const fuselage = new THREE.Mesh(fuselageGeo, bodyMat);
    fuselage.rotation.x = Math.PI / 2;
    group.add(fuselage);
    // Nose cone
    const noseGeo = new THREE.ConeGeometry(0.3 * s, 1.5 * s, 8);
    const nose = new THREE.Mesh(noseGeo, bodyMat);
    nose.rotation.x = -Math.PI / 2;
    nose.position.z = -2.75 * s;
    group.add(nose);
    // Cockpit canopy (glass)
    const cockpitGeo = new THREE.SphereGeometry(0.35 * s, 8, 6);
    const cockpitMat = new THREE.MeshStandardMaterial({
        color: 0x44aaff,
        roughness: 0.1,
        metalness: 0.9,
        transparent: true,
        opacity: 0.6,
    });
    const cockpit = new THREE.Mesh(cockpitGeo, cockpitMat);
    cockpit.position.set(0, 0.25 * s, -0.8 * s);
    group.add(cockpit);
    // Wings (flat boxes)
    const wingSpan = profile.wingArea > 30 ? 10 : 7;
    const wingGeo = new THREE.BoxGeometry(wingSpan * s, 0.08 * s, 1.5 * s);
    const wings = new THREE.Mesh(wingGeo, bodyMat);
    wings.position.set(0, -0.05 * s, 0.2 * s);
    group.add(wings);
    // Wing tips (accent)
    for (const side of [-1, 1]) {
        const tipGeo = new THREE.BoxGeometry(0.3 * s, 0.1 * s, 0.5 * s);
        const tip = new THREE.Mesh(tipGeo, accentMat);
        tip.position.set(side * wingSpan * s / 2, -0.05 * s, 0.2 * s);
        group.add(tip);
    }
    // Horizontal stabilizer (tail wings)
    const hStabGeo = new THREE.BoxGeometry(3 * s, 0.06 * s, 1 * s);
    const hStab = new THREE.Mesh(hStabGeo, bodyMat);
    hStab.position.set(0, 0, 2 * s);
    group.add(hStab);
    // Vertical stabilizer (tail fin)
    const vStabGeo = new THREE.BoxGeometry(0.06 * s, 1.5 * s, 1.2 * s);
    const vStab = new THREE.Mesh(vStabGeo, bodyMat);
    vStab.position.set(0, 0.7 * s, 1.8 * s);
    group.add(vStab);
    // Tail fin accent stripe
    const finStripeGeo = new THREE.BoxGeometry(0.08 * s, 0.3 * s, 0.8 * s);
    const finStripe = new THREE.Mesh(finStripeGeo, accentMat);
    finStripe.position.set(0, 1 * s, 1.7 * s);
    group.add(finStripe);
    // Engine exhaust(s)
    for (const xOff of profile.wingArea > 35 ? [-0.8 * s, 0.8 * s] : [0]) {
        const exhaustGeo = new THREE.CylinderGeometry(0.12 * s, 0.15 * s, 0.6 * s, 8);
        const exhaust = new THREE.Mesh(exhaustGeo, darkMat);
        exhaust.rotation.x = Math.PI / 2;
        exhaust.position.set(xOff, -0.1 * s, 2.3 * s);
        group.add(exhaust);
        // Exhaust glow
        const glowGeo = new THREE.CylinderGeometry(0.08 * s, 0.05 * s, 0.3 * s, 6);
        const glowMat = new THREE.MeshStandardMaterial({
            color: 0xff4400,
            emissive: 0xff2200,
            emissiveIntensity: 2,
        });
        const glow = new THREE.Mesh(glowGeo, glowMat);
        glow.rotation.x = Math.PI / 2;
        glow.position.set(xOff, -0.1 * s, 2.65 * s);
        glow.name = 'exhaustGlow';
        group.add(glow);
    }
    // Landing gear (simple)
    for (const xOff of [-0.5 * s, 0.5 * s]) {
        const gearGeo = new THREE.CylinderGeometry(0.03 * s, 0.03 * s, 0.4 * s, 4);
        const gear = new THREE.Mesh(gearGeo, darkMat);
        gear.position.set(xOff, -0.45 * s, -0.5 * s);
        group.add(gear);
        const wheelGeo = new THREE.CylinderGeometry(0.08 * s, 0.08 * s, 0.06 * s, 8);
        const wheel = new THREE.Mesh(wheelGeo, darkMat);
        wheel.rotation.z = Math.PI / 2;
        wheel.position.set(xOff, -0.65 * s, -0.5 * s);
        group.add(wheel);
    }
    // Nose gear
    const noseGearGeo = new THREE.CylinderGeometry(0.02 * s, 0.02 * s, 0.3 * s, 4);
    const noseGear = new THREE.Mesh(noseGearGeo, darkMat);
    noseGear.position.set(0, -0.35 * s, -1.8 * s);
    group.add(noseGear);
    return group;
}
// Build enemy aircraft model (simpler)
function buildEnemyModel(color) {
    const group = new THREE.Group();
    const mat = new THREE.MeshStandardMaterial({ color, roughness: 0.5, metalness: 0.4 });
    const darkMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.7, metalness: 0.6 });
    // Body
    const bodyGeo = new THREE.CylinderGeometry(0.25, 0.2, 3.5, 8);
    const body = new THREE.Mesh(bodyGeo, mat);
    body.rotation.x = Math.PI / 2;
    group.add(body);
    // Nose
    const noseGeo = new THREE.ConeGeometry(0.25, 1.2, 8);
    const nose = new THREE.Mesh(noseGeo, mat);
    nose.rotation.x = -Math.PI / 2;
    nose.position.z = -2.35;
    group.add(nose);
    // Wings
    const wingGeo = new THREE.BoxGeometry(7, 0.06, 1.2);
    const wings = new THREE.Mesh(wingGeo, mat);
    wings.position.set(0, -0.04, 0.15);
    group.add(wings);
    // Tail
    const tailGeo = new THREE.BoxGeometry(2.5, 0.05, 0.8);
    const tail = new THREE.Mesh(tailGeo, mat);
    tail.position.set(0, 0, 1.7);
    group.add(tail);
    // Vertical stabilizer
    const vStabGeo = new THREE.BoxGeometry(0.05, 1.2, 1);
    const vStab = new THREE.Mesh(vStabGeo, mat);
    vStab.position.set(0, 0.6, 1.5);
    group.add(vStab);
    // Exhaust glow
    const glowGeo = new THREE.CylinderGeometry(0.07, 0.04, 0.25, 6);
    const glowMat = new THREE.MeshStandardMaterial({ color: 0xff3300, emissive: 0xff1100, emissiveIntensity: 2 });
    const glow = new THREE.Mesh(glowGeo, glowMat);
    glow.rotation.x = Math.PI / 2;
    glow.position.set(0, -0.08, 2.35);
    group.add(glow);
    return group;
}
// Build target waypoint marker
function buildWaypointMarker() {
    const group = new THREE.Group();
    // Ring
    const ringGeo = new THREE.TorusGeometry(3, 0.2, 8, 16);
    const ringMat = new THREE.MeshStandardMaterial({ color: 0x00ff88, emissive: 0x00ff44, emissiveIntensity: 1 });
    const ring = new THREE.Mesh(ringGeo, ringMat);
    group.add(ring);
    // Inner glow sphere
    const innerGeo = new THREE.SphereGeometry(1.5, 8, 8);
    const innerMat = new THREE.MeshStandardMaterial({ color: 0x00ff88, emissive: 0x00ff44, emissiveIntensity: 0.5, transparent: true, opacity: 0.3 });
    const inner = new THREE.Mesh(innerGeo, innerMat);
    group.add(inner);
    return group;
}
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Renderer = void 0;
const terrain_1 = require("./terrain");
const models_1 = require("./models");
class Renderer {
    constructor() {
        this.exhaustGlow = null;
        // Scene with fog for depth
        this.scene = new THREE.Scene();
        const skyColor = 0x6db3f2;
        this.scene.background = new THREE.Color(skyColor);
        this.scene.fog = new THREE.FogExp2(skyColor, 0.00012);
        // Camera
        this.camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 1, 20000);
        // Renderer
        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.setClearColor(skyColor);
        document.getElementById('canvas-container').appendChild(this.renderer.domElement);
        // Lighting
        const ambientLight = new THREE.AmbientLight(0x6688aa, 0.5);
        this.scene.add(ambientLight);
        const hemiLight = new THREE.HemisphereLight(0x87CEEB, 0x445522, 0.6);
        this.scene.add(hemiLight);
        // Sun (directional light)
        this.sunLight = new THREE.DirectionalLight(0xffffee, 1.2);
        this.sunLight.position.set(500, 800, 300);
        this.scene.add(this.sunLight);
        // Clouds
        this.createClouds();
        // Terrain generator
        this.terrainGenerator = new terrain_1.TerrainGenerator(Math.floor(Math.random() * 10000));
        // Aircraft mesh (placeholder)
        this.aircraftMesh = new THREE.Group();
    }
    setupAircraft(profile) {
        const model = (0, models_1.buildAircraftModel)(profile);
        while (this.aircraftMesh.children.length > 0) {
            this.aircraftMesh.remove(this.aircraftMesh.children[0]);
        }
        // Copy children from model to aircraft mesh
        for (const child of model.children) {
            this.aircraftMesh.add(child);
        }
        // Find exhaust glow for animation
        for (const child of this.aircraftMesh.children) {
            if (child.name === 'exhaustGlow') {
                this.exhaustGlow = child;
                break;
            }
        }
        this.scene.add(this.aircraftMesh);
    }
    setupTerrain() {
        const terrain = (0, terrain_1.createTerrainMesh)(this.terrainGenerator, 12000, 300);
        if (terrain)
            this.scene.add(terrain);
        const water = (0, terrain_1.createWater)(12000);
        this.scene.add(water);
    }
    setupRunway() {
        const runway = (0, terrain_1.createRunway)();
        this.scene.add(runway);
    }
    setupEnemies(count) {
        // Enemies are set up by the game manager
    }
    getTerrainHeight(x, z) {
        return this.terrainGenerator.getHeight(x, z);
    }
    updateCamera(state, dt) {
        // Chase camera with smooth follow
        const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(state.orientation);
        const up = new THREE.Vector3(0, 1, 0).applyQuaternion(state.orientation);
        // Camera offset behind and above aircraft
        const camDist = 25;
        const camHeight = 8;
        const targetPos = state.position.clone()
            .sub(forward.multiplyScalar(camDist))
            .add(up.multiplyScalar(camHeight));
        // Smooth camera movement
        this.camera.position.lerp(targetPos, Math.min(1, dt * 4));
        // Look slightly ahead of aircraft
        const lookTarget = state.position.clone().add(forward.multiplyScalar(20));
        this.camera.lookAt(lookTarget);
    }
    updateAircraftVisual(state) {
        this.aircraftMesh.position.copy(state.position);
        this.aircraftMesh.quaternion.copy(state.orientation);
        // Animate exhaust glow based on throttle
        if (this.exhaustGlow) {
            const intensity = state.throttle * 3;
            this.exhaustGlow.material.emissiveIntensity = Math.max(0.5, intensity);
            const scale = 0.8 + state.throttle * 0.6;
            this.exhaustGlow.scale.set(scale, scale, scale);
        }
    }
    updateSun(state) {
        // Move sun relative to aircraft for consistent lighting
        this.sunLight.position.copy(state.position).add(new THREE.Vector3(500, 800, 300));
    }
    render() {
        this.renderer.render(this.scene, this.camera);
    }
    createClouds() {
        const cloudMat = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            transparent: true,
            opacity: 0.6,
            roughness: 1,
            metalness: 0,
        });
        for (let i = 0; i < 80; i++) {
            const cloudGroup = new THREE.Group();
            const numPuffs = 3 + Math.floor(Math.random() * 4);
            for (let j = 0; j < numPuffs; j++) {
                const puffGeo = new THREE.SphereGeometry(20 + Math.random() * 40, 7, 5);
                const puff = new THREE.Mesh(puffGeo, cloudMat);
                puff.position.set((Math.random() - 0.5) * 60, (Math.random() - 0.5) * 10, (Math.random() - 0.5) * 40);
                puff.scale.y = 0.4;
                cloudGroup.add(puff);
            }
            cloudGroup.position.set((Math.random() - 0.5) * 10000, 300 + Math.random() * 600, (Math.random() - 0.5) * 10000);
            this.scene.add(cloudGroup);
        }
    }
    resize() {
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.renderer.setSize(window.innerWidth, window.innerHeight);
    }
}
exports.Renderer = Renderer;
};

// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.GameManager = exports.SCENARIOS = void 0;
const Physics = __importStar(require("./physics"));
const models_1 = require("./models");
exports.SCENARIOS = [
    {
        type: 'takeoff',
        name: 'Takeoff & Landing',
        description: 'Master the art of precision flight. Take off from the runway and land safely.',
        initialAltitude: 12,
        terrainScale: 1.0,
    },
    {
        type: 'dogfight',
        name: 'Dogfight',
        description: 'Engage enemy aircraft in high-speed aerial combat. Destroy all hostiles!',
        initialAltitude: 500,
        terrainScale: 1.2,
    },
];
class GameManager {
    constructor(renderer, input, audio) {
        this.enemies = [];
        this.waypoints = [];
        this.running = false;
        this.gameOver = false;
        this.message = '';
        this.lastTime = 0;
        this.renderer = renderer;
        this.input = input;
        this.audio = audio;
        this.state = Physics.createInitialState(new THREE.Vector3(0, 12, 0));
        this.profile = null; // Set later
        this.scenario = exports.SCENARIOS[0];
    }
    start(profile, scenario) {
        this.profile = profile;
        this.scenario = scenario;
        this.renderer.setupAircraft(profile);
        if (scenario.type === 'takeoff') {
            // Get terrain height at runway position and place aircraft above it
            const rwAlt = this.renderer.getTerrainHeight(0, -900);
            this.state = Physics.createInitialState(new THREE.Vector3(0, rwAlt + 20, -900));
            // Orient aircraft pointing toward +Z (forward along runway)
            // In Three.js, -Z is forward by default; we want the nose to point +Z
            const euler = new THREE.Euler(0, Math.PI, 0);
            this.state.orientation.setFromEuler(euler);
            this.state.onGround = true;
        }
        else {
            // Dogfight: start in the air with some speed
            this.state = Physics.createInitialState(new THREE.Vector3(0, scenario.initialAltitude, 0));
            this.state.velocity.set(80, 5, 0); // Initial forward speed
            this.state.throttle = 0.6;
        }
        this.enemies = [];
        if (scenario.type === 'dogfight') {
            this.spawnEnemies(4);
        }
        this.running = true;
        this.gameOver = false;
        this.message = '';
        this.lastTime = performance.now();
    }
    spawnEnemies(count) {
        for (let i = 0; i < count; i++) {
            const angle = (i / count) * Math.PI * 2;
            const dist = 500 + Math.random() * 300;
            const pos = new THREE.Vector3(this.state.position.x + Math.cos(angle) * dist, 200 + Math.random() * 400, this.state.position.z + Math.sin(angle) * dist);
            const model = (0, models_1.buildEnemyModel)(0xcc3333);
            model.position.copy(pos);
            this.renderer.scene.add(model);
            this.enemies.push({
                position: pos.clone(),
                velocity: new THREE.Vector3((Math.random() - 0.5) * 60, 0, (Math.random() - 0.5) * 60),
                orientation: new THREE.Quaternion(),
                health: 100,
                alive: true,
                mesh: model,
                targetPosition: pos.clone(),
            });
        }
        // Add waypoints for dogfight objectives
        for (let i = 0; i < 3; i++) {
            const angle = (i / 3) * Math.PI * 2 + Math.PI / 6;
            const dist = 800;
            const wpPos = new THREE.Vector3(this.state.position.x + Math.cos(angle) * dist, 300 + Math.random() * 200, this.state.position.z + Math.sin(angle) * dist);
            const marker = (0, models_1.buildWaypointMarker)();
            marker.position.copy(wpPos);
            this.renderer.scene.add(marker);
            this.waypoints.push(marker);
        }
    }
    update() {
        if (!this.running || this.gameOver)
            return;
        const now = performance.now();
        let dt = (now - this.lastTime) / 1000;
        this.lastTime = now;
        dt = Math.min(dt, 0.05); // Cap delta time
        // Update input
        this.input.update();
        // Update throttle
        Physics.updateThrottle(this.state, this.input.controls, dt, this.profile.engineResponseRate);
        // Get terrain altitude at current position
        const terrainAlt = this.renderer.getTerrainHeight(this.state.position.x, this.state.position.z);
        this.state.altitude = Math.max(0, this.state.position.y - terrainAlt);
        // Update physics
        const { dragForce } = Physics.updatePhysics(this.profile, this.state, this.input.controls, dt, terrainAlt);
        // Check crash conditions (only after aircraft has been airborne for a bit)
        if (!this.state.onGround && this.state.altitude < 2 && this.state.airspeed > 10) {
            // Crashed into ground at speed
            this.gameOver = true;
            this.message = 'CRASHED!';
            return;
        }
        // Update aircraft visual
        this.renderer.updateAircraftVisual(this.state);
        // Update camera
        this.renderer.updateCamera(this.state, dt);
        // Update sun position
        this.renderer.updateSun(this.state);
        // Update enemies
        this.updateEnemies(dt);
        // Check dogfight objectives
        if (this.scenario.type === 'dogfight') {
            this.checkDogfightObjectives();
        }
        // Check takeoff/landing objectives
        if (this.scenario.type === 'takeoff') {
            this.checkTakeoffObjectives();
        }
        // Update audio
        this.audio.update(this.state.throttle, this.state.airspeed, this.state.altitude, this.state.isStalled);
        // Render
        this.renderer.render();
    }
    updateEnemies(dt) {
        for (const enemy of this.enemies) {
            if (!enemy.alive)
                continue;
            // Simple AI: fly toward player with some randomness
            const toPlayer = new THREE.Vector3().subVectors(this.state.position, enemy.position);
            const distToPlayer = toPlayer.length();
            // Change target periodically
            if (Math.random() < 0.01) {
                enemy.targetPosition.set(this.state.position.x + (Math.random() - 0.5) * 600, Math.max(100, this.state.position.y + (Math.random() - 0.5) * 200), this.state.position.z + (Math.random() - 0.5) * 600);
            }
            // Move toward target
            const toTarget = new THREE.Vector3().subVectors(enemy.targetPosition, enemy.position);
            if (toTarget.lengthSq() < 100) {
                enemy.targetPosition.set(this.state.position.x + (Math.random() - 0.5) * 800, Math.max(100, 200 + Math.random() * 400), this.state.position.z + (Math.random() - 0.5) * 800);
            }
            const dir = toTarget.normalize();
            enemy.velocity.lerp(dir.multiplyScalar(70), dt * 2);
            // Keep above terrain
            const terrAlt = this.renderer.getTerrainHeight(enemy.position.x, enemy.position.z);
            if (enemy.position.y < terrAlt + 50) {
                enemy.velocity.y += 10;
            }
            enemy.position.add(enemy.velocity.clone().multiplyScalar(dt));
            // Orient toward velocity direction
            if (enemy.velocity.lengthSq() > 1) {
                const velDir = enemy.velocity.clone().normalize();
                const forward = new THREE.Vector3(0, 0, -1);
                const quat = new THREE.Quaternion().setFromUnitVectors(forward, velDir);
                enemy.orientation.copy(quat);
            }
            // Update mesh position
            if (enemy.mesh) {
                enemy.mesh.position.copy(enemy.position);
                enemy.mesh.quaternion.copy(enemy.orientation);
            }
            // Collision with player
            if (distToPlayer < 15) {
                enemy.health -= dt * 30;
                if (enemy.health <= 0) {
                    enemy.alive = false;
                    if (enemy.mesh)
                        this.renderer.scene.remove(enemy.mesh);
                }
            }
            // Enemy shoots at player (proximity damage)
            if (distToPlayer < 50 && Math.random() < dt * 0.3) {
                this.message = 'UNDER FIRE!';
                setTimeout(() => { this.message = ''; }, 1000);
            }
        }
    }
    checkDogfightObjectives() {
        const aliveEnemies = this.enemies.filter(e => e.alive).length;
        if (aliveEnemies === 0) {
            this.gameOver = true;
            this.message = 'VICTORY! All enemies destroyed!';
        }
        // Check waypoint proximity for bonus
        for (const wp of this.waypoints) {
            const dist = this.state.position.distanceTo(wp.position);
            if (dist < 20) {
                this.renderer.scene.remove(wp);
                this.message = 'WAYPOINT CLEARED!';
                setTimeout(() => { this.message = ''; }, 1500);
            }
        }
    }
    checkTakeoffObjectives() {
        // Success: reach altitude of 200m+ and maintain for a bit
        if (this.state.altitude > 200 && this.state.airspeed > 40) {
            this.message = 'SUCCESSFULLY AIRBORNE!';
            setTimeout(() => { this.message = ''; }, 3000);
        }
        // Landing: touch down gently on runway area
        if (this.state.onGround && Math.abs(this.state.position.x) < 40 && Math.abs(this.state.position.z) < 1000) {
            const verticalSpeed = Math.abs(this.state.velocity.y);
            if (verticalSpeed < 5) {
                this.gameOver = true;
                this.message = 'PERFECT LANDING!';
            }
            else {
                this.gameOver = true;
                this.message = 'HARDED LANDING - SIM ENDED';
            }
        }
    }
    updateHUD() {
        const kts = Math.round(this.state.airspeed * 1.94384); // m/s to knots
        const altFt = Math.round(this.state.altitude * 3.28084); // meters to feet
        const vspeedFpm = Math.round(this.state.velocity.y * 196.85); // m/s to ft/min
        // Get heading from orientation
        const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(this.state.orientation);
        let heading = Math.atan2(forward.x, forward.z) * 180 / Math.PI;
        if (heading < 0)
            heading += 360;
        // Get pitch and roll from orientation
        const euler = new THREE.Euler().setFromQuaternion(this.state.orientation);
        const pitchDeg = -euler.x * 180 / Math.PI;
        const rollDeg = euler.z * 180 / Math.PI;
        document.getElementById('hud-airspeed').textContent = kts.toString();
        document.getElementById('hud-altitude').textContent = altFt.toString();
        document.getElementById('hud-heading').textContent = heading.toFixed(0).padStart(3, '0');
        document.getElementById('hud-vspeed').textContent = vspeedFpm.toString();
        document.getElementById('hud-throttle').textContent = Math.round(this.state.throttle * 100).toString();
        document.getElementById('hud-gforce').textContent = this.state.gForce.toFixed(1);
        document.getElementById('hud-pitch').textContent = pitchDeg.toFixed(1);
        document.getElementById('hud-roll').textContent = rollDeg.toFixed(1);
        document.getElementById('hud-aoa').textContent = (this.state.angleOfAttack * 180 / Math.PI).toFixed(1);
        document.getElementById('hud-drag').textContent = Math.round(this.state.airspeed > 0 ?
            0.5 * 1.225 * this.state.airspeed ** 2 * this.profile.wingArea * this.profile.cd0 : 0).toString();
        // Stall warning
        if (this.state.isStalled) {
            document.getElementById('hud-center').style.borderColor = 'rgba(255,0,0,0.8)';
            document.getElementById('hud-center').innerHTML = '<span style="color:#f00;font-size:14px">STALL</span>';
        }
        else {
            document.getElementById('hud-center').style.borderColor = 'rgba(0,255,0,0.3)';
            document.getElementById('hud-center').innerHTML = '';
        }
        // Message display
        const msgEl = document.getElementById('message-overlay');
        if (msgEl) {
            if (this.message) {
                msgEl.style.display = 'block';
                document.getElementById('message-text').textContent = this.message;
            }
            else {
                msgEl.style.display = 'none';
            }
        }
    }
    stop() {
        this.running = false;
    }
}
exports.GameManager = GameManager;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>