← Flight Simulator results

Flight Simulator

Qwen 3.6 27b · 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 91,131 bytes · SHA-256 309ca5686baf
<!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.160.0/build/three.min.js"></script>
<style>
</style>
</head>
<body>
<div id="app" style="position:relative;width:100vw;height:100vh;overflow:hidden;">
  <canvas id="gameCanvas" style="display:block;width:100%;height:100%;"></canvas>
  <canvas id="hudCanvas" style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;"></canvas>
  <div id="loadingOverlay" style="position:absolute;top:0;left:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#0a0a2e;color:#88aacc;font-family:Arial;font-size:24px;z-index:100;">Loading Apex Aero...</div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./types":"src/types.ts","./aircraft":"src/aircraft.ts","./terrain":"src/terrain.ts","./audio":"src/audio.ts","./input":"src/input.ts","./hud":"src/hud.ts","./game":"src/game.ts","./noise":"src/noise.ts","./atmosphere":"src/atmosphere.ts"},"src/noise.ts":{"./types":"src/types.ts"},"src/physics.ts":{"./types":"src/types.ts"},"src/aircraft.ts":{"./types":"src/types.ts","./noise":"src/noise.ts"},"src/terrain.ts":{"./noise":"src/noise.ts","./types":"src/types.ts"},"src/hud.ts":{"./types":"src/types.ts"},"src/game.ts":{"./types":"src/types.ts","./noise":"src/noise.ts","./physics":"src/physics.ts","./input":"src/input.ts","./audio":"src/audio.ts"},"src/atmosphere.ts":{"./noise":"src/noise.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 });
// Apex Aero - Flight Simulator Entry Point
const types_1 = require("./types");
const aircraft_1 = require("./aircraft");
const terrain_1 = require("./terrain");
const audio_1 = require("./audio");
const input_1 = require("./input");
const hud_1 = require("./hud");
const game_1 = require("./game");
const atmosphere_1 = require("./atmosphere");
let renderer;
let scene;
let camera;
let clock;
let hud;
let hudCanvas;
let terrainManager;
let audioSystem;
let gameManager;
let input;
let aircraftGroup;
let aircraftModel;
let playerState;
let selectedAircraft = 'fighter';
let cameraMode = 'chase';
let targetMeshes = [];
let particleMeshes = [];
let ambientLight;
let dirLight;
let skyDome;
let clouds;
function init() {
    // Get existing DOM elements
    const canvas = document.getElementById('gameCanvas');
    hudCanvas = document.getElementById('hudCanvas');
    const loadingOverlay = document.getElementById('loadingOverlay');
    if (!canvas || !hudCanvas)
        return;
    // Three.js setup
    renderer = new THREE.WebGLRenderer({
        canvas,
        antialias: true,
        powerPreference: 'high-performance',
    });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x87CEEB);
    scene.fog = new THREE.FogExp2(0x87CEEB, 0.00015);
    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 50000);
    camera.position.set(0, 50, 50);
    clock = new THREE.Clock();
    // Lighting
    ambientLight = new THREE.AmbientLight(0x404060, 0.6);
    scene.add(ambientLight);
    dirLight = new THREE.DirectionalLight(0xfff5e0, 1.5);
    dirLight.position.set(500, 1000, 500);
    dirLight.castShadow = true;
    dirLight.shadow.mapSize.width = 2048;
    dirLight.shadow.mapSize.height = 2048;
    dirLight.shadow.camera.near = 10;
    dirLight.shadow.camera.far = 5000;
    dirLight.shadow.camera.left = -1000;
    dirLight.shadow.camera.right = 1000;
    dirLight.shadow.camera.top = 1000;
    dirLight.shadow.camera.bottom = -1000;
    scene.add(dirLight);
    const hemiLight = new THREE.HemisphereLight(0x87CEEB, 0x4a7c3f, 0.4);
    scene.add(hemiLight);
    // Terrain
    terrainManager = new terrain_1.TerrainManager(scene);
    // Water
    (0, terrain_1.createWater)(scene);
    // Atmosphere
    skyDome = (0, atmosphere_1.createSkyDome)(scene);
    clouds = (0, atmosphere_1.createClouds)(scene);
    (0, atmosphere_1.createSun)(scene);
    // Audio
    audioSystem = new audio_1.AudioSystem();
    // HUD
    hud = new hud_1.HUD(hudCanvas);
    hud.resize(window.innerWidth, window.innerHeight);
    // Input
    input = (0, input_1.createInputState)();
    (0, input_1.setupInput)(input, canvas);
    // Game manager
    gameManager = new game_1.GameManager(scene, audioSystem);
    // Resize handler
    window.addEventListener('resize', onResize);
    // Hide loading overlay
    if (loadingOverlay)
        loadingOverlay.style.display = 'none';
    // Start render loop
    animate();
}
function onResize() {
    const w = window.innerWidth;
    const h = window.innerHeight;
    camera.aspect = w / h;
    camera.updateProjectionMatrix();
    renderer.setSize(w, h);
    hud.resize(w, h);
}
function loadGameMode() {
    // Cleanup previous mode
    if (aircraftGroup) {
        scene.remove(aircraftGroup);
    }
    targetMeshes.forEach(m => scene.remove(m));
    targetMeshes = [];
    particleMeshes.forEach(m => scene.remove(m));
    particleMeshes = [];
    selectedAircraft = gameManager.selectedAircraftType;
    const profile = aircraft_1.AIRCRAFT_PROFILES[selectedAircraft];
    // Create aircraft
    aircraftModel = (0, aircraft_1.buildAircraftModel)(selectedAircraft);
    aircraftGroup = new THREE.Group();
    aircraftGroup.add(aircraftModel);
    // Starting position
    const startPos = new THREE.Vector3(0, 15, 0);
    playerState = (0, aircraft_1.createAircraftState)(startPos);
    aircraftGroup.position.copy(startPos);
    scene.add(aircraftGroup);
    // Mode-specific setup
    if (gameManager.state === types_1.GameState.TakeoffLanding) {
        (0, terrain_1.createRunway)(scene, new THREE.Vector3(-200, 0, 0), new THREE.Vector3(800, 0, 0), 40, 1000);
    }
    if (gameManager.state === types_1.GameState.Dogfight) {
        const ds = gameManager.dogfight;
        if (ds) {
            ds.targets.forEach((target, i) => {
                const mesh = (0, terrain_1.createTargetDrone)(scene, target.position);
                targetMeshes.push(mesh);
                target.mesh = mesh;
            });
        }
    }
    // Init audio on first interaction
    audioSystem.init();
}
function updateCamera(dt) {
    if (!playerState || !aircraftGroup)
        return;
    const pos = aircraftGroup.position;
    if (cameraMode === 'chase') {
        // Chase camera
        const camDist = 30;
        const camHeight = 10;
        // Get aircraft forward direction
        const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(playerState.orientation);
        const up = new THREE.Vector3(0, 1, 0).applyQuaternion(playerState.orientation);
        const right = new THREE.Vector3(1, 0, 0).applyQuaternion(playerState.orientation);
        // Camera offset behind and above aircraft
        const camOffset = new THREE.Vector3()
            .addVectors(forward.clone().multiplyScalar(-camDist), up.clone().multiplyScalar(camHeight));
        // Apply mouse camera rotation
        const euler = new THREE.Euler(input.cameraPitch, input.cameraYaw, 0, 'YXZ');
        const q = new THREE.Quaternion().setFromEuler(euler);
        camOffset.applyQuaternion(q);
        const camPos = new THREE.Vector3().addVectors(pos, camOffset);
        camera.position.lerp(camPos, Math.min(1, dt * 5));
        // Look at aircraft with slight offset
        const lookTarget = new THREE.Vector3()
            .addVectors(pos, forward.clone().multiplyScalar(20));
        camera.lookAt(lookTarget);
    }
    else {
        // Cockpit camera
        const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(playerState.orientation);
        const up = new THREE.Vector3(0, 1, 0).applyQuaternion(playerState.orientation);
        const camPos = new THREE.Vector3().copy(pos);
        camPos.add(forward.clone().multiplyScalar(1));
        camPos.add(up.clone().multiplyScalar(0.5));
        camera.position.copy(camPos);
        const lookTarget = new THREE.Vector3().addVectors(pos, forward.clone().multiplyScalar(100));
        camera.lookAt(lookTarget);
    }
}
function updateParticles() {
    // Remove old particle meshes
    particleMeshes.forEach(m => scene.remove(m));
    particleMeshes = [];
    const particles = gameManager.getParticles();
    // Create particle system
    if (particles.length === 0)
        return;
    // Use individual meshes for simplicity
    const maxParticles = Math.min(particles.length, 200);
    for (let i = 0; i < maxParticles; i++) {
        const p = particles[i];
        if (p.life > p.maxLife)
            continue;
        const geo = new THREE.SphereGeometry(p.size * 0.3, 4, 4);
        const lifeRatio = 1 - p.life / p.maxLife;
        const mat = new THREE.MeshBasicMaterial({
            color: p.color,
            transparent: true,
            opacity: lifeRatio * 0.6,
        });
        const mesh = new THREE.Mesh(geo, mat);
        mesh.position.copy(p.position);
        scene.add(mesh);
        particleMeshes.push(mesh);
    }
}
function animate() {
    requestAnimationFrame(animate);
    const dt = Math.min(clock.getDelta(), 0.05);
    const time = clock.getElapsedTime();
    // Process input
    const controls = (0, input_1.processInput)(input, dt);
    // Handle mode transitions
    const prevState = gameManager.state;
    if (gameManager.state !== prevState) {
        loadGameMode();
    }
    if (gameManager.state === types_1.GameState.Menu) {
        // Slow camera orbit in menu
        const orbitSpeed = 0.1;
        camera.position.x = Math.sin(time * orbitSpeed) * 200;
        camera.position.z = Math.cos(time * orbitSpeed) * 200;
        camera.position.y = 100 + Math.sin(time * 0.15) * 20;
        camera.lookAt(0, 50, 0);
        // Update terrain
        terrainManager.update(camera.position);
        // Update sky dome
        if (skyDome)
            (0, atmosphere_1.updateSkyDome)(skyDome, camera.position);
        // Render HUD
        const profile = aircraft_1.AIRCRAFT_PROFILES[selectedAircraft];
        hud.render(playerState || (0, aircraft_1.createAircraftState)(new THREE.Vector3(0, 15, 0)), profile, gameManager.state, gameManager.dogfight, time, selectedAircraft);
        renderer.render(scene, camera);
        return;
    }
    if (!playerState || !aircraftGroup) {
        renderer.render(scene, camera);
        return;
    }
    // Update game
    const profile = aircraft_1.AIRCRAFT_PROFILES[selectedAircraft];
    gameManager.update(playerState, profile, input, dt, time);
    // Apply controls to aircraft state
    playerState.pitchInput = controls.pitch;
    playerState.rollInput = controls.roll;
    playerState.yawInput = controls.yaw;
    playerState.throttle = Math.max(0, Math.min(1, playerState.throttle + controls.throttleDelta));
    // Camera mode toggle
    if (input.keyT) {
        cameraMode = cameraMode === 'chase' ? 'cockpit' : 'chase';
        input.keyT = false; // Debounce
    }
    // Update aircraft visual position
    aircraftGroup.position.copy(playerState.position);
    aircraftGroup.quaternion.copy(playerState.orientation);
    // Update aircraft visuals (engine glow, propeller)
    (0, aircraft_1.updateAircraftVisuals)(aircraftModel, playerState.throttle, playerState.airspeed, selectedAircraft, time);
    // Update camera
    updateCamera(dt);
    // Update terrain chunks
    terrainManager.update(playerState.position);
    // Update target positions (dogfight)
    if (gameManager.state === types_1.GameState.Dogfight && gameManager.dogfight) {
        gameManager.dogfight.targets.forEach((target, i) => {
            if (targetMeshes[i]) {
                targetMeshes[i].position.copy(target.position);
                targetMeshes[i].quaternion.copy(target.orientation);
            }
        });
    }
    // Update particles
    updateParticles();
    // Update shadow light to follow aircraft
    dirLight.position.set(playerState.position.x + 500, playerState.position.y + 1000, playerState.position.z + 500);
    // Render HUD
    hud.render(playerState, profile, gameManager.state, gameManager.dogfight, time, selectedAircraft);
    // Update sky dome position
    if (skyDome)
        (0, atmosphere_1.updateSkyDome)(skyDome, camera.position);
    // Render scene
    renderer.render(scene, camera);
}
// Start the game
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";
// Core type definitions for Apex Aero Flight Simulator
Object.defineProperty(exports, "__esModule", { value: true });
exports.GameState = exports.BiomeType = void 0;
var BiomeType;
(function (BiomeType) {
    BiomeType[BiomeType["Water"] = 0] = "Water";
    BiomeType[BiomeType["Beach"] = 1] = "Beach";
    BiomeType[BiomeType["Grassland"] = 2] = "Grassland";
    BiomeType[BiomeType["Forest"] = 3] = "Forest";
    BiomeType[BiomeType["Mountain"] = 4] = "Mountain";
    BiomeType[BiomeType["Snow"] = 5] = "Snow";
})(BiomeType || (exports.BiomeType = BiomeType = {}));
var GameState;
(function (GameState) {
    GameState[GameState["Menu"] = 0] = "Menu";
    GameState[GameState["TakeoffLanding"] = 1] = "TakeoffLanding";
    GameState[GameState["Dogfight"] = 2] = "Dogfight";
    GameState[GameState["Crashed"] = 3] = "Crashed";
})(GameState || (exports.GameState = GameState = {}));
};

// ── module: src/noise.ts ──
__mods["src/noise.ts"] = function (exports, require, module) {
"use strict";
// Simplex-like noise implementation for terrain generation
Object.defineProperty(exports, "__esModule", { value: true });
exports.noise3D = noise3D;
exports.fbm = fbm;
exports.ridgedNoise = ridgedNoise;
exports.terrainHeight = terrainHeight;
exports.getBiome = getBiome;
const F2 = 0.5 * (Math.sqrt(3) - 1);
const G2 = (3 - Math.sqrt(3)) / 6;
const F3 = 1 / 3;
const G3 = 1 / 6;
const 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]
];
// Permutation table
const perm = new Uint8Array(512);
(function initPerm(seed) {
    const p = new Uint8Array(256);
    for (let i = 0; i < 256; i++)
        p[i] = i;
    let s = seed || 42;
    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++)
        perm[i] = p[i & 255];
})(12345);
function dot3(g, x, y, z) {
    return g[0] * x + g[1] * y + g[2] * z;
}
function noise3D(x, y, z) {
    const s = (x + y + z) * F3;
    const i = Math.floor(x + s);
    const j = Math.floor(y + s);
    const k = Math.floor(z + s);
    const t = (i + j + k) * G3;
    const X0 = i - t, Y0 = j - t, Z0 = k - t;
    const x0 = x - X0, y0 = y - Y0, z0 = z - Z0;
    let i1, j1, k1, i2, j2, k2;
    if (x0 >= y0) {
        if (y0 >= z0) {
            i1 = 1;
            j1 = 0;
            k1 = 0;
            i2 = 1;
            j2 = 1;
            k2 = 0;
        }
        else if (x0 >= z0) {
            i1 = 1;
            j1 = 0;
            k1 = 0;
            i2 = 1;
            j2 = 0;
            k2 = 1;
        }
        else {
            i1 = 0;
            j1 = 0;
            k1 = 1;
            i2 = 1;
            j2 = 0;
            k2 = 1;
        }
    }
    else {
        if (y0 < z0) {
            i1 = 0;
            j1 = 0;
            k1 = 1;
            i2 = 0;
            j2 = 1;
            k2 = 1;
        }
        else if (x0 < z0) {
            i1 = 0;
            j1 = 1;
            k1 = 0;
            i2 = 0;
            j2 = 1;
            k2 = 1;
        }
        else {
            i1 = 0;
            j1 = 1;
            k1 = 0;
            i2 = 1;
            j2 = 1;
            k2 = 0;
        }
    }
    const x1 = x0 - i1 + G3, y1 = y0 - j1 + G3, z1 = z0 - k1 + G3;
    const x2 = x0 - i2 + 2 * G3, y2 = y0 - j2 + 2 * G3, z2 = z0 - k2 + 2 * G3;
    const x3 = x0 - 1 + 3 * G3, y3 = y0 - 1 + 3 * G3, z3 = z0 - 1 + 3 * G3;
    const ii = i & 255, jj = j & 255, kk = k & 255;
    const g0 = grad3[perm[ii + perm[jj + perm[kk]]] % 12];
    const g1 = grad3[perm[ii + i1 + perm[jj + j1 + perm[kk + k1]]] % 12];
    const g2 = grad3[perm[ii + i2 + perm[jj + j2 + perm[kk + k2]]] % 12];
    const g3 = grad3[perm[ii + 1 + perm[jj + 1 + perm[kk + 1]]] % 12];
    let n0, n1, n2, n3;
    let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
    n0 = t0 < 0 ? 0 : (t0 *= t0, t0 * t0 * dot3(g0, x0, y0, z0));
    let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
    n1 = t1 < 0 ? 0 : (t1 *= t1, t1 * t1 * dot3(g1, x1, y1, z1));
    let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
    n2 = t2 < 0 ? 0 : (t2 *= t2, t2 * t2 * dot3(g2, x2, y2, z2));
    let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
    n3 = t3 < 0 ? 0 : (t3 *= t3, t3 * t3 * dot3(g3, x3, y3, z3));
    return 32 * (n0 + n1 + n2 + n3);
}
// Fractal Brownian Motion for more natural terrain
function fbm(x, y, octaves = 6, persistence = 0.5, lacunarity = 2.0) {
    let value = 0;
    let amplitude = 1;
    let frequency = 1;
    let maxVal = 0;
    for (let i = 0; i < octaves; i++) {
        value += amplitude * noise3D(x * frequency, y * frequency, 0);
        maxVal += amplitude;
        amplitude *= persistence;
        frequency *= lacunarity;
    }
    return value / maxVal;
}
// Ridged noise for mountain ridges
function ridgedNoise(x, y, octaves = 4) {
    let value = 0;
    let amplitude = 1;
    let frequency = 1;
    let previous = 1;
    let total = 0;
    let maxTotal = 0;
    for (let i = 0; i < octaves; i++) {
        let n = Math.abs(noise3D(x * frequency, y * frequency, 0));
        n = 1 - n;
        n = n * n;
        value += (n * amplitude * previous);
        previous = n;
        total += amplitude;
        maxTotal += amplitude;
        amplitude *= 0.5;
        frequency *= 2;
    }
    return value / maxTotal;
}
// Combined terrain height function
function terrainHeight(x, z) {
    const scale = 0.002;
    const baseHeight = fbm(x * scale, z * scale, 6, 0.5, 2.0);
    const ridgeHeight = ridgedNoise(x * scale * 1.5, z * scale * 1.5, 4);
    const detail = noise3D(x * scale * 4, z * scale * 4, 0) * 0.1;
    let height = baseHeight * 800 + ridgeHeight * 400 + detail * 50;
    // Flatten near origin for runway area
    const distFromOrigin = Math.sqrt(x * x + z * z);
    const runwayRadius = 500;
    if (distFromOrigin < runwayRadius) {
        const t = distFromOrigin / runwayRadius;
        const flatten = Math.pow(t, 2);
        height = height * flatten + 10 * (1 - flatten);
    }
    return Math.max(height, -50); // Sea floor
}
// Get biome at location
const types_1 = require("./types");
function getBiome(x, z) {
    const h = terrainHeight(x, z);
    if (h < 0)
        return types_1.BiomeType.Water;
    if (h < 5)
        return types_1.BiomeType.Beach;
    if (h < 200)
        return noise3D(x * 0.01, z * 0.01, 1) > 0 ? types_1.BiomeType.Forest : types_1.BiomeType.Grassland;
    if (h < 500)
        return types_1.BiomeType.Mountain;
    return types_1.BiomeType.Snow;
}
};

// ── module: src/physics.ts ──
__mods["src/physics.ts"] = function (exports, require, module) {
"use strict";
// Realistic aerodynamic physics model for flight simulation
// Implements Newtonian mechanics with lift, drag, thrust, weight, and control surface effects
Object.defineProperty(exports, "__esModule", { value: true });
exports.airDensity = airDensity;
exports.speedOfSound = speedOfSound;
exports.machNumber = machNumber;
exports.liftCoefficient = liftCoefficient;
exports.dragCoefficient = dragCoefficient;
exports.calculateForces = calculateForces;
exports.updatePhysics = updatePhysics;
exports.updateThrust = updateThrust;
exports.checkTerrainCollision = checkTerrainCollision;
exports.createCrashParticles = createCrashParticles;
exports.updateParticles = updateParticles;
exports.createContrail = createContrail;
// Physical constants
const G = 9.80665; // m/s²
const SEA_LEVEL_DENSITY = 1.225; // kg/m³
const SEA_LEVEL_TEMP = 288.15; // K
const TEMP_LAPSE = 0.0065; // K/m
const R = 287.05; // J/(kg·K)
// Air density at altitude (ISA model)
function airDensity(altitude) {
    if (altitude < 0)
        return SEA_LEVEL_DENSITY;
    const temp = Math.max(SEA_LEVEL_TEMP - TEMP_LAPSE * altitude, 200);
    const ratio = temp / SEA_LEVEL_TEMP;
    return SEA_LEVEL_DENSITY * Math.pow(ratio, R / (G * TEMP_LAPSE) - 1);
}
// Speed of sound at altitude
function speedOfSound(altitude) {
    const temp = Math.max(SEA_LEVEL_TEMP - TEMP_LAPSE * altitude, 200);
    return Math.sqrt(1.4 * R * temp);
}
// Mach number
function machNumber(airspeed, altitude) {
    return airspeed / speedOfSound(altitude);
}
// Lift coefficient from angle of attack (linear + stall model)
function liftCoefficient(alpha, profile) {
    // Linear region
    let cl = profile.cl0 + profile.clAlpha * alpha;
    // Stall model: gradual loss of lift beyond stall angle
    if (Math.abs(alpha) > profile.stallAngle) {
        const excess = Math.abs(alpha) - profile.stallAngle;
        const stallFactor = Math.exp(-excess * 5);
        cl *= stallFactor;
        // Add some uncommanded lift at extreme angles (deep stall)
        if (excess > 1.0) {
            cl += Math.sign(alpha) * 0.3 * (excess - 1.0);
        }
    }
    return Math.max(-profile.clMax * 0.5, Math.min(profile.clMax, cl));
}
// Drag coefficient (profile drag + induced drag + compressibility drag)
function dragCoefficient(alpha, cl, mach, profile) {
    // Profile drag (parasitic)
    const cdProfile = profile.cd0;
    // Induced drag (from lift)
    const cdInduced = profile.k * cl * cl;
    // Compressibility drag (increases near Mach 1)
    let cdCompress = 0;
    if (mach > 0.7) {
        cdCompress = 0.001 * Math.pow((mach - 0.7) * 10, 3);
    }
    // Wave drag at supersonic speeds
    if (mach > 1.0) {
        cdCompress += 0.05 * Math.pow(mach - 1.0, 2);
    }
    return cdProfile + cdInduced + cdCompress;
}
// Calculate forces in body frame
function calculateForces(state, profile, altitude) {
    const rho = airDensity(altitude);
    const v = state.airspeed;
    const vSq = v * v;
    const alpha = state.angleOfAttack;
    const beta = state.sideslipAngle;
    const mach = machNumber(v, altitude);
    // Weight
    const weight = profile.mass * G;
    // Dynamic pressure
    const q = 0.5 * rho * vSq;
    // Lift
    const cl = liftCoefficient(alpha, profile);
    const lift = q * profile.wingArea * cl;
    // Drag
    const cd = dragCoefficient(alpha, cl, mach, profile);
    const drag = q * profile.referenceArea * cd;
    // Thrust (altitude-corrected)
    const altFactor = Math.max(0, 1 - altitude * profile.thrustRollOff / 1000);
    const availableThrust = profile.maxThrust * altFactor;
    const thrust = state.currentThrust * altFactor;
    // Side force from sideslip
    const sideForce = q * profile.wingArea * 0.3 * beta;
    // Transform forces to body frame
    // Body frame: X forward, Y right, Z down (aerospace convention)
    const forceBody = new THREE.Vector3(thrust - drag, // X: thrust minus drag
    -sideForce, // Y: side force (negative for stability)
    lift - weight // Z: lift minus weight
    );
    // Torque from control surfaces
    const controlAuthority = Math.min(1, q / 5000); // Controls less effective at low speed
    const pitchTorque = -state.pitchInput * controlAuthority * 5000;
    const rollTorque = -state.rollInput * controlAuthority * 3000;
    const yawTorque = -state.yawInput * controlAuthority * 1500;
    // Damping torques (proportional to angular velocity)
    const pitchDamping = -state.angularVelocity.y * 800;
    const rollDamping = -state.angularVelocity.x * 600;
    const yawDamping = -state.angularVelocity.z * 400;
    // Adverse yaw from roll
    const adverseYaw = -state.rollInput * controlAuthority * 500;
    const torqueBody = new THREE.Vector3(rollTorque + rollDamping, pitchTorque + pitchDamping, yawTorque + yawDamping + adverseYaw);
    return { lift, drag, thrust, weight, forceBody, torqueBody };
}
// Update aircraft state using physics (semi-implicit Euler integration)
function updatePhysics(state, profile, dt) {
    if (dt > 0.05)
        dt = 0.05; // Cap timestep for stability
    const altitude = state.position.y;
    const { forceBody, torqueBody } = calculateForces(state, profile, altitude);
    // Transform body-frame forces to world frame
    const forceWorld = forceBody.clone().applyQuaternion(state.orientation);
    const torqueWorld = torqueBody.clone().applyQuaternion(state.orientation);
    // --- Translational dynamics ---
    // F = ma => a = F/m
    const acceleration = forceWorld.clone().divideScalar(profile.mass);
    // Update velocity
    state.velocity.x += acceleration.x * dt;
    state.velocity.y += acceleration.y * dt;
    state.velocity.z += acceleration.z * dt;
    // Clamp velocity to prevent numerical instability
    const maxSpeed = 700; // ~Mach 2
    if (state.velocity.lengthSq() > maxSpeed * maxSpeed) {
        state.velocity.normalize().multiplyScalar(maxSpeed);
    }
    // Update position
    state.position.x += state.velocity.x * dt;
    state.position.y += state.velocity.y * dt;
    state.position.z += state.velocity.z * dt;
    // --- Rotational dynamics ---
    // τ = Iα => α = τ/I
    const angAccelX = torqueWorld.x / profile.ix;
    const angAccelY = torqueWorld.y / profile.iy;
    const angAccelZ = torqueWorld.z / profile.iz;
    // Update angular velocity
    state.angularVelocity.x += angAccelX * dt;
    state.angularVelocity.y += angAccelY * dt;
    state.angularVelocity.z += angAccelZ * dt;
    // Angular velocity damping (gyroscopic stability)
    const angDamping = 0.97;
    state.angularVelocity.x *= angDamping;
    state.angularVelocity.y *= angDamping;
    state.angularVelocity.z *= angDamping;
    // Clamp angular velocities
    const maxAngVel = 2.5;
    if (state.angularVelocity.length() > maxAngVel) {
        state.angularVelocity.normalize().multiplyScalar(maxAngVel);
    }
    // Update orientation using angular velocity
    const angVelMag = state.angularVelocity.length();
    if (angVelMag > 0.001) {
        const axis = new THREE.Vector3().copy(state.angularVelocity).normalize();
        const angle = angVelMag * dt;
        const deltaQuat = new THREE.Quaternion().setFromAxisAngle(axis, angle);
        state.orientation.multiply(deltaQuat);
        state.orientation.normalize();
    }
    // --- Update aerodynamic state ---
    // Forward direction in world space
    const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(state.orientation);
    const up = new THREE.Vector3(0, 1, 0).applyQuaternion(state.orientation);
    const right = new THREE.Vector3(1, 0, 0).applyQuaternion(state.orientation);
    // Velocity direction
    const velDir = state.velocity.clone().normalize();
    state.airspeed = state.velocity.length();
    // Angle of attack: angle between velocity and aircraft plane
    const velProj = velDir.clone().sub(forward.clone().multiplyScalar(velDir.dot(forward)));
    const velInPlane = velProj.clone().normalize();
    if (velInPlane.lengthSq() > 0.001) {
        state.angleOfAttack = Math.asin(Math.max(-1, Math.min(1, velDir.dot(up))));
    }
    // Sideslip angle
    state.sideslipAngle = Math.asin(Math.max(-1, Math.min(1, velDir.dot(right))));
    // Dynamic pressure
    const rho = airDensity(state.position.y);
    state.dynamicPressure = 0.5 * rho * state.airspeed * state.airspeed;
    // Stall detection
    state.isStalled = Math.abs(state.angleOfAttack) > profile.stallAngle && state.airspeed > 10;
    // G-force calculation
    const totalAccel = acceleration.length();
    state.gForce = totalAccel / G;
    // --- Engine dynamics ---
    // Smooth throttle response
    const thrustDelta = (state.throttle * profile.maxThrust - state.currentThrust) * profile.engineResponseRate * dt;
    state.currentThrust = Math.max(0, Math.min(profile.maxThrust, state.currentThrust + thrustDelta));
    // RPM tracking
    state.rpm = state.throttle * 100;
}
// Engine thrust update
function updateThrust(state, targetThrottle, dt) {
    state.throttle = Math.max(0, Math.min(1, targetThrottle));
}
// Check terrain collision
function checkTerrainCollision(position, terrainHeight, aircraftSize = 5) {
    return position.y < terrainHeight + aircraftSize;
}
// Create crash particles
function createCrashParticles(position, count = 50) {
    const particles = [];
    for (let i = 0; i < count; i++) {
        particles.push({
            position: position.clone(),
            velocity: new THREE.Vector3((Math.random() - 0.5) * 100, Math.random() * 80 + 20, (Math.random() - 0.5) * 100),
            life: 0,
            maxLife: Math.random() * 3 + 2,
            size: Math.random() * 3 + 1,
            color: Math.random() > 0.5 ? 0xff6600 : 0xff3300,
        });
    }
    return particles;
}
// Update particles
function updateParticles(particles, dt) {
    return particles
        .map(p => {
        p.life += dt;
        p.velocity.y -= G * dt;
        p.position.x += p.velocity.x * dt;
        p.position.y += p.velocity.y * dt;
        p.position.z += p.velocity.z * dt;
        return p;
    })
        .filter(p => p.life < p.maxLife);
}
// Create contrail particles
function createContrail(position, velocity, altitude) {
    // Contrails form at high altitude with high speed
    if (altitude < 3000)
        return null;
    if (Math.random() > 0.3)
        return null;
    return {
        position: position.clone(),
        velocity: velocity.clone().multiplyScalar(-0.1),
        life: 0,
        maxLife: 8 + Math.random() * 4,
        size: 2 + Math.random() * 2,
        color: 0xffffff,
    };
}
};

// ── module: src/aircraft.ts ──
__mods["src/aircraft.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AIRCRAFT_PROFILES = void 0;
exports.createAircraftState = createAircraftState;
exports.buildAircraftModel = buildAircraftModel;
exports.updateAircraftVisuals = updateAircraftVisuals;
// Three distinct aircraft with very different flight characteristics
exports.AIRCRAFT_PROFILES = {
    // F-16 style fighter - high performance, agile
    fighter: {
        name: 'Viper F-16',
        mass: 9200,
        wingArea: 27.87,
        wingSpan: 13.0,
        referenceArea: 27.87,
        clMax: 1.8,
        clAlpha: 6.5,
        cl0: 0.0,
        cd0: 0.015,
        k: 0.04,
        maxThrust: 128000,
        thrustRollOff: 0.0065,
        engineResponseRate: 8000,
        pitchRate: 1.5,
        rollRate: 2.0,
        yawRate: 0.8,
        controlResponse: 4.0,
        ix: 8000,
        iy: 12000,
        iz: 18000,
        stallAngle: 0.44,
        color: 0x708090,
        accentColor: 0xff4444,
    },
    // A-10 style attack aircraft - heavy, slow, stable
    attack: {
        name: 'Thunderbolt A-10',
        mass: 13000,
        wingArea: 47.5,
        wingSpan: 17.5,
        referenceArea: 47.5,
        clMax: 2.2,
        clAlpha: 5.5,
        cl0: 0.1,
        cd0: 0.025,
        k: 0.05,
        maxThrust: 80000,
        thrustRollOff: 0.006,
        engineResponseRate: 4000,
        pitchRate: 0.8,
        rollRate: 1.2,
        yawRate: 0.5,
        controlResponse: 2.5,
        ix: 15000,
        iy: 22000,
        iz: 32000,
        stallAngle: 0.35,
        color: 0x556B2F,
        accentColor: 0xffaa00,
    },
    // Light sport aircraft - slow, easy to fly
    sport: {
        name: 'Skyhawk Cessna',
        mass: 1150,
        wingArea: 16.2,
        wingSpan: 11.0,
        referenceArea: 16.2,
        clMax: 1.5,
        clAlpha: 5.8,
        cl0: 0.2,
        cd0: 0.03,
        k: 0.06,
        maxThrust: 8000,
        thrustRollOff: 0.007,
        engineResponseRate: 2000,
        pitchRate: 0.6,
        rollRate: 0.8,
        yawRate: 0.3,
        controlResponse: 1.8,
        ix: 2000,
        iy: 3500,
        iz: 4500,
        stallAngle: 0.30,
        color: 0xffffff,
        accentColor: 0x0066cc,
    },
};
function createAircraftState(startPos) {
    return {
        position: startPos.clone(),
        orientation: new THREE.Quaternion(),
        velocity: new THREE.Vector3(),
        angularVelocity: new THREE.Vector3(),
        throttle: 0,
        currentThrust: 0,
        rpm: 0,
        angleOfAttack: 0,
        sideslipAngle: 0,
        airspeed: 0,
        dynamicPressure: 0,
        isStalled: false,
        gForce: 1,
        pitchInput: 0,
        rollInput: 0,
        yawInput: 0,
    };
}
// Build 3D model for an aircraft type
function buildAircraftModel(type) {
    const group = new THREE.Group();
    const profile = exports.AIRCRAFT_PROFILES[type];
    if (!profile)
        return group;
    const mainColor = profile.color;
    const accentColor = profile.accentColor;
    const matMain = new THREE.MeshStandardMaterial({
        color: mainColor,
        metalness: 0.6,
        roughness: 0.3,
    });
    const matAccent = new THREE.MeshStandardMaterial({
        color: accentColor,
        metalness: 0.5,
        roughness: 0.4,
    });
    const matGlass = new THREE.MeshStandardMaterial({
        color: 0x88ccff,
        metalness: 0.1,
        roughness: 0.1,
        transparent: true,
    });
    const matDark = new THREE.MeshStandardMaterial({
        color: 0x222222,
        metalness: 0.8,
        roughness: 0.2,
    });
    if (type === 'fighter') {
        buildFighter(group, matMain, matAccent, matGlass, matDark);
    }
    else if (type === 'attack') {
        buildAttack(group, matMain, matAccent, matGlass, matDark);
    }
    else {
        buildSport(group, matMain, matAccent, matGlass, matDark);
    }
    group.scale.set(1.5, 1.5, 1.5);
    return group;
}
function buildFighter(group, matMain, matAccent, matGlass, matDark) {
    // Fuselage - sleek delta wing fighter
    const fuselageGeo = new THREE.ConeGeometry(0.3, 4, 8);
    const fuselage = new THREE.Mesh(fuselageGeo, matMain);
    fuselage.rotation.x = Math.PI / 2;
    fuselage.position.z = -0.5;
    group.add(fuselage);
    // Nose cone
    const noseGeo = new THREE.ConeGeometry(0.15, 1.5, 8);
    const nose = new THREE.Mesh(noseGeo, matDark);
    nose.rotation.x = Math.PI / 2;
    nose.position.z = -2.5;
    group.add(nose);
    // Cockpit
    const cockpitGeo = new THREE.SphereGeometry(0.25, 8, 6, 0, Math.PI * 2, 0, Math.PI / 2);
    const cockpit = new THREE.Mesh(cockpitGeo, matGlass);
    cockpit.position.set(0, 0.2, -0.5);
    group.add(cockpit);
    // Main wings (delta)
    const wingShape = new THREE.Shape();
    wingShape.moveTo(0, 0);
    wingShape.lineTo(3, 1.5);
    wingShape.lineTo(2.5, 2.5);
    wingShape.lineTo(0, 2);
    wingShape.lineTo(0, 0);
    const wingGeo = new THREE.ExtrudeGeometry(wingShape, { depth: 0.05, bevelEnabled: false });
    const wingLeft = new THREE.Mesh(wingGeo, matMain);
    wingLeft.rotation.x = -Math.PI / 2;
    wingLeft.position.set(0, -0.1, -0.5);
    group.add(wingLeft);
    const wingRight = new THREE.Mesh(wingGeo, matMain);
    wingRight.rotation.x = -Math.PI / 2;
    wingRight.rotation.z = Math.PI;
    wingRight.position.set(0, -0.1, -0.5);
    group.add(wingRight);
    // Vertical stabilizer
    const vstabGeo = new THREE.BoxGeometry(0.05, 1.2, 1.0);
    const vstab = new THREE.Mesh(vstabGeo, matMain);
    vstab.position.set(0, 0.5, 1.0);
    group.add(vstab);
    // Engine nozzle
    const nozzleGeo = new THREE.CylinderGeometry(0.2, 0.25, 0.5, 8);
    const nozzle = new THREE.Mesh(nozzleGeo, matDark);
    nozzle.rotation.x = Math.PI / 2;
    nozzle.position.z = 1.5;
    group.add(nozzle);
    // Engine glow
    const glowGeo = new THREE.CircleGeometry(0.22, 8);
    const glowMat = new THREE.MeshBasicMaterial({ color: 0xff6600, transparent: true, opacity: 0.8 });
    const glow = new THREE.Mesh(glowGeo, glowMat);
    glow.position.z = 1.76;
    glow.name = 'engineGlow';
    group.add(glow);
    // Landing gear
    const gearGeo = new THREE.CylinderGeometry(0.03, 0.03, 0.5, 4);
    const gearMat = new THREE.MeshStandardMaterial({ color: 0x333333 });
    const gear1 = new THREE.Mesh(gearGeo, gearMat);
    gear1.position.set(-0.3, -0.4, -0.5);
    group.add(gear1);
    const gear2 = new THREE.Mesh(gearGeo, gearMat);
    gear2.position.set(0.3, -0.4, -0.5);
    group.add(gear2);
    const gear3 = new THREE.Mesh(gearGeo, gearMat);
    gear3.position.set(0, -0.3, 1.0);
    group.add(gear3);
}
function buildAttack(group, matMain, matAccent, matGlass, matDark) {
    // Fuselage - thick, straight wing
    const fuselageGeo = new THREE.BoxGeometry(0.8, 0.6, 5);
    const fuselage = new THREE.Mesh(fuselageGeo, matMain);
    fuselage.position.z = -0.5;
    group.add(fuselage);
    // Nose
    const noseGeo = new THREE.ConeGeometry(0.4, 1.5, 6);
    const nose = new THREE.Mesh(noseGeo, matMain);
    nose.rotation.x = Math.PI / 2;
    nose.position.z = -3;
    group.add(nose);
    // Cockpit (tandem, two seats)
    const cockpitGeo = new THREE.BoxGeometry(0.5, 0.4, 1.2);
    const cockpit = new THREE.Mesh(cockpitGeo, matGlass);
    cockpit.position.set(0, 0.4, -0.5);
    group.add(cockpit);
    // Straight wings (wide span)
    const wingGeo = new THREE.BoxGeometry(7, 0.1, 1.5);
    const wing = new THREE.Mesh(wingGeo, matMain);
    wing.position.set(0, -0.1, -0.5);
    group.add(wing);
    // Wingtip tanks
    const tankGeo = new THREE.CylinderGeometry(0.15, 0.15, 1.5, 6);
    const tankL = new THREE.Mesh(tankGeo, matAccent);
    tankL.rotation.x = Math.PI / 2;
    tankL.position.set(-3.5, -0.1, -0.5);
    group.add(tankL);
    const tankR = new THREE.Mesh(tankGeo, matAccent);
    tankR.rotation.x = Math.PI / 2;
    tankR.position.set(3.5, -0.1, -0.5);
    group.add(tankR);
    // Twin vertical stabilizers
    const vstabGeo = new THREE.BoxGeometry(0.08, 1.0, 1.2);
    const vs1 = new THREE.Mesh(vstabGeo, matMain);
    vs1.position.set(-0.5, 0.5, 1.5);
    vs1.rotation.z = 0.2;
    group.add(vs1);
    const vs2 = new THREE.Mesh(vstabGeo, matMain);
    vs2.position.set(0.5, 0.5, 1.5);
    vs2.rotation.z = -0.2;
    group.add(vs2);
    // Twin engines
    const engGeo = new THREE.CylinderGeometry(0.25, 0.3, 1.0, 8);
    const engL = new THREE.Mesh(engGeo, matDark);
    engL.rotation.x = Math.PI / 2;
    engL.position.set(-0.35, -0.1, 2);
    group.add(engL);
    const engR = new THREE.Mesh(engGeo, matDark);
    engR.rotation.x = Math.PI / 2;
    engR.position.set(0.35, -0.1, 2);
    group.add(engR);
    // Engine glows
    const glowGeo = new THREE.CircleGeometry(0.28, 8);
    const glowMat = new THREE.MeshBasicMaterial({ color: 0xff8800, transparent: true, opacity: 0.7 });
    const glowL = new THREE.Mesh(glowGeo, glowMat);
    glowL.position.set(-0.35, -0.1, 2.51);
    glowL.name = 'engineGlow';
    group.add(glowL);
    const glowR = new THREE.Mesh(glowGeo, glowMat);
    glowR.position.set(0.35, -0.1, 2.51);
    group.add(glowR);
    // Gun pod under fuselage
    const gunGeo = new THREE.CylinderGeometry(0.08, 0.08, 1.0, 6);
    const gun = new THREE.Mesh(gunGeo, matDark);
    gun.rotation.x = Math.PI / 2;
    gun.position.set(0, -0.4, -2.5);
    group.add(gun);
    // Landing gear
    const gearGeo = new THREE.CylinderGeometry(0.04, 0.04, 0.6, 4);
    const gearMat = new THREE.MeshStandardMaterial({ color: 0x333333 });
    const gear1 = new THREE.Mesh(gearGeo, gearMat);
    gear1.position.set(-1, -0.5, -1);
    group.add(gear1);
    const gear2 = new THREE.Mesh(gearGeo, gearMat);
    gear2.position.set(1, -0.5, -1);
    group.add(gear2);
    const gear3 = new THREE.Mesh(gearGeo, gearMat);
    gear3.position.set(0, -0.4, 1.5);
    group.add(gear3);
}
function buildSport(group, matMain, matAccent, matGlass, matDark) {
    // Fuselage - rounded, small
    const fuselageGeo = new THREE.CapsuleGeometry(0.3, 2.5, 8, 12);
    const fuselage = new THREE.Mesh(fuselageGeo, matMain);
    fuselage.rotation.x = Math.PI / 2;
    fuselage.position.z = -0.5;
    group.add(fuselage);
    // Nose cone
    const noseGeo = new THREE.SphereGeometry(0.25, 8, 6);
    const nose = new THREE.Mesh(noseGeo, matMain);
    nose.position.z = -1.5;
    group.add(nose);
    // Cockpit (large windows)
    const cockpitGeo = new THREE.SphereGeometry(0.35, 8, 6, 0, Math.PI * 2, 0, Math.PI * 0.6);
    const cockpit = new THREE.Mesh(cockpitGeo, matGlass);
    cockpit.position.set(0, 0.2, -0.8);
    group.add(cockpit);
    // Low-mounted wings
    const wingGeo = new THREE.BoxGeometry(4, 0.08, 0.8);
    const wing = new THREE.Mesh(wingGeo, matMain);
    wing.position.set(0, -0.15, -0.3);
    group.add(wing);
    // Wing struts
    const strutGeo = new THREE.CylinderGeometry(0.02, 0.02, 0.5, 4);
    const strutL = new THREE.Mesh(strutGeo, matDark);
    strutL.position.set(-1, -0.3, -0.3);
    group.add(strutL);
    const strutR = new THREE.Mesh(strutGeo, matDark);
    strutR.position.set(1, -0.3, -0.3);
    group.add(strutR);
    // Tail
    const hstabGeo = new THREE.BoxGeometry(1.5, 0.06, 0.5);
    const hstab = new THREE.Mesh(hstabGeo, matMain);
    hstab.position.set(0, 0.1, 1.5);
    group.add(hstab);
    const vstabGeo = new THREE.BoxGeometry(0.05, 0.8, 0.6);
    const vstab = new THREE.Mesh(vstabGeo, matMain);
    vstab.position.set(0, 0.4, 1.5);
    group.add(vstab);
    // Propeller
    const propGeo = new THREE.BoxGeometry(2, 0.05, 0.08);
    const propMat = new THREE.MeshStandardMaterial({ color: 0x444444, metalness: 0.8 });
    const prop = new THREE.Mesh(propGeo, propMat);
    prop.position.z = -1.8;
    prop.name = 'propeller';
    group.add(prop);
    // Prop hub
    const hubGeo = new THREE.CylinderGeometry(0.08, 0.08, 0.15, 6);
    const hub = new THREE.Mesh(hubGeo, matDark);
    hub.rotation.x = Math.PI / 2;
    hub.position.z = -1.8;
    group.add(hub);
    // Landing gear (tail dragger)
    const gearGeo = new THREE.CylinderGeometry(0.03, 0.03, 0.4, 4);
    const gearMat = new THREE.MeshStandardMaterial({ color: 0x333333 });
    const gear1 = new THREE.Mesh(gearGeo, gearMat);
    gear1.position.set(-0.4, -0.4, -0.3);
    group.add(gear1);
    const gear2 = new THREE.Mesh(gearGeo, gearMat);
    gear2.position.set(0.4, -0.4, -0.3);
    group.add(gear2);
    const gear3 = new THREE.Mesh(gearGeo, gearMat);
    gear3.position.set(0, -0.2, 1.7);
    group.add(gear3);
}
// Update visual elements (engine glow, propeller)
function updateAircraftVisuals(group, throttle, airspeed, type, time) {
    // Engine glow intensity
    const glows = group.children.filter(c => c.name === 'engineGlow');
    glows.forEach(glow => {
        const mesh = glow;
        const mat = mesh.material;
        if (mat) {
            const intensity = 0.2 + throttle * 0.8;
            mat.opacity = intensity;
            if (throttle > 0.7) {
                mat.color.set(0xff4400);
            }
            else if (throttle > 0.4) {
                mat.color.set(0xff8800);
            }
            else {
                mat.color.set(0xffaa44);
            }
        }
    });
    // Propeller rotation
    if (type === 'sport') {
        const prop = group.children.find(c => c.name === 'propeller');
        if (prop) {
            prop.rotation.z = time * airspeed * 0.5;
        }
    }
}
};

// ── module: src/terrain.ts ──
__mods["src/terrain.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TerrainManager = void 0;
exports.createWater = createWater;
exports.createRunway = createRunway;
exports.createTargetDrone = createTargetDrone;
// Procedural terrain generation with heightmap and biome coloring
const noise_1 = require("./noise");
const types_1 = require("./types");
const TERRAIN_CHUNK_SIZE = 2000;
const TERRAIN_RESOLUTION = 200;
const CHUNKS_AROUND = 3;
// Biome colors
const BIOME_COLORS = {
    [types_1.BiomeType.Water]: { top: 0x1a5276, bottom: 0x0e3652 },
    [types_1.BiomeType.Beach]: { top: 0xd4b483, bottom: 0xc4a473 },
    [types_1.BiomeType.Grassland]: { top: 0x4a7c3f, bottom: 0x3a6c2f },
    [types_1.BiomeType.Forest]: { top: 0x2d5a1e, bottom: 0x1d4a0e },
    [types_1.BiomeType.Mountain]: { top: 0x8b7355, bottom: 0x6b5335 },
    [types_1.BiomeType.Snow]: { top: 0xf0f0f0, bottom: 0xd0d0d0 },
};
class TerrainManager {
    constructor(scene) {
        this.chunks = new Map();
        this.scene = scene;
        this.playerPosition = new THREE.Vector3();
    }
    update(playerPos) {
        this.playerPosition.copy(playerPos);
        const playerChunkX = Math.floor(playerPos.x / TERRAIN_CHUNK_SIZE);
        const playerChunkZ = Math.floor(playerPos.z / TERRAIN_CHUNK_SIZE);
        // Determine which chunks should exist
        const neededKeys = new Set();
        for (let dx = -CHUNKS_AROUND; dx <= CHUNKS_AROUND; dx++) {
            for (let dz = -CHUNKS_AROUND; dz <= CHUNKS_AROUND; dz++) {
                const cx = playerChunkX + dx;
                const cz = playerChunkZ + dz;
                neededKeys.add(`${cx},${cz}`);
            }
        }
        // Remove far chunks
        for (const [key, mesh] of this.chunks) {
            if (!neededKeys.has(key)) {
                this.scene.remove(mesh);
                mesh.geometry.dispose();
                this.chunks.delete(key);
            }
        }
        // Add new chunks
        for (const key of neededKeys) {
            if (!this.chunks.has(key)) {
                const [cx, cz] = key.split(',').map(Number);
                const mesh = this.createChunk(cx, cz);
                this.scene.add(mesh);
                this.chunks.set(key, mesh);
            }
        }
    }
    createChunk(cx, cz) {
        const size = TERRAIN_CHUNK_SIZE;
        const res = TERRAIN_RESOLUTION;
        const segments = res;
        const baseX = cx * size;
        const baseZ = cz * size;
        const vertices = [];
        const colors = [];
        const indices = [];
        for (let iz = 0; iz <= segments; iz++) {
            for (let ix = 0; ix <= segments; ix++) {
                const wx = baseX + (ix / segments) * size;
                const wz = baseZ + (iz / segments) * size;
                const h = (0, noise_1.terrainHeight)(wx, wz);
                vertices.push(wx, h, wz);
                const biome = (0, noise_1.getBiome)(wx, wz);
                const colorData = BIOME_COLORS[biome] || BIOME_COLORS[types_1.BiomeType.Grassland];
                // Add some variation based on noise
                const variation = ((0, noise_1.terrainHeight)(wx + 10, wz + 10) - h) * 0.01;
                const c = new THREE.Color(colorData.top);
                c.r = Math.max(0, Math.min(1, c.r + variation));
                c.g = Math.max(0, Math.min(1, c.g + variation * 0.5));
                c.b = Math.max(0, Math.min(1, c.b + variation * 0.3));
                colors.push(c.r, c.g, c.b);
            }
        }
        for (let iz = 0; iz < segments; iz++) {
            for (let ix = 0; ix < segments; ix++) {
                const a = iz * (segments + 1) + ix;
                const b = a + 1;
                const c = a + segments + 1;
                const d = c + 1;
                indices.push(a, c, b);
                indices.push(b, c, d);
            }
        }
        const geometry = new THREE.BufferGeometry();
        geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3));
        geometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3));
        geometry.setIndex(indices);
        geometry.computeVertexNormals();
        const material = new THREE.MeshStandardMaterial({
            vertexColors: true,
            metalness: 0.1,
            roughness: 0.9,
            flatShading: true,
        });
        const mesh = new THREE.Mesh(geometry, material);
        mesh.receiveShadow = true;
        return mesh;
    }
    getHeightAt(x, z) {
        return (0, noise_1.terrainHeight)(x, z);
    }
    cleanup() {
        for (const [, mesh] of this.chunks) {
            this.scene.remove(mesh);
            mesh.geometry.dispose();
        }
        this.chunks.clear();
    }
}
exports.TerrainManager = TerrainManager;
// Water plane
function createWater(scene) {
    const geo = new THREE.PlaneGeometry(20000, 20000);
    const mat = new THREE.MeshStandardMaterial({
        color: 0x1a6baa,
        metalness: 0.8,
        roughness: 0.2,
        transparent: true,
        opacity: 0.7,
    });
    const water = new THREE.Mesh(geo, mat);
    water.rotation.x = -Math.PI / 2;
    water.position.y = -5;
    water.receiveShadow = true;
    scene.add(water);
    return water;
}
// Runway for takeoff/landing
function createRunway(scene, start, end, width = 40, length = 1000) {
    const geo = new THREE.PlaneGeometry(width, length);
    const mat = new THREE.MeshStandardMaterial({
        color: 0x333333,
        metalness: 0.1,
        roughness: 0.8,
    });
    const runway = new THREE.Mesh(geo, mat);
    const dir = new THREE.Vector3().subVectors(end, start).normalize();
    const mid = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
    runway.position.copy(mid);
    runway.position.y = (0, noise_1.terrainHeight)(mid.x, mid.z) + 0.5;
    // Rotate to align with runway direction
    const up = new THREE.Vector3(0, 1, 0);
    const right = new THREE.Vector3().crossVectors(dir, up).normalize();
    const newUp = new THREE.Vector3().crossVectors(right, dir).normalize();
    const matrix = new THREE.Matrix4();
    const basis = new THREE.Matrix4();
    basis.set(right.x, newUp.x, dir.x, 0, right.y, newUp.y, dir.y, 0, right.z, newUp.z, dir.z, 0, 0, 0, 0, 1);
    runway.rotation.setFromRotationMatrix(basis);
    runway.receiveShadow = true;
    scene.add(runway);
    // Add runway lights
    const lightGeo = new THREE.SphereGeometry(0.5, 4, 4);
    const lightMat = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    for (let i = 0; i < 10; i++) {
        const t = i / 9;
        const pos = new THREE.Vector3().lerpVectors(start, end, t);
        pos.y = (0, noise_1.terrainHeight)(pos.x, pos.z) + 1;
        const lightL = new THREE.Mesh(lightGeo, lightMat);
        lightL.position.copy(pos);
        lightL.position.x += right.x * (width / 2 + 2);
        lightL.position.z += right.z * (width / 2 + 2);
        scene.add(lightL);
        const lightR = new THREE.Mesh(lightGeo, lightMat.clone());
        lightR.position.copy(pos);
        lightR.position.x -= right.x * (width / 2 + 2);
        lightR.position.z -= right.z * (width / 2 + 2);
        scene.add(lightR);
    }
    // Threshold markings
    const markGeo = new THREE.PlaneGeometry(5, 30);
    const markMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
    const markStart = new THREE.Mesh(markGeo, markMat);
    markStart.position.copy(start);
    markStart.position.y = (0, noise_1.terrainHeight)(start.x, start.z) + 0.6;
    markStart.rotation.x = -Math.PI / 2;
    scene.add(markStart);
    return runway;
}
// Target drones for dogfight
function createTargetDrone(scene, position) {
    const group = new THREE.Group();
    // Body
    const bodyGeo = new THREE.CapsuleGeometry(0.5, 2, 6, 8);
    const bodyMat = new THREE.MeshStandardMaterial({
        color: 0xcc2222,
        metalness: 0.5,
        roughness: 0.4,
    });
    const body = new THREE.Mesh(bodyGeo, bodyMat);
    body.rotation.x = Math.PI / 2;
    group.add(body);
    // Wings
    const wingGeo = new THREE.BoxGeometry(3, 0.1, 0.8);
    const wingMat = new THREE.MeshStandardMaterial({
        color: 0xaa1111,
        metalness: 0.5,
        roughness: 0.4,
    });
    const wing = new THREE.Mesh(wingGeo, wingMat);
    wing.position.z = -0.3;
    group.add(wing);
    // Engine glow
    const glowGeo = new THREE.CircleGeometry(0.4, 8);
    const glowMat = new THREE.MeshBasicMaterial({ color: 0xff4400, transparent: true, opacity: 0.6 });
    const glow = new THREE.Mesh(glowGeo, glowMat);
    glow.position.z = 1.1;
    group.add(glow);
    group.position.copy(position);
    scene.add(group);
    return group.children[0];
}
};

// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInputState = createInputState;
exports.setupInput = setupInput;
exports.processInput = processInput;
function createInputState() {
    return {
        pitchUp: false, pitchDown: false,
        rollLeft: false, rollRight: false,
        yawLeft: false, yawRight: false,
        throttleUp: false, throttleDown: false,
        fire: false, fireHeld: false,
        keyW: false, keyS: false, keyA: false, keyD: false,
        keyQ: false, keyE: false, keySpace: false,
        keyR: false, keyT: false, keyEnter: false, keyEscape: false,
        mouseX: 0, mouseY: 0,
        mouseLeft: false, mouseRight: false,
        cameraPitch: 0, cameraYaw: 0,
    };
}
function setupInput(input, canvas) {
    const keyMap = {
        'w': 'keyW', 's': 'keyS', 'a': 'keyA', 'd': 'keyD',
        'q': 'keyQ', 'e': 'keyE', ' ': 'keySpace',
        'r': 'keyR', 't': 'keyT', 'Enter': 'keyEnter',
        'Escape': 'keyEscape',
    };
    window.addEventListener('keydown', (e) => {
        const key = e.key.toLowerCase();
        if (key in keyMap) {
            input[keyMap[key]] = true;
        }
        // Flight controls
        if (key === 'arrowup' || key === 'w')
            input.pitchUp = true;
        if (key === 'arrowdown' || key === 's')
            input.pitchDown = true;
        if (key === 'arrowleft' || key === 'a')
            input.rollLeft = true;
        if (key === 'arrowright' || key === 'd')
            input.rollRight = true;
        if (key === 'q')
            input.yawLeft = true;
        if (key === 'e')
            input.yawRight = true;
        if (key === 'shift')
            input.throttleUp = true;
        if (key === 'control')
            input.throttleDown = true;
        if (key === ' ')
            input.fire = true;
        e.preventDefault();
    });
    window.addEventListener('keyup', (e) => {
        const key = e.key.toLowerCase();
        if (key in keyMap) {
            input[keyMap[key]] = false;
        }
        if (key === 'arrowup' || key === 'w')
            input.pitchUp = false;
        if (key === 'arrowdown' || key === 's')
            input.pitchDown = false;
        if (key === 'arrowleft' || key === 'a')
            input.rollLeft = false;
        if (key === 'arrowright' || key === 'd')
            input.rollRight = false;
        if (key === 'q')
            input.yawLeft = false;
        if (key === 'e')
            input.yawRight = false;
        if (key === 'shift')
            input.throttleUp = false;
        if (key === 'control')
            input.throttleDown = false;
        if (key === ' ')
            input.fire = false;
        e.preventDefault();
    });
    canvas.addEventListener('mousedown', (e) => {
        if (e.button === 0)
            input.mouseLeft = true;
        if (e.button === 2)
            input.mouseRight = true;
    });
    canvas.addEventListener('mouseup', (e) => {
        if (e.button === 0)
            input.mouseLeft = false;
        if (e.button === 2)
            input.mouseRight = false;
    });
    canvas.addEventListener('mousemove', (e) => {
        input.mouseX = e.movementX || 0;
        input.mouseY = e.movementY || 0;
    });
    canvas.addEventListener('contextmenu', (e) => e.preventDefault());
    // Pointer lock for camera control
    canvas.addEventListener('click', () => {
        canvas.requestPointerLock();
    });
    document.addEventListener('pointerlockchange', () => {
        if (!document.pointerLockElement) {
            // Show pause overlay
        }
    });
}
function processInput(input, dt) {
    const sensitivity = 1.0;
    let pitch = 0;
    let roll = 0;
    let yaw = 0;
    let throttleDelta = 0;
    // Keyboard controls
    if (input.pitchUp)
        pitch -= sensitivity;
    if (input.pitchDown)
        pitch += sensitivity;
    if (input.rollLeft)
        roll -= sensitivity;
    if (input.rollRight)
        roll += sensitivity;
    if (input.yawLeft)
        yaw -= sensitivity;
    if (input.yawRight)
        yaw += sensitivity;
    // Throttle
    const throttleRate = 0.5 * dt;
    if (input.throttleUp)
        throttleDelta = throttleRate;
    if (input.throttleDown)
        throttleDelta = -throttleRate;
    // Mouse camera control (when pointer locked)
    if (document.pointerLockElement) {
        input.cameraYaw -= input.mouseX * 0.002;
        input.cameraPitch -= input.mouseY * 0.002;
        input.cameraPitch = Math.max(-Math.PI / 2.5, Math.min(Math.PI / 2.5, input.cameraPitch));
        input.mouseX = 0;
        input.mouseY = 0;
    }
    return { pitch, roll, yaw, throttleDelta };
}
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
// Audio system using Web Audio API
// Dynamic engine sound, wind noise, and environmental audio
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioSystem = void 0;
class AudioSystem {
    constructor() {
        this.ctx = null;
        this.engineGain = null;
        this.engineOsc = null;
        this.engineOsc2 = null;
        this.windGain = null;
        this.windNoise = null;
        this.masterGain = null;
        this.initialized = false;
        this.currentThrottle = 0;
        this.currentAirspeed = 0;
        this.currentAltitude = 0;
    }
    init() {
        if (this.initialized)
            return;
        try {
            this.ctx = new AudioContext();
            this.masterGain = this.ctx.createGain();
            this.masterGain.gain.value = 0.3;
            this.masterGain.connect(this.ctx.destination);
            // Engine sound - two oscillators for richness
            this.engineGain = this.ctx.createGain();
            this.engineGain.gain.value = 0;
            this.engineGain.connect(this.masterGain);
            this.engineOsc = this.ctx.createOscillator();
            this.engineOsc.type = 'sawtooth';
            this.engineOsc.frequency.value = 80;
            this.engineOsc.connect(this.engineGain);
            this.engineOsc.start();
            this.engineOsc2 = this.ctx.createOscillator();
            this.engineOsc2.type = 'square';
            this.engineOsc2.frequency.value = 40;
            const osc2Gain = this.ctx.createGain();
            osc2Gain.gain.value = 0.3;
            this.engineOsc2.connect(osc2Gain);
            osc2Gain.connect(this.engineGain);
            this.engineOsc2.start();
            // Wind noise
            this.windGain = this.ctx.createGain();
            this.windGain.gain.value = 0;
            this.windGain.connect(this.masterGain);
            const windFilter = this.ctx.createBiquadFilter();
            windFilter.type = 'lowpass';
            windFilter.frequency.value = 500;
            windFilter.connect(this.windGain);
            // Create noise buffer
            const bufferSize = this.ctx.sampleRate * 2;
            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.windNoise = this.ctx.createBufferSource();
            this.windNoise.buffer = buffer;
            this.windNoise.loop = true;
            this.windNoise.connect(windFilter);
            this.windNoise.start();
            this.initialized = true;
        }
        catch (e) {
            console.warn('Audio init failed:', e);
        }
    }
    update(throttle, airspeed, altitude, isStalled) {
        if (!this.initialized || !this.ctx)
            return;
        // Smooth transitions
        this.currentThrottle += (throttle - this.currentThrottle) * 0.1;
        this.currentAirspeed += (airspeed - this.currentAirspeed) * 0.05;
        this.currentAltitude += (altitude - this.currentAltitude) * 0.05;
        // Engine sound
        if (this.engineOsc && this.engineOsc2 && this.engineGain) {
            // Base frequency from throttle
            const baseFreq = 60 + this.currentThrottle * 200;
            // Add variation from airspeed
            const speedFreq = this.currentAirspeed * 0.5;
            this.engineOsc.frequency.value = Math.max(30, baseFreq + speedFreq);
            this.engineOsc2.frequency.value = Math.max(20, baseFreq * 0.5 + speedFreq * 0.3);
            // Volume from throttle and altitude
            const altFactor = Math.max(0.3, 1 - this.currentAltitude / 15000);
            this.engineGain.gain.value = this.currentThrottle * 0.6 * altFactor + 0.02;
        }
        // Wind noise
        if (this.windGain) {
            const windVolume = Math.min(0.4, this.currentAirspeed / 300);
            // Stall warning - increase wind noise
            const stallBoost = isStalled ? 0.3 : 0;
            this.windGain.gain.value = windVolume + stallBoost;
        }
        // Resume context if suspended (browser autoplay policy)
        if (this.ctx.state === 'suspended') {
            this.ctx.resume();
        }
    }
    playExplosion() {
        if (!this.initialized || !this.ctx || !this.masterGain)
            return;
        const bufferSize = 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++) {
            const t = i / this.ctx.sampleRate;
            data[i] = (Math.random() * 2 - 1) * Math.exp(-t * 3) * 2;
        }
        const source = this.ctx.createBufferSource();
        source.buffer = buffer;
        const filter = this.ctx.createBiquadFilter();
        filter.type = 'lowpass';
        filter.frequency.value = 200;
        const gain = this.ctx.createGain();
        gain.gain.value = 0.8;
        source.connect(filter);
        filter.connect(gain);
        gain.connect(this.masterGain);
        source.start();
    }
    playGunshot() {
        if (!this.initialized || !this.ctx || !this.masterGain)
            return;
        const osc = this.ctx.createOscillator();
        osc.type = 'sawtooth';
        osc.frequency.value = 200;
        osc.frequency.exponentialRampToValueAtTime(50, this.ctx.currentTime + 0.1);
        const gain = this.ctx.createGain();
        gain.gain.value = 0.3;
        gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.15);
        osc.connect(gain);
        gain.connect(this.masterGain);
        osc.start();
        osc.stop(this.ctx.currentTime + 0.15);
    }
    playStallWarning() {
        if (!this.initialized || !this.ctx || !this.masterGain)
            return;
        const osc = this.ctx.createOscillator();
        osc.type = 'sine';
        osc.frequency.value = 800;
        const gain = this.ctx.createGain();
        gain.gain.value = 0.15;
        gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.3);
        osc.connect(gain);
        gain.connect(this.masterGain);
        osc.start();
        osc.stop(this.ctx.currentTime + 0.3);
    }
    setVolume(v) {
        if (this.masterGain) {
            this.masterGain.gain.value = v;
        }
    }
    dispose() {
        if (this.engineOsc)
            this.engineOsc.stop();
        if (this.engineOsc2)
            this.engineOsc2.stop();
        if (this.windNoise)
            this.windNoise.stop();
        if (this.ctx)
            this.ctx.close();
        this.initialized = false;
    }
}
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 overlay rendering on a 2D canvas
const types_1 = require("./types");
class HUD {
    constructor(canvas) {
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.width = canvas.width;
        this.height = canvas.height;
    }
    resize(w, h) {
        this.width = w;
        this.height = h;
        this.canvas.width = w;
        this.canvas.height = h;
    }
    render(state, profile, gameState, dogfightState, time, selectedAircraft) {
        const ctx = this.ctx;
        const w = this.width;
        const h = this.height;
        ctx.clearRect(0, 0, w, h);
        if (gameState === types_1.GameState.Menu) {
            this.renderMenu(ctx, w, h, time);
            return;
        }
        if (gameState === types_1.GameState.Crashed) {
            this.renderCrashScreen(ctx, w, h, time);
            return;
        }
        this.renderFlightHUD(ctx, w, h, state, profile, time);
        if (gameState === types_1.GameState.TakeoffLanding) {
            this.renderTakeoffHUD(ctx, w, h, state, time);
        }
        if (gameState === types_1.GameState.Dogfight && dogfightState) {
            this.renderDogfightHUD(ctx, w, h, dogfightState, state, time);
        }
    }
    renderMenu(ctx, w, h, time) {
        // Background gradient
        const grad = ctx.createLinearGradient(0, 0, 0, h);
        grad.addColorStop(0, '#0a0a2e');
        grad.addColorStop(1, '#1a1a4e');
        ctx.fillStyle = grad;
        ctx.fillRect(0, 0, w, h);
        // Stars
        ctx.fillStyle = 'rgba(255,255,255,0.5)';
        for (let i = 0; i < 100; i++) {
            const x = (Math.sin(i * 127.1 + time * 0.01) * 0.5 + 0.5) * w;
            const y = (Math.cos(i * 311.7 + time * 0.005) * 0.5 + 0.5) * h;
            const size = Math.sin(time * 2 + i) * 0.5 + 1;
            ctx.fillRect(x, y, size, size);
        }
        // Title
        ctx.textAlign = 'center';
        ctx.fillStyle = '#ffffff';
        ctx.font = 'bold 64px Arial';
        ctx.fillText('APEX AERO', w / 2, h * 0.25);
        ctx.font = '24px Arial';
        ctx.fillStyle = '#88aacc';
        ctx.fillText('Flight Simulator', w / 2, h * 0.32);
        // Aircraft selection
        const aircraft = ['fighter', 'attack', 'sport'];
        const names = ['Viper F-16', 'Thunderbolt A-10', 'Skyhawk Cessna'];
        const desc = ['Agile fighter jet', 'Heavy attack aircraft', 'Light sport plane'];
        for (let i = 0; i < 3; i++) {
            const y = h * 0.45 + i * 80;
            ctx.fillStyle = 'rgba(255,255,255,0.1)';
            ctx.fillRect(w * 0.2, y, w * 0.6, 60);
            ctx.fillStyle = '#ffffff';
            ctx.font = 'bold 20px Arial';
            ctx.textAlign = 'left';
            ctx.fillText(`[${i + 1}] ${names[i]}`, w * 0.25, y + 25);
            ctx.fillStyle = '#88aacc';
            ctx.font = '14px Arial';
            ctx.fillText(desc[i], w * 0.25, y + 45);
        }
        // Mode selection
        ctx.textAlign = 'center';
        ctx.fillStyle = '#aaddff';
        ctx.font = '18px Arial';
        ctx.fillText('[T] Takeoff/Landing  [D] Dogfight', w / 2, h * 0.8);
        ctx.fillText('Press corresponding key to select', w / 2, h * 0.85);
        // Controls help
        ctx.fillStyle = '#668899';
        ctx.font = '12px Arial';
        ctx.fillText('W/S: Pitch | A/D: Roll | Q/E: Yaw | Shift/Ctrl: Throttle | Space: Fire', w / 2, h * 0.95);
    }
    renderFlightHUD(ctx, w, h, state, profile, time) {
        ctx.textAlign = 'center';
        ctx.font = '14px monospace';
        // Crosshair
        const cx = w / 2;
        const cy = h / 2;
        ctx.strokeStyle = 'rgba(0, 255, 0, 0.7)';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(cx - 20, cy);
        ctx.lineTo(cx - 8, cy);
        ctx.moveTo(cx + 8, cy);
        ctx.lineTo(cx + 20, cy);
        ctx.moveTo(cx, cy - 20);
        ctx.lineTo(cx, cy - 8);
        ctx.moveTo(cx, cy + 8);
        ctx.lineTo(cx, cy + 20);
        ctx.stroke();
        // Pitch ladder
        ctx.strokeStyle = 'rgba(0, 255, 0, 0.4)';
        ctx.fillStyle = 'rgba(0, 255, 0, 0.6)';
        ctx.font = '10px monospace';
        for (let deg = -30; deg <= 30; deg += 10) {
            if (deg === 0)
                continue;
            const yOff = deg * 3;
            const y = cy + yOff;
            const lineW = Math.abs(deg) === 10 ? 40 : 20;
            ctx.beginPath();
            ctx.moveTo(cx - lineW, y);
            ctx.lineTo(cx + lineW, y);
            ctx.stroke();
            if (Math.abs(deg) === 10) {
                ctx.fillText(`${Math.abs(deg)}°`, cx + 50, y + 3);
            }
        }
        // Airspeed (left side)
        const speedKnots = Math.round(state.airspeed * 1.94384);
        ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
        ctx.fillRect(20, h / 2 - 100, 80, 200);
        ctx.strokeStyle = 'rgba(0, 255, 0, 0.5)';
        ctx.strokeRect(20, h / 2 - 100, 80, 200);
        ctx.fillStyle = '#00ff00';
        ctx.font = 'bold 24px monospace';
        ctx.fillText(`${speedKnots}`, 60, h / 2 - 20);
        ctx.font = '10px monospace';
        ctx.fillText('KTS', 60, h / 2);
        // Speed tape
        for (let i = -5; i <= 5; i++) {
            const spd = speedKnots + i * 20;
            if (spd < 0)
                continue;
            const y = h / 2 - 40 - i * 16;
            ctx.font = '9px monospace';
            ctx.fillText(`${spd}`, 60, y);
        }
        // Altitude (right side)
        const alt = Math.round(state.position.y);
        ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
        ctx.fillRect(w - 100, h / 2 - 100, 80, 200);
        ctx.strokeStyle = 'rgba(0, 255, 0, 0.5)';
        ctx.strokeRect(w - 100, h / 2 - 100, 80, 200);
        ctx.fillStyle = '#00ff00';
        ctx.font = 'bold 24px monospace';
        ctx.fillText(`${alt}`, w - 60, h / 2 - 20);
        ctx.font = '10px monospace';
        ctx.fillText('FT', w - 60, h / 2);
        // Altitude tape
        for (let i = -5; i <= 5; i++) {
            const a = alt + i * 100;
            const y = h / 2 - 40 - i * 16;
            ctx.font = '9px monospace';
            ctx.fillText(`${a}`, w - 60, y);
        }
        // Heading
        const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(state.orientation);
        const heading = Math.atan2(forward.x, forward.z) * 180 / Math.PI;
        const headingNorm = ((heading % 360) + 360) % 360;
        ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
        ctx.fillRect(w / 2 - 60, 10, 120, 30);
        ctx.fillStyle = '#00ff00';
        ctx.font = 'bold 16px monospace';
        ctx.fillText(`${headingNorm.toFixed(0)}°`, w / 2, 30);
        // G-Force indicator (bottom center)
        ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
        ctx.fillRect(w / 2 - 50, h - 60, 100, 40);
        ctx.fillStyle = state.gForce > 3 ? '#ff4444' : state.gForce > 2 ? '#ffaa00' : '#00ff00';
        ctx.font = 'bold 14px monospace';
        ctx.fillText(`G: ${state.gForce.toFixed(1)}`, w / 2, h - 35);
        // Throttle bar (bottom right)
        const throttleW = 120;
        const throttleH = 15;
        ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
        ctx.fillRect(w - throttleW - 20, h - 60, throttleW, throttleH);
        const throttleColor = state.throttle > 0.8 ? '#ff4444' : state.throttle > 0.5 ? '#ffaa00' : '#44ff44';
        ctx.fillStyle = throttleColor;
        ctx.fillRect(w - throttleW - 20, h - 60, throttleW * state.throttle, throttleH);
        ctx.fillStyle = '#00ff00';
        ctx.font = '10px monospace';
        ctx.fillText(`THR: ${Math.round(state.throttle * 100)}%`, w - throttleW / 2 - 20, h - 40);
        // Stall warning
        if (state.isStalled) {
            const flash = Math.sin(time * 10) > 0;
            if (flash) {
                ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
                ctx.fillRect(0, 0, w, h);
                ctx.fillStyle = '#ff0000';
                ctx.font = 'bold 36px Arial';
                ctx.textAlign = 'center';
                ctx.fillText('⚠ STALL ⚠', w / 2, h / 2 + 60);
            }
        }
        // Aircraft name (top left)
        ctx.textAlign = 'left';
        ctx.fillStyle = '#88aacc';
        ctx.font = '12px Arial';
        ctx.fillText(profile.name, 20, 30);
        // Mach number
        const mach = state.airspeed / 340;
        ctx.fillText(`M: ${mach.toFixed(2)}`, 20, 48);
        // Angle of attack
        ctx.fillText(`AoA: ${(state.angleOfAttack * 180 / Math.PI).toFixed(1)}°`, 20, 66);
    }
    renderTakeoffHUD(ctx, w, h, state, time) {
        ctx.textAlign = 'center';
        ctx.fillStyle = '#ffdd44';
        ctx.font = '16px Arial';
        // Phase indicator
        let phase = 'FLIGHT';
        if (state.position.y < 20 && state.airspeed < 30)
            phase = 'TAXI';
        else if (state.position.y < 50 && state.airspeed > 30)
            phase = 'TAKEOFF';
        else if (state.position.y > 200)
            phase = 'CRUISE';
        else if (state.position.y < 100 && state.velocity.y > 0)
            phase = 'APPROACH';
        ctx.fillText(`Phase: ${phase}`, w / 2, h - 80);
        // Vertical speed
        const vsi = state.velocity.y * 196.85; // ft/min
        ctx.fillText(`VSI: ${vsi.toFixed(0)} ft/min`, w / 2, h - 95);
    }
    renderDogfightHUD(ctx, w, h, ds, state, time) {
        ctx.textAlign = 'center';
        // Score
        ctx.fillStyle = '#ffdd44';
        ctx.font = 'bold 18px Arial';
        ctx.fillText(`Score: ${ds.score}`, w / 2, 60);
        // Time remaining
        ctx.fillStyle = ds.timeRemaining < 30 ? '#ff4444' : '#00ff00';
        ctx.fillText(`Time: ${ds.timeRemaining.toFixed(0)}s`, w / 2, 80);
        // Health bar
        const healthW = 200;
        const healthH = 15;
        const healthX = w / 2 - healthW / 2;
        const healthY = h - 90;
        ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
        ctx.fillRect(healthX, healthY, healthW, healthH);
        const healthPct = ds.playerHealth / 100;
        ctx.fillStyle = healthPct > 0.5 ? '#44ff44' : healthPct > 0.25 ? '#ffaa00' : '#ff4444';
        ctx.fillRect(healthX, healthY, healthW * healthPct, healthH);
        ctx.strokeStyle = '#ffffff';
        ctx.strokeRect(healthX, healthY, healthW, healthH);
        ctx.fillStyle = '#ffffff';
        ctx.font = '11px monospace';
        ctx.fillText(`HP: ${ds.playerHealth}`, w / 2, healthY + 12);
        // Ammo
        ctx.fillText(`Ammo: ${ds.ammo}/${ds.maxAmmo}`, w / 2, h - 110);
        // Target count
        const alive = ds.targets.filter(t => t.isAlive).length;
        ctx.fillStyle = '#ff6666';
        ctx.fillText(`Targets: ${alive}`, w / 2, h - 130);
        // Target reticle when aiming at enemy
        if (alive > 0) {
            ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
            ctx.lineWidth = 2;
            const cx = w / 2, cy = h / 2;
            ctx.beginPath();
            ctx.arc(cx, cy, 35, 0, Math.PI * 2);
            ctx.stroke();
            // Corner brackets
            const s = 15;
            ctx.beginPath();
            ctx.moveTo(cx - 35, cy - s);
            ctx.lineTo(cx - 35, cy + s);
            ctx.moveTo(cx + 35, cy - s);
            ctx.lineTo(cx + 35, cy + s);
            ctx.moveTo(cx - s, cy - 35);
            ctx.lineTo(cx + s, cy - 35);
            ctx.moveTo(cx - s, cy + 35);
            ctx.lineTo(cx + s, cy + 35);
            ctx.stroke();
        }
    }
    renderCrashScreen(ctx, w, h, time) {
        ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
        ctx.fillRect(0, 0, w, h);
        ctx.textAlign = 'center';
        ctx.fillStyle = '#ff4444';
        ctx.font = 'bold 48px Arial';
        ctx.fillText('CRASHED', w / 2, h / 2 - 30);
        ctx.fillStyle = '#ffffff';
        ctx.font = '20px Arial';
        ctx.fillText('Press R to restart', w / 2, h / 2 + 20);
        ctx.fillText('Press ESC for menu', w / 2, h / 2 + 50);
        // Flicker effect
        if (Math.sin(time * 5) > 0) {
            ctx.fillStyle = 'rgba(255, 100, 0, 0.2)';
            ctx.fillRect(0, 0, w, h);
        }
    }
}
exports.HUD = HUD;
};

// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GameManager = void 0;
// Game mode management
const types_1 = require("./types");
const noise_1 = require("./noise");
const physics_1 = require("./physics");
class GameManager {
    constructor(scene, audio) {
        this.gameState = types_1.GameState.Menu;
        this.selectedAircraft = 'fighter';
        this.takeoffState = null;
        this.dogfightState = null;
        this.particles = [];
        this.scene = scene;
        this.audio = audio;
    }
    get state() { return this.gameState; }
    get selectedAircraftType() { return this.selectedAircraft; }
    get dogfight() { return this.dogfightState; }
    selectAircraft(index) {
        const types = ['fighter', 'attack', 'sport'];
        if (index >= 0 && index < types.length) {
            this.selectedAircraft = types[index];
        }
    }
    startTakeoffLanding() {
        this.gameState = types_1.GameState.TakeoffLanding;
        this.takeoffState = {
            runwayStart: new THREE.Vector3(-200, 0, 0),
            runwayEnd: new THREE.Vector3(800, 0, 0),
            runwayWidth: 40,
            runwayLength: 1000,
            phase: 'taxi',
            score: 0,
        };
        this.particles = [];
    }
    startDogfight() {
        this.gameState = types_1.GameState.Dogfight;
        this.dogfightState = {
            targets: [],
            playerHealth: 100,
            score: 0,
            ammo: 30,
            maxAmmo: 30,
            lastShot: 0,
            timeLimit: 180,
            timeRemaining: 180,
        };
        this.particles = [];
        // Spawn targets
        for (let i = 0; i < 5; i++) {
            const angle = (i / 5) * Math.PI * 2;
            const dist = 500 + Math.random() * 500;
            const pos = new THREE.Vector3(Math.cos(angle) * dist, 200 + Math.random() * 400, Math.sin(angle) * dist);
            this.dogfightState.targets.push({
                position: pos,
                velocity: new THREE.Vector3((Math.random() - 0.5) * 50, 0, (Math.random() - 0.5) * 50),
                health: 100,
                maxHealth: 100,
                isAlive: true,
                orientation: new THREE.Quaternion(),
                mesh: null,
                lastShot: 0,
            });
        }
    }
    update(aircraftState, profile, input, dt, time) {
        if (this.gameState === types_1.GameState.Menu) {
            this.handleMenuInput(input);
            return;
        }
        if (this.gameState === types_1.GameState.Crashed) {
            this.handleCrashInput(input);
            return;
        }
        // Update physics
        (0, physics_1.updatePhysics)(aircraftState, profile, dt);
        // Update particles
        this.particles = (0, physics_1.updateParticles)(this.particles, dt);
        // Contrails
        const contrail = (0, physics_1.createContrail)(aircraftState.position, aircraftState.velocity, aircraftState.position.y);
        if (contrail)
            this.particles.push(contrail);
        // Terrain collision
        const groundHeight = (0, noise_1.terrainHeight)(aircraftState.position.x, aircraftState.position.z);
        if ((0, physics_1.checkTerrainCollision)(aircraftState.position, groundHeight, 5)) {
            this.crash();
            return;
        }
        // Mode-specific updates
        if (this.gameState === types_1.GameState.TakeoffLanding) {
            this.updateTakeoffLanding(aircraftState, dt, time);
        }
        if (this.gameState === types_1.GameState.Dogfight && this.dogfightState) {
            this.updateDogfight(aircraftState, input, dt, time);
        }
        // Update audio
        this.audio.update(aircraftState.throttle, aircraftState.airspeed, aircraftState.position.y, aircraftState.isStalled);
    }
    handleMenuInput(input) {
        // Aircraft selection: 1, 2, 3 keys or a/s/d
        if (input.keyA || input.keyW)
            this.selectAircraft(0); // fighter
        if (input.keyS)
            this.selectAircraft(1); // attack
        if (input.keyD || input.keyE)
            this.selectAircraft(2); // sport
        // Start game: T for takeoff, Enter for dogfight, Space for quick start
        if (input.keyT) {
            this.startTakeoffLanding();
        }
        if (input.keyEnter || input.keySpace) {
            this.startDogfight();
        }
    }
    handleCrashInput(input) {
        if (input.keyR) {
            this.gameState = types_1.GameState.Menu;
        }
        if (input.keyEscape) {
            this.gameState = types_1.GameState.Menu;
        }
    }
    updateTakeoffLanding(state, dt, time) {
        if (!this.takeoffState)
            return;
        const ts = this.takeoffState;
        const alt = state.position.y;
        const speed = state.airspeed;
        // Phase detection
        if (alt < 20 && speed < 30) {
            ts.phase = 'taxi';
        }
        else if (alt < 50 && speed > 30) {
            ts.phase = 'takeoff';
        }
        else if (alt > 200) {
            ts.phase = 'flight';
        }
        else if (alt < 100 && state.velocity.y > 0) {
            ts.phase = 'approach';
        }
        // Score based on smooth landing
        if (ts.phase === 'taxi' && alt < 5 && speed < 5) {
            ts.score += dt * 10;
        }
    }
    updateDogfight(state, input, dt, time) {
        if (!this.dogfightState)
            return;
        const ds = this.dogfightState;
        // Timer
        ds.timeRemaining -= dt;
        if (ds.timeRemaining <= 0) {
            ds.timeRemaining = 0;
            // Time's up - check if all targets destroyed
            const alive = ds.targets.filter(t => t.isAlive).length;
            if (alive === 0) {
                ds.score += 1000;
            }
        }
        // Shooting
        if (input.fire && time - ds.lastShot > 0.15 && ds.ammo > 0) {
            ds.lastShot = time;
            ds.ammo--;
            this.audio.playGunshot();
            this.shootAtTargets(state, ds);
        }
        // Ammo reload
        if (ds.ammo <= 0 && time - ds.lastShot > 3) {
            ds.ammo = ds.maxAmmo;
        }
        // Update target AI
        ds.targets.forEach(target => {
            if (!target.isAlive)
                return;
            this.updateTargetAI(target, state, dt, time);
        });
        // Target shooting at player
        ds.targets.forEach(target => {
            if (!target.isAlive)
                return;
            const dist = target.position.distanceTo(state.position);
            if (dist < 500 && time - target.lastShot > 2) {
                target.lastShot = time;
                // Check line of sight
                const dir = new THREE.Vector3().subVectors(state.position, target.position).normalize();
                // Simple hit check
                if (Math.random() < 0.15) {
                    ds.playerHealth -= 10;
                    if (ds.playerHealth <= 0) {
                        ds.playerHealth = 0;
                        this.crash();
                    }
                }
            }
        });
    }
    shootAtTargets(state, ds) {
        const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(state.orientation);
        ds.targets.forEach(target => {
            if (!target.isAlive)
                return;
            const dist = target.position.distanceTo(state.position);
            if (dist > 800)
                return;
            // Check if target is in front
            const toTarget = new THREE.Vector3().subVectors(target.position, state.position).normalize();
            const dot = forward.dot(toTarget);
            if (dot > 0.9) { // Within narrow cone
                target.health -= 34;
                if (target.health <= 0) {
                    target.isAlive = false;
                    target.health = 0;
                    ds.score += 200;
                    this.audio.playExplosion();
                    // Create explosion particles
                    this.particles.push(...(0, physics_1.createCrashParticles)(target.position, 30));
                }
            }
        });
    }
    updateTargetAI(target, playerState, dt, time) {
        // Simple AI: fly in circles and occasionally chase player
        const distToPlayer = target.position.distanceTo(playerState.position);
        if (distToPlayer < 1000) {
            // Chase player
            const toPlayer = new THREE.Vector3().subVectors(playerState.position, target.position).normalize();
            target.velocity.x += toPlayer.x * 10 * dt;
            target.velocity.z += toPlayer.z * 10 * dt;
        }
        else {
            // Patrol pattern
            const angle = time * 0.3;
            const patrolRadius = 300;
            const patrolX = Math.cos(angle) * patrolRadius;
            const patrolZ = Math.sin(angle) * patrolRadius;
            target.velocity.x += (patrolX - target.velocity.x) * 0.01;
            target.velocity.z += (patrolZ - target.velocity.z) * 0.01;
        }
        // Keep altitude
        if (target.position.y < 200) {
            target.velocity.y += 5 * dt;
        }
        else if (target.position.y > 600) {
            target.velocity.y -= 5 * dt;
        }
        // Clamp velocity
        const maxSpeed = 100;
        if (target.velocity.length() > maxSpeed) {
            target.velocity.normalize().multiplyScalar(maxSpeed);
        }
        // Update position
        target.position.x += target.velocity.x * dt;
        target.position.y += target.velocity.y * dt;
        target.position.z += target.velocity.z * dt;
        // Ground collision
        const groundH = (0, noise_1.terrainHeight)(target.position.x, target.position.z);
        if (target.position.y < groundH + 20) {
            target.position.y = groundH + 20;
            target.velocity.y = Math.abs(target.velocity.y);
        }
        // Update orientation to face velocity direction
        if (target.velocity.length() > 1) {
            const velDir = target.velocity.clone().normalize();
            const forward = new THREE.Vector3(0, 0, -1);
            const quat = new THREE.Quaternion().setFromUnitVectors(forward, velDir);
            target.orientation.copy(quat);
        }
    }
    crash() {
        this.gameState = types_1.GameState.Crashed;
        this.audio.playExplosion();
    }
    getParticles() {
        return this.particles;
    }
    resetToMenu() {
        this.gameState = types_1.GameState.Menu;
        this.takeoffState = null;
        this.dogfightState = null;
        this.particles = [];
    }
}
exports.GameManager = GameManager;
};

// ── module: src/atmosphere.ts ──
__mods["src/atmosphere.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createClouds = createClouds;
exports.createSun = createSun;
exports.createSkyDome = createSkyDome;
exports.updateSkyDome = updateSkyDome;
function createClouds(scene) {
    const cloudGroup = new THREE.Group();
    const cloudMat = new THREE.MeshStandardMaterial({
        color: 0xffffff,
        transparent: true,
        opacity: 0.7,
        metalness: 0,
        roughness: 1,
    });
    // Create cloud clusters
    for (let i = 0; i < 80; i++) {
        const cloud = createCloudCluster(cloudMat);
        const x = (Math.random() - 0.5) * 8000;
        const z = (Math.random() - 0.5) * 8000;
        const y = 500 + Math.random() * 1500;
        cloud.position.set(x, y, z);
        cloudGroup.add(cloud);
    }
    scene.add(cloudGroup);
    return cloudGroup;
}
function createCloudCluster(mat) {
    const group = new THREE.Group();
    const numPuffs = 3 + Math.floor(Math.random() * 5);
    for (let i = 0; i < numPuffs; i++) {
        const size = 30 + Math.random() * 60;
        const geo = new THREE.SphereGeometry(size, 7, 5);
        const puff = new THREE.Mesh(geo, mat);
        puff.position.set((Math.random() - 0.5) * 80, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 80);
        group.add(puff);
    }
    return group;
}
function createSun(scene) {
    // Sun disc
    const sunGeo = new THREE.SphereGeometry(100, 16, 16);
    const sunMat = new THREE.MeshBasicMaterial({
        color: 0xffffcc,
        transparent: true,
        opacity: 0.9,
    });
    const sun = new THREE.Mesh(sunGeo, sunMat);
    sun.position.set(2000, 3000, 3000);
    scene.add(sun);
    // Sun glow
    const glowGeo = new THREE.SphereGeometry(200, 16, 16);
    const glowMat = new THREE.MeshBasicMaterial({
        color: 0xffffaa,
        transparent: true,
        opacity: 0.15,
    });
    const glow = new THREE.Mesh(glowGeo, glowMat);
    glow.position.copy(sun.position);
    scene.add(glow);
    return sun;
}
// Create a sky dome with gradient
function createSkyDome(scene) {
    const skyGeo = new THREE.SphereGeometry(20000, 32, 15);
    const skyMat = new THREE.ShaderMaterial({
        side: THREE.BackSide,
        uniforms: {
            topColor: { value: new THREE.Color(0x0077ff) },
            bottomColor: { value: new THREE.Color(0xaaddff) },
            offset: { value: 4 },
            exponent: { value: 0.6 },
        },
        vertexShader: `
      varying vec3 vWorldPosition;
      void main() {
        vec4 worldPosition = modelMatrix * vec4(position, 1.0);
        vWorldPosition = worldPosition.xyz;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    `,
        fragmentShader: `
      uniform vec3 topColor;
      uniform vec3 bottomColor;
      uniform float offset;
      uniform float exponent;
      varying vec3 vWorldPosition;
      void main() {
        float h = normalize(vWorldPosition + offset).y;
        gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h, 0.0), exponent), 0.0)), 1.0);
      }
    `,
    });
    const sky = new THREE.Mesh(skyGeo, skyMat);
    scene.add(sky);
    return sky;
}
// Update sky dome to follow camera
function updateSkyDome(sky, cameraPos) {
    sky.position.copy(cameraPos);
}
};

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