Flight Simulator
ThinkingCap Qwen3.6 27B GGUF · typescript

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 53,957 bytes · SHA-256 85395367d573
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Apex Aero — Flight Simulator</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; font-family: 'Segoe UI', 'Roboto', sans-serif; }
#app { width: 100%; height: 100%; position: relative; }
#gameCanvas { display: block; width: 100%; height: 100%; }
/* HUD */
#hud { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; display: none; }
#hud-panel { width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: space-between; padding: 15px 20px; }
#hud-top { display: flex; justify-content: space-between; font-size: 16px; font-family: 'Courier New', monospace; color: #0f0; text-shadow: 0 0 8px rgba(0,255,0,0.5); font-weight: bold; }
#hud-center { display: flex; flex-direction: column; align-items: center; justify-content: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
#hud-crosshair { color: rgba(0,255,0,0.5); font-size: 48px; font-weight: bold; }
#hud-stall { color: #ff0000; font-size: 28px; font-weight: bold; font-family: monospace; text-shadow: 0 0 15px red; margin-top: 20px; }
#hud-bottom { display: flex; justify-content: space-between; font-size: 14px; font-family: 'Courier New', monospace; color: #0f0; text-shadow: 0 0 8px rgba(0,255,0,0.5); }
#hud-mission { color: #ffff00; }
/* Menu */
#menu { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; z-index: 100; }
#menu-container { background: rgba(10,15,30,0.95); border: 2px solid rgba(0,255,100,0.3); border-radius: 16px; padding: 40px 50px; text-align: center; max-width: 600px; box-shadow: 0 0 60px rgba(0,255,100,0.1); }
#menu-title { font-size: 48px; color: #0f0; text-shadow: 0 0 30px rgba(0,255,0,0.5); letter-spacing: 8px; margin-bottom: 5px; }
#menu-subtitle { font-size: 18px; color: #0a6; margin-bottom: 30px; letter-spacing: 3px; }
#aircraft-select h2, #mission-select h2 { color: #8f8; margin: 20px 0 15px; font-size: 18px; letter-spacing: 2px; }
#aircraft-cards { display: flex; gap: 10px; justify-content: center; margin-bottom: 20px; }
.acard { background: rgba(0,255,100,0.05); border: 1px solid rgba(0,255,100,0.2); border-radius: 8px; padding: 12px 16px; cursor: pointer; transition: all 0.3s; min-width: 160px; }
.acard:hover { background: rgba(0,255,100,0.15); border-color: rgba(0,255,100,0.5); }
.acard.active { background: rgba(0,255,100,0.2); border-color: #0f0; box-shadow: 0 0 20px rgba(0,255,0,0.2); }
.acard-name { color: #0f0; font-weight: bold; font-size: 16px; }
.acard-desc { color: #aaa; font-size: 12px; margin: 6px 0; }
.acard-stats { color: #6a6; font-size: 11px; font-family: monospace; }
#mission-select button { background: rgba(0,255,100,0.1); border: 1px solid rgba(0,255,100,0.4); color: #0f0; border-radius: 8px; transition: all 0.3s; font-family: inherit; }
#mission-select button:hover { background: rgba(0,255,100,0.25); border-color: #0f0; box-shadow: 0 0 20px rgba(0,255,0,0.3); transform: scale(1.05); }
</style>
</head>
<body>
<div id="app">
<canvas id="gameCanvas"></canvas>
<div id="hud"></div>
<div id="menu"></div>
<div id="minimap"></div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./types":"src/types.ts","./aircraft":"src/aircraft.ts","./physics":"src/physics.ts","./terrain":"src/terrain.ts","./audio":"src/audio.ts","./input":"src/input.ts","./models":"src/models.ts","./hud":"src/hud.ts","./mission":"src/mission.ts"},"src/aircraft.ts":{"./types":"src/types.ts"},"src/physics.ts":{"./types":"src/types.ts"},"src/terrain.ts":{"./types":"src/types.ts"},"src/input.ts":{"./types":"src/types.ts"},"src/models.ts":{"./types":"src/types.ts"},"src/hud.ts":{"./types":"src/types.ts"},"src/mission.ts":{"./types":"src/types.ts"}};
function __require(id) {
if (__cache[id]) return __cache[id].exports;
var module = __cache[id] = { exports: {} };
var factory = __mods[id];
if (!factory) throw new Error("Module not found: " + id);
factory(module.exports, function (spec) {
var target = (__map[id] && __map[id][spec]) || spec;
return __require(target);
}, module);
return module.exports;
}
// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
// Apex Aero — Main entry module
// Initializes Three.js scene, game state, and runs the render loop
Object.defineProperty(exports, "__esModule", { value: true });
const aircraft_1 = require("./aircraft");
const physics_1 = require("./physics");
const terrain_1 = require("./terrain");
const audio_1 = require("./audio");
const input_1 = require("./input");
const models_1 = require("./models");
const hud_1 = require("./hud");
const mission_1 = require("./mission");
// --- Globals ---
let scene;
let camera;
let renderer;
let game;
let aircraftMesh;
let enemyMeshes;
let exhaustFlameL;
let exhaustFlameR;
let lastCamMode = 'chase';
let camModeIndex = 0;
// --- Initialize ---
function init() {
// Scene
scene = new THREE.Scene();
scene.background = 0x87CEEB;
scene.fog = new THREE.Fog(0x87CEEB, 2000, 10000);
// Camera
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 20000);
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x87CEEB, 1);
document.getElementById('gameCanvas').replaceWith(renderer.domElement);
renderer.domElement.id = 'gameCanvas';
// Monkey-patch THREE.BufferAttribute to prevent onUploadCallback errors
THREE.BufferAttribute.prototype.onUploadCallback = function () { };
// Lighting
const ambient = new THREE.AmbientLight(0x404060, 0.5);
scene.add(ambient);
const hemi = new THREE.HemisphereLight(0x87CEEB, 0x362907, 0.6);
scene.add(hemi);
const sun = new THREE.DirectionalLight(0xffffff, 1.0);
sun.position.set(500, 800, 300);
scene.add(sun);
// Initialize terrain
(0, terrain_1.initTerrain)();
// Clouds
const cloudGroup = new THREE.Group();
for (let i = 0; i < 60; i++) {
const c = new THREE.Mesh(new THREE.SphereGeometry(30 + Math.random() * 60, 6, 4), new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true, transparent: true, opacity: 0.7 }));
c.position.set((Math.random() - 0.5) * 3000, 400 + Math.random() * 400, (Math.random() - 0.5) * 3000);
c.scale.set(1 + Math.random(), 0.3 + Math.random() * 0.3, 1 + Math.random());
cloudGroup.add(c);
}
scene.add(cloudGroup);
// Runway strip
const runwayMat = new THREE.MeshPhongMaterial({ color: 0x333333, flatShading: true });
const runway = new THREE.Mesh(new THREE.PlaneGeometry(30, 400), runwayMat);
runway.rotation.x = -Math.PI / 2;
runway.position.set(0, 0.5, -100);
scene.add(runway);
// Runway markings
const markMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
for (let z = -280; z < 80; z += 40) {
const mark = new THREE.Mesh(new THREE.PlaneGeometry(2, 12), markMat);
mark.rotation.x = -Math.PI / 2;
mark.position.set(0, 0.6, -100 + z);
scene.add(mark);
}
// Runway edge lights
const lightMat = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
for (let z = -280; z < 80; z += 20) {
const l1 = new THREE.Mesh(new THREE.SphereGeometry(0.5, 4, 4), lightMat);
l1.position.set(-8, 0.8, -100 + z);
scene.add(l1);
const l2 = new THREE.Mesh(new THREE.SphereGeometry(0.5, 4, 4), lightMat);
l2.position.set(8, 0.8, -100 + z);
scene.add(l2);
}
// Initialize game state
game = createInitialState();
enemyMeshes = new Map();
// Initialize input
(0, input_1.initInputHandlers)(game.input, () => { });
// Initialize HUD
(0, hud_1.initHUD)();
// Initialize audio (deferred — resume on first interaction)
(0, audio_1.initAudio)();
// Show menu
showMainMenu();
// Handle resize
window.addEventListener('resize', onResize);
// First interaction resumes audio
window.addEventListener('keydown', () => (0, audio_1.resumeAudio)(), { once: true });
window.addEventListener('click', () => (0, audio_1.resumeAudio)(), { once: true });
// Start render loop
game.lastTime = performance.now();
requestAnimationFrame(gameLoop);
}
function createInitialState() {
return {
phase: 'menu',
selectedAircraft: 0,
flight: {
position: new THREE.Vector3(0, 2, 0),
velocity: new THREE.Vector3(0, 0, 0),
quaternion: new THREE.Quaternion(),
angularVelocity: new THREE.Vector3(0, 0, 0),
throttle: 0,
airspeed: 0,
altitude: 0,
angleOfAttack: 0,
sideslipAngle: 0,
gForce: 1,
engineRPM: 800,
engineTemp: 20,
isGrounded: true,
isStalled: false,
terrainHeight: 0
},
input: (0, input_1.createInput)(),
mission: {
type: 'takeoff',
waypoints: [],
enemies: [],
score: 0,
phase: '',
time: 0,
alive: true
},
cameraMode: 'chase',
cameraOffset: new THREE.Vector3(0, 5, -15),
lastTime: 0,
deltaTime: 0,
fps: 0,
frameCount: 0,
missionTime: 0,
terrainChunks: new Map(),
weather: {
windDirection: 0,
windSpeed: 5,
turbulence: 0.1,
cloudDensity: 0.3,
visibility: 8000
}
};
}
// --- Menu ---
function showMainMenu() {
game.phase = 'menu';
const menu = document.getElementById('menu');
menu.style.display = 'block';
menu.innerHTML = `
<div id="menu-container">
<h1 id="menu-title">APEX AERO</h1>
<p id="menu-subtitle">Flight Simulator</p>
<div id="aircraft-select">
<h2>Select Aircraft</h2>
<div id="aircraft-cards"></div>
</div>
<div id="mission-select">
<h2>Select Mission</h2>
<button id="btn-takeoff" style="font-size:18px;padding:12px 30px;margin:8px;cursor:pointer;">🛫 Takeoff & Landing</button>
<button id="btn-dogfight" style="font-size:18px;padding:12px 30px;margin:8px;cursor:pointer;">⚔️ Dogfight</button>
</div>
</div>
`;
const cards = document.getElementById('aircraft-cards');
aircraft_1.AIRCRAFT.forEach((ac, i) => {
const card = document.createElement('div');
card.className = 'acard' + (i === game.selectedAircraft ? ' active' : '');
card.innerHTML = `
<div class="acard-name">${ac.name}</div>
<div class="acard-desc">${ac.description}</div>
<div class="acard-stats">
SPD:${Math.round(ac.maxSpeed * 3.6)}km/h | WGT:${ac.mass / 1000}t | THR:${(ac.thrust / 1000).toFixed(0)}kN
</div>
`;
card.addEventListener('click', () => {
game.selectedAircraft = i;
document.querySelectorAll('.acard').forEach(c => c.classList.remove('active'));
card.classList.add('active');
});
cards.appendChild(card);
});
document.getElementById('btn-takeoff').addEventListener('click', () => startMission('takeoff'));
document.getElementById('btn-dogfight').addEventListener('click', () => startMission('dogfight'));
}
function startMission(type) {
(0, audio_1.resumeAudio)();
const menu = document.getElementById('menu');
if (menu)
menu.style.display = 'none';
game.phase = 'playing';
const ac = aircraft_1.AIRCRAFT[game.selectedAircraft];
// Reset flight state
game.flight.position = new THREE.Vector3(0, type === 'takeoff' ? 2 : 300, type === 'takeoff' ? 0 : -200);
game.flight.velocity = new THREE.Vector3(0, 0, type === 'takeoff' ? 0 : 150);
game.flight.quaternion = new THREE.Quaternion();
game.flight.throttle = type === 'takeoff' ? 0.1 : 0.8;
game.flight.airspeed = type === 'takeoff' ? 0 : 150;
game.flight.isGrounded = type === 'takeoff';
game.flight.isStalled = false;
game.mission = (0, mission_1.initMission)(type);
game.missionTime = 0;
document.getElementById('hud').style.display = 'block';
// Build aircraft model
scene.children = scene.children.filter(c => !(c instanceof THREE.Group) || c === scene);
aircraftMesh = (0, models_1.buildAircraftModel)(ac, 1.5);
scene.add(aircraftMesh);
// Build exhaust flames
const flameMat = new THREE.MeshBasicMaterial({ color: 0xff6600, transparent: true, opacity: 0.7 });
exhaustFlameL = new THREE.Mesh(new THREE.ConeGeometry(0.3, 2, 6), flameMat);
exhaustFlameL.rotation.x = Math.PI / 2;
exhaustFlameL.position.set(-1.5, -0.3, -4);
aircraftMesh.add(exhaustFlameL);
exhaustFlameR = new THREE.Mesh(new THREE.ConeGeometry(0.3, 2, 6), flameMat);
exhaustFlameR.rotation.x = Math.PI / 2;
exhaustFlameR.position.set(1.5, -0.3, -4);
aircraftMesh.add(exhaustFlameR);
// Build enemy models for dogfight
if (type === 'dogfight') {
enemyMeshes.clear();
game.mission.enemies.forEach((enemy, i) => {
const em = (0, models_1.buildEnemyAircraft)(0xcc2222);
scene.add(em);
enemyMeshes.set(i, em);
});
}
// Clear old terrain
game.terrainChunks.forEach(mesh => scene.remove(mesh));
game.terrainChunks.clear();
}
// --- Camera ---
function updateCamera(dt) {
if (game.phase !== 'playing')
return;
const fp = game.flight.position;
if (game.cameraMode === 'chase') {
// Chase camera behind and above aircraft
const offset = new THREE.Vector3(0, 8, -20);
offset.applyQuaternion(game.flight.quaternion);
const camTarget = fp.clone().add(offset);
camera.position.lerp(camTarget, 3 * dt);
const lookTarget = fp.clone().add(new THREE.Vector3(0, 2, 10).applyQuaternion(game.flight.quaternion));
camera.lookAt(lookTarget);
}
else if (game.cameraMode === 'cockpit') {
const forward = new THREE.Vector3(0, 0, 1).applyQuaternion(game.flight.quaternion);
const up = new THREE.Vector3(0, 1, 0).applyQuaternion(game.flight.quaternion);
const camPos = fp.clone().add(forward.multiplyScalar(2)).add(up.multiplyScalar(1.2));
camera.position.copy(camPos);
const lookTarget = fp.clone().add(forward.multiplyScalar(100));
camera.lookAt(lookTarget);
}
}
// --- Game Loop ---
function gameLoop(now) {
requestAnimationFrame(gameLoop);
const dt = Math.min((now - game.lastTime) / 1000, 0.1);
game.lastTime = now;
game.deltaTime = dt;
// FPS counter
game.frameCount++;
if (game.frameCount % 30 === 0) {
game.fps = Math.round(1 / dt);
}
if (game.phase === 'playing') {
const ac = aircraft_1.AIRCRAFT[game.selectedAircraft];
// Update terrain
game.flight.terrainHeight = (0, terrain_1.getTerrainHeightAt)(game.flight.position.x, game.flight.position.z);
(0, terrain_1.updateTerrainChunks)(scene, game);
// Physics
(0, physics_1.computeFlightPhysics)(game.flight, ac, game.input, dt);
// Update aircraft model transform
aircraftMesh.position.copy(game.flight.position);
aircraftMesh.quaternion.copy(game.flight.quaternion);
aircraftMesh.updateMatrixWorld(true);
// Exhaust flames visualization
const thrustScale = 0.5 + game.flight.throttle * 3;
exhaustFlameL.scale.set(1, 1, thrustScale);
exhaustFlameR.scale.set(1, 1, thrustScale);
const flameOpacity = 0.3 + game.flight.throttle * 0.5;
exhaustFlameL.material.opacity = flameOpacity;
exhaustFlameR.material.opacity = flameOpacity;
// Enemy updates
if (game.mission.type === 'dogfight') {
game.mission.enemies.forEach((enemy, i) => {
const em = enemyMeshes.get(i);
if (em && enemy.alive) {
em.position.copy(enemy.position);
em.quaternion.copy(enemy.quaternion);
em.updateMatrixWorld(true);
}
else if (em) {
scene.remove(em);
}
});
}
// Mission update
(0, mission_1.updateMission)(game.mission, game.flight, dt);
game.missionTime += dt;
// Camera
updateCamera(dt);
// Audio
(0, audio_1.updateAudio)(game.flight.throttle, game.flight.airspeed, game.flight.isStalled, game.flight.altitude, dt);
// HUD
(0, hud_1.updateHUD)(game.flight, game.mission, ac);
}
// Render
renderer.render(scene, camera);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Camera mode toggle
const origHandler = input_1.initInputHandlers;
window.addEventListener('keydown', (e) => {
if (e.code === 'KeyC' && game.phase === 'playing') {
const modes = ['chase', 'cockpit'];
camModeIndex = (camModeIndex + 1) % modes.length;
game.cameraMode = modes[camModeIndex];
}
});
// Start
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 });
};
// ── module: src/aircraft.ts ──
__mods["src/aircraft.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AIRCRAFT = void 0;
// Three distinct aircraft with mathematically differentiated flight profiles
exports.AIRCRAFT = [
{
name: 'Viper FX-7',
maxSpeed: 280,
thrust: 180000,
mass: 12000,
wingArea: 48,
liftCoef: 1.8,
dragCoef: 0.025,
inducedDragFactor: 0.04,
turnRate: 1.8,
pitchRate: 1.2,
yawRate: 0.5,
stallSpeed: 55,
color: 0x6B8E23,
accentColor: 0xFF6600,
description: 'Light fighter. High agility, low thrust. Best for dogfighting.'
},
{
name: 'Thunder RB-12',
maxSpeed: 320,
thrust: 280000,
mass: 22000,
wingArea: 70,
liftCoef: 2.0,
dragCoef: 0.035,
inducedDragFactor: 0.035,
turnRate: 1.3,
pitchRate: 0.9,
yawRate: 0.4,
stallSpeed: 65,
color: 0x4682B4,
accentColor: 0xFFD700,
description: 'Heavy interceptor. High thrust, strong lift. Great for all missions.'
},
{
name: 'Phantom SR-21',
maxSpeed: 380,
thrust: 220000,
mass: 16000,
wingArea: 55,
liftCoef: 1.5,
dragCoef: 0.018,
inducedDragFactor: 0.05,
turnRate: 2.1,
pitchRate: 1.4,
yawRate: 0.6,
stallSpeed: 50,
color: 0x2F2F2F,
accentColor: 0xFF0044,
description: 'High-speed interceptor. Lowest drag, hardest to stall.'
}
];
};
// ── module: src/physics.ts ──
__mods["src/physics.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAirDensity = getAirDensity;
exports.computeFlightPhysics = computeFlightPhysics;
// Standard sea-level air density
const RHO_SL = 1.225;
// Scale height for atmosphere model
const H0 = 8500;
// Gravity
const G = 9.81;
// Sea level temperature for Mach calculations (not used but good ref)
const T_SL = 288.15;
function getAirDensity(altitude) {
return RHO_SL * Math.exp(-altitude / H0);
}
function computeFlightPhysics(state, aircraft, input, dt) {
// Clamp dt to prevent physics explosions
dt = Math.min(dt, 0.05);
const altitude = Math.max(0, state.position.y);
const rho = getAirDensity(altitude);
// --- Velocity in aircraft body frame ---
const bodyVel = new THREE.Vector3();
bodyVel.copy(state.velocity);
bodyVel.applyQuaternion(state.quaternion.clone().invert());
const forwardSpeed = bodyVel.z;
const lateralSpeed = bodyVel.x;
const verticalSpeed = bodyVel.y;
// Airspeed (magnitude)
state.airspeed = state.velocity.length();
state.altitude = altitude;
// --- Angle of Attack ---
const velDir = state.velocity.clone().normalize();
const aircraftForward = new THREE.Vector3(0, 0, 1).applyQuaternion(state.quaternion);
const dot = velDir.dot(aircraftForward);
state.angleOfAttack = Math.asin(Math.max(-1, Math.min(1, velDir.y - aircraftForward.y * dot)));
state.sideslipAngle = Math.atan2(lateralSpeed, Math.max(1, forwardSpeed));
// --- Lift Calculation ---
// CL = liftCoef * angleOfAttack (simplified, no stall model beyond threshold)
const aoa = state.angleOfAttack;
let CL = aircraft.liftCoef * aoa;
// Stall detection — lift drops off sharply beyond stall AOA
const stallAoa = Math.asin(aircraft.stallSpeed / Math.max(aircraft.maxSpeed, 1)) * 2;
if (Math.abs(aoa) > stallAoa || state.airspeed < aircraft.stallSpeed) {
state.isStalled = true;
CL *= 0.4; // Stall: dramatic lift loss
}
else {
state.isStalled = false;
}
const dynamicPressure = 0.5 * rho * state.airspeed * state.airspeed;
const liftForce = dynamicPressure * aircraft.wingArea * CL;
// Lift acts perpendicular to relative wind (in aircraft up direction)
const liftDir = new THREE.Vector3(0, 1, 0).applyQuaternion(state.quaternion);
const liftVec = liftDir.multiplyScalar(liftForce);
// --- Drag Calculation ---
// CD = CD0 + K * CL^2 (parasite drag + induced drag)
const CD = aircraft.dragCoef + aircraft.inducedDragFactor * CL * CL;
const dragForce = dynamicPressure * aircraft.wingArea * CD;
// Drag opposes velocity
const dragDir = state.airspeed > 0.1 ? state.velocity.clone().normalize().multiplyScalar(-1) : new THREE.Vector3(0, 0, 0);
const dragVec = dragDir.multiplyScalar(dragForce);
// --- Thrust ---
// Throttle input with ramp-up/ramp-down rates
const throttleRate = 0.5; // per second
if (input.throttleUp) {
state.throttle = Math.min(1, state.throttle + throttleRate * dt);
}
else if (input.throttleDown) {
state.throttle = Math.max(0, state.throttle - throttleRate * dt);
}
// Engine RPM based on throttle
state.engineRPM = 800 + state.throttle * 9200;
// Thrust vector in aircraft forward direction
const thrustForce = aircraft.thrust * state.throttle;
const thrustDir = new THREE.Vector3(0, 0, 1).applyQuaternion(state.quaternion);
const thrustVec = thrustDir.multiplyScalar(thrustForce);
// Forward axis for later use
const fwdAxis2 = aircraftForward.clone();
// --- Gravity ---
const gravityVec = new THREE.Vector3(0, -G * aircraft.mass, 0);
// --- Ground friction ---
const groundClearance = state.position.y;
const onGround = groundClearance < 2.0;
let frictionVec = new THREE.Vector3(0, 0, 0);
if (onGround) {
state.isGrounded = true;
// Ground friction proportional to vertical penetration and velocity
const frictionCoeff = 0.8;
frictionVec.copy(state.velocity).multiplyScalar(-frictionCoeff * aircraft.mass);
// Prevent going below ground
if (state.position.y < 0)
state.position.y = 0;
}
else {
state.isGrounded = false;
}
// --- Sum forces ---
const totalForce = new THREE.Vector3();
totalForce.add(liftVec);
totalForce.add(dragVec);
totalForce.add(thrustVec);
totalForce.add(gravityVec);
totalForce.add(frictionVec);
// --- Acceleration and velocity update ---
const accel = totalForce.multiplyScalar(1 / aircraft.mass);
state.velocity.add(accel.multiplyScalar(dt));
// --- Position update ---
state.position.add(state.velocity.clone().multiplyScalar(dt));
// Keep above ground (terrain check handled elsewhere)
state.terrainHeight = 0; // Will be set by terrain module
if (state.position.y < state.terrainHeight + 1) {
state.position.y = state.terrainHeight + 1;
if (state.velocity.y < 0) {
// Hard landing / crash threshold
if (state.velocity.y < -15) {
// Crash
state.velocity.y = -state.velocity.y * 0.2;
}
else {
state.velocity.y = 0;
}
}
}
// --- Angular dynamics (attitude changes) ---
const rollInput = (input.rollLeft ? -1 : 0) + (input.rollRight ? 1 : 0);
const pitchInput = (input.up ? 1 : 0) + (input.down ? -1 : 0);
const yawInput = (input.left ? 1 : 0) + (input.right ? -1 : 0);
// Rate limiting based on aircraft characteristics
const rollRate = rollInput * aircraft.turnRate * dt;
const pitchRate = pitchInput * aircraft.pitchRate * dt;
const yawRateVal = yawInput * aircraft.yawRate * dt;
// Create rotation quaternions
const fwdAxis = new THREE.Vector3(0, 0, 1).applyQuaternion(state.quaternion);
const rightAxis = new THREE.Vector3(1, 0, 0).applyQuaternion(state.quaternion);
const upAxis = new THREE.Vector3(0, 1, 0).applyQuaternion(state.quaternion);
// Yaw (heading) around world Y axis
const yawQ = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawRateVal);
state.quaternion.multiply(yawQ);
// Pitch around aircraft right axis
const pitchQ = new THREE.Quaternion().setFromAxisAngle(rightAxis, pitchRate);
state.quaternion.multiply(pitchQ);
// Roll around aircraft forward axis
const rollQ = new THREE.Quaternion().setFromAxisAngle(fwdAxis, rollRate);
state.quaternion.multiply(rollQ);
// Normalize quaternion
state.quaternion.normalize();
// --- G-force calculation ---
const gForceMag = accel.length() / G;
state.gForce = gForceMag + 1; // +1 for gravity baseline
// --- Auto-leveling when no input (stability augmentation) ---
if (pitchInput === 0 && !state.isGrounded) {
// Gentle return to level flight
const autoLevel = 0.3 * dt;
const worldUp = new THREE.Vector3(0, 1, 0);
const planeUp = new THREE.Vector3(0, 1, 0).applyQuaternion(state.quaternion);
const cross = new THREE.Vector3().crossVectors(planeUp, worldUp);
if (cross.lengthSq() > 0.001) {
const levelQ = new THREE.Quaternion().setFromAxisAngle(cross.normalize(), autoLevel);
state.quaternion.multiply(levelQ);
}
}
}
};
// ── module: src/terrain.ts ──
__mods["src/terrain.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initTerrain = initTerrain;
exports.updateTerrainChunks = updateTerrainChunks;
exports.getTerrainHeightAt = getTerrainHeightAt;
// Patch BEFORE anything else runs
THREE.BufferAttribute.prototype.onUploadCallback = function () { };
const CHUNK_SIZE = 500;
const CHUNK_RES = 24;
const RENDER_DIST = 1200;
const TERRAIN_SCALE = 0.003;
const TERRAIN_HEIGHT = 300;
let simplex;
function initTerrain() {
simplex = new SimplexNoise(Math.random() * 65536 | 0);
}
function getHeight(x, z) {
if (!simplex)
return 0;
let h = 0, amp = 1, freq = 1;
for (let i = 0; i < 5; i++) {
h += simplex.noise2D(x * TERRAIN_SCALE * freq, z * TERRAIN_SCALE * freq) * amp;
amp *= 0.5;
freq *= 2;
}
const d = Math.sqrt(x * x + z * z);
if (d < 400) {
h *= (d / 400) * (d / 400);
}
return Math.max(0, h * TERRAIN_HEIGHT);
}
function createTerrainChunk(cx, cz) {
const w = CHUNK_SIZE, res = CHUNK_RES, hw = w / 2;
// Non-indexed: each quad = 6 vertices (2 triangles)
const vertsPerQuad = 6;
const totalVerts = res * res * vertsPerQuad;
const pos = new Float32Array(totalVerts * 3);
const col = new Float32Array(totalVerts * 3);
let vi = 0;
for (let iz = 0; iz < res; iz++) {
for (let ix = 0; ix < res; ix++) {
const x0 = (ix / res) * w - hw;
const x1 = ((ix + 1) / res) * w - hw;
const z0 = (iz / res) * w - hw;
const z1 = ((iz + 1) / res) * w - hw;
const h00 = getHeight(x0 + cx, z0 + cz);
const h10 = getHeight(x1 + cx, z0 + cz);
const h01 = getHeight(x0 + cx, z1 + cz);
const h11 = getHeight(x1 + cx, z1 + cz);
const verts = [
[x0, h00, z0], [x1, h10, z0], [x0, h00, z1],
[x1, h10, z0], [x1, h11, z1], [x0, h00, z1]
];
for (let v = 0; v < 6; v++) {
pos[vi * 3] = verts[v][0];
pos[vi * 3 + 1] = verts[v][1];
pos[vi * 3 + 2] = verts[v][2];
const nh = Math.min(1, verts[v][1] / TERRAIN_HEIGHT);
if (nh < 0.15) {
col[vi * 3] = 0.76;
col[vi * 3 + 1] = 0.7;
col[vi * 3 + 2] = 0.5;
}
else if (nh < 0.4) {
col[vi * 3] = 0.3 + nh * 0.5;
col[vi * 3 + 1] = 0.5 + nh * 0.3;
col[vi * 3 + 2] = 0.2;
}
else if (nh < 0.7) {
col[vi * 3] = 0.5 - nh * 0.2;
col[vi * 3 + 1] = 0.45 - nh * 0.15;
col[vi * 3 + 2] = 0.4 - nh * 0.15;
}
else {
const s = (nh - 0.7) / 0.3;
col[vi * 3] = 0.4 + s * 0.55;
col[vi * 3 + 1] = 0.35 + s * 0.55;
col[vi * 3 + 2] = 0.35 + s * 0.55;
}
vi++;
}
}
}
const geo = new THREE.BufferGeometry();
const pa = new THREE.BufferAttribute(pos, 3);
pa.onUploadCallback = function () { };
const ca = new THREE.BufferAttribute(col, 3);
ca.onUploadCallback = function () { };
// Compute per-triangle normals (flat shading = duplicate normals per triangle)
const nrm = new Float32Array(totalVerts * 3);
vi = 0;
for (let iz = 0; iz < res; iz++) {
for (let ix = 0; ix < res; ix++) {
const x0 = (ix / res) * w - hw;
const x1 = ((ix + 1) / res) * w - hw;
const z0 = (iz / res) * w - hw;
const z1 = ((iz + 1) / res) * w - hw;
const h00 = getHeight(x0 + cx, z0 + cz);
const h10 = getHeight(x1 + cx, z0 + cz);
const h01 = getHeight(x0 + cx, z1 + cz);
const h11 = getHeight(x1 + cx, z1 + cz);
// Triangle 1 normal
const ax = x1 - x0, ay = h10 - h00, az = z0 - z0;
const bx = x0 - x0, by = h01 - h00, bz = z1 - z0;
let nx = ay * bz - az * by, ny = az * bx - ax * bz, nz = ax * by - ay * bx;
const ln = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;
nx /= ln;
ny /= ln;
nz /= ln;
nrm[vi * 3] = nx;
nrm[vi * 3 + 1] = ny;
nrm[vi * 3 + 2] = nz;
vi++;
nrm[vi * 3] = nx;
nrm[vi * 3 + 1] = ny;
nrm[vi * 3 + 2] = nz;
vi++;
nrm[vi * 3] = nx;
nrm[vi * 3 + 1] = ny;
nrm[vi * 3 + 2] = nz;
vi++;
// Triangle 2 normal
const cx2 = x1 - x0, cy2 = h11 - h10, cz2 = z1 - z0;
const dx2 = x0 - x1, dy2 = h01 - h10, dz2 = z1 - z0;
nx = cy2 * dz2 - cz2 * dy2;
ny = cz2 * dx2 - cx2 * dz2;
nz = cx2 * dy2 - cy2 * dx2;
const ln2 = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;
nx /= ln2;
ny /= ln2;
nz /= ln2;
nrm[vi * 3] = nx;
nrm[vi * 3 + 1] = ny;
nrm[vi * 3 + 2] = nz;
vi++;
nrm[vi * 3] = nx;
nrm[vi * 3 + 1] = ny;
nrm[vi * 3 + 2] = nz;
vi++;
nrm[vi * 3] = nx;
nrm[vi * 3 + 1] = ny;
nrm[vi * 3 + 2] = nz;
vi++;
}
}
const na = new THREE.BufferAttribute(nrm, 3);
na.onUploadCallback = function () { };
geo.setAttribute('position', pa);
geo.setAttribute('color', ca);
geo.setAttribute('normal', na);
const mat = new THREE.MeshPhongMaterial({
vertexColors: true, flatShading: true, side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(cx, 0, cz);
return mesh;
}
function updateTerrainChunks(scene, state) {
const px = Math.floor(state.flight.position.x / CHUNK_SIZE) * CHUNK_SIZE;
const pz = Math.floor(state.flight.position.z / CHUNK_SIZE) * CHUNK_SIZE;
const chunks = [];
const vc = Math.ceil(RENDER_DIST / CHUNK_SIZE) + 1;
for (let dx = -vc; dx <= vc; dx++) {
for (let dz = -vc; dz <= vc; dz++) {
const cx = px + dx * CHUNK_SIZE, cz = pz + dz * CHUNK_SIZE;
if (Math.sqrt(dx * dx + dz * dz) * CHUNK_SIZE < RENDER_DIST)
chunks.push([`${cx}_${cz}`, cx, cz]);
}
}
const keys = new Set(chunks.map(c => c[0]));
const remove = [];
state.terrainChunks.forEach((m, k) => { if (!keys.has(k))
remove.push(m); });
for (const m of remove) {
scene.remove(m);
state.terrainChunks.delete(`${m.position.x}_${m.position.z}`);
}
for (const [k, cx, cz] of chunks) {
if (!state.terrainChunks.has(k)) {
scene.add(createTerrainChunk(cx, cz));
state.terrainChunks.set(k, scene.children[scene.children.length - 1]);
}
}
}
function getTerrainHeightAt(x, z) {
return getHeight(x, z);
}
};
// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
// Dynamic audio system tied to flight physics
// Uses Web Audio API for real-time synthesized sounds
Object.defineProperty(exports, "__esModule", { value: true });
exports.initAudio = initAudio;
exports.resumeAudio = resumeAudio;
exports.updateAudio = updateAudio;
let audioCtx = null;
let engineGain = null;
let engineOsc = null;
let windGain = null;
let windNoise = null;
let stallWarn = null;
let stallGain = null;
let masterGain = null;
function initAudio() {
try {
audioCtx = new AudioContext();
masterGain = audioCtx.createGain();
masterGain.gain.value = 0.4;
masterGain.connect(audioCtx.destination);
// Engine oscillator (sawtooth for jet sound)
engineOsc = audioCtx.createOscillator();
engineOsc.type = 'sawtooth';
engineOsc.frequency.value = 80;
engineGain = audioCtx.createGain();
engineGain.gain.value = 0;
engineOsc.connect(engineGain);
engineGain.connect(masterGain);
engineOsc.start();
// Wind noise (filtered white noise)
const bufferSize = audioCtx.sampleRate * 2;
const noiseBuffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = noiseBuffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
windNoise = audioCtx.createBufferSource();
windNoise.buffer = noiseBuffer;
windNoise.loop = true;
const windFilter = audioCtx.createBiquadFilter();
windFilter.type = 'lowpass';
windFilter.frequency.value = 400;
windFilter.Q.value = 0.5;
windGain = audioCtx.createGain();
windGain.gain.value = 0;
windNoise.connect(windFilter);
windFilter.connect(windGain);
windGain.connect(masterGain);
windNoise.start();
// Stall warning oscillator
stallWarn = audioCtx.createOscillator();
stallWarn.type = 'square';
stallWarn.frequency.value = 800;
stallGain = audioCtx.createGain();
stallGain.gain.value = 0;
stallWarn.connect(stallGain);
stallGain.connect(masterGain);
stallWarn.start();
}
catch (e) {
// Audio may not initialize until user interaction
console.warn('Audio init deferred:', e);
}
}
function resumeAudio() {
if (audioCtx && audioCtx.state === 'suspended') {
audioCtx.resume();
}
}
function updateAudio(throttle, airspeed, isStalled, altitude, dt) {
if (!audioCtx || !engineOsc || !engineGain || !windGain || !stallGain)
return;
// Engine frequency based on throttle and altitude (less dense air = higher pitch perception)
const altFactor = Math.exp(-altitude / 5000);
const engineFreq = 60 + throttle * 200 * (1 + altFactor * 0.2);
engineOsc.frequency.setTargetAtTime(engineFreq, audioCtx.currentTime, dt);
// Engine volume based on throttle
const engineVol = throttle * 0.3 + 0.01;
engineGain.gain.setTargetAtTime(engineVol, audioCtx.currentTime, dt);
// Wind noise based on airspeed
const windVol = Math.min(1, airspeed / 250) * 0.2;
windGain.gain.setTargetAtTime(windVol, audioCtx.currentTime, dt);
// Stall warning
const stallVol = isStalled ? 0.15 : 0;
stallGain.gain.setTargetAtTime(stallVol, audioCtx.currentTime, dt);
}
};
// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
// Keyboard and mouse input handling
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInput = createInput;
exports.initInputHandlers = initInputHandlers;
function createInput() {
return {
up: false, down: false, left: false, right: false,
rollLeft: false, rollRight: false,
throttleUp: false, throttleDown: false,
brake: false, gear: false
};
}
function initInputHandlers(input, onChange) {
const keyMap = {
'ArrowUp': 'up', 'KeyW': 'up',
'ArrowDown': 'down', 'KeyS': 'down',
'ArrowLeft': 'left', 'KeyA': 'left',
'ArrowRight': 'right', 'KeyD': 'right',
'KeyQ': 'rollLeft', 'KeyE': 'rollRight',
'ShiftLeft': 'throttleUp', 'ShiftRight': 'throttleUp',
'ControlLeft': 'throttleDown', 'ControlRight': 'throttleDown',
'Space': 'brake'
};
window.addEventListener('keydown', (e) => {
const key = keyMap[e.code];
if (key) {
input[key] = true;
e.preventDefault();
onChange();
}
// Camera mode toggle
if (e.code === 'KeyC') {
e.preventDefault();
onChange();
}
});
window.addEventListener('keyup', (e) => {
const key = keyMap[e.code];
if (key) {
input[key] = false;
e.preventDefault();
onChange();
}
});
}
};
// ── module: src/models.ts ──
__mods["src/models.ts"] = function (exports, require, module) {
"use strict";
// 3D aircraft model generation — builds distinct models per aircraft profile
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAircraftModel = buildAircraftModel;
exports.buildEnemyAircraft = buildEnemyAircraft;
function buildAircraftModel(aircraft, scale = 1) {
const group = new THREE.Group();
const bodyMat = new THREE.MeshPhongMaterial({
color: aircraft.color,
specular: 0x444444,
shininess: 80,
flatShading: true
});
const accentMat = new THREE.MeshPhongMaterial({
color: aircraft.accentColor,
specular: 0x666666,
shininess: 100
});
const darkMat = new THREE.MeshPhongMaterial({
color: 0x222222,
specular: 0x333333,
shininess: 60
});
const glassMat = new THREE.MeshPhongMaterial({
color: 0x88ccff,
specular: 0xffffff,
shininess: 200,
transparent: true
});
// Fuselage
const fuselageLen = 10 * scale;
const fuselageRad = 0.6 * scale;
const fuselage = new THREE.CylinderGeometry(fuselageRad, fuselageRad * 0.4, fuselageLen, 8);
const fuselageMesh = new THREE.Mesh(fuselage, bodyMat);
fuselageMesh.rotation.x = Math.PI / 2;
group.add(fuselageMesh);
// Nose cone
const noseGeo = new THREE.ConeGeometry(fuselageRad, 3 * scale, 8);
const nose = new THREE.Mesh(noseGeo, bodyMat);
nose.rotation.x = Math.PI / 2;
nose.position.z = fuselageLen / 2 + 1.2 * scale;
group.add(nose);
// Cockpit
const cockpitGeo = new THREE.SphereGeometry(0.45 * scale, 8, 6);
const cockpit = new THREE.Mesh(cockpitGeo, glassMat);
cockpit.position.set(0, 0.35 * scale, 2 * scale);
cockpit.scale.set(1, 0.6, 1.5);
group.add(cockpit);
// Main wings
const wingSpan = (aircraft.wingArea / fuselageLen) * scale;
const wingGeo = new THREE.BoxGeometry(wingSpan * 2, 0.08 * scale, 2 * scale);
const wings = new THREE.Mesh(wingGeo, bodyMat);
wings.position.set(0, 0, 0.5 * scale);
group.add(wings);
// Wing leading edge strips (accent)
const stripGeo = new THREE.BoxGeometry(wingSpan * 2, 0.04 * scale, 0.15 * scale);
const stripL = new THREE.Mesh(stripGeo, accentMat);
stripL.position.set(0, 0, 0.5 * scale + scale);
group.add(stripL);
// Wing tip lights
const tipGeo = new THREE.SphereGeometry(0.08 * scale, 4, 4);
const tipL = new THREE.Mesh(tipGeo, accentMat);
tipL.position.set(-wingSpan, 0, 0.5 * scale);
group.add(tipL);
const tipR = new THREE.Mesh(tipGeo, accentMat);
tipR.position.set(wingSpan, 0, 0.5 * scale);
group.add(tipR);
// Tail - vertical stabilizer
const vTailGeo = new THREE.BoxGeometry(0.08 * scale, 2 * scale, 1.2 * scale);
const vTail = new THREE.Mesh(vTailGeo, bodyMat);
vTail.position.set(0, 1 * scale, -3.5 * scale);
group.add(vTail);
// Tail - horizontal stabilizer
const hTailGeo = new THREE.BoxGeometry(wingSpan, 0.06 * scale, 1.2 * scale);
const hTail = new THREE.Mesh(hTailGeo, bodyMat);
hTail.position.set(0, 0, -3.5 * scale);
group.add(hTail);
// Engine nacelles
const nacelleGeo = new THREE.CylinderGeometry(0.35 * scale, 0.3 * scale, 2.5 * scale, 8);
const nacelleL = new THREE.Mesh(nacelleGeo, darkMat);
nacelleL.rotation.x = Math.PI / 2;
nacelleL.position.set(-1 * scale, -0.2 * scale, 0);
group.add(nacelleL);
const nacelleR = new THREE.Mesh(nacelleGeo, darkMat);
nacelleR.rotation.x = Math.PI / 2;
nacelleR.position.set(1 * scale, -0.2 * scale, 0);
group.add(nacelleR);
// Exhaust nozzles
const nozGeo = new THREE.CylinderGeometry(0.2 * scale, 0.15 * scale, 0.5 * scale, 8);
const nozL = new THREE.Mesh(nozGeo, accentMat);
nozL.rotation.x = Math.PI / 2;
nozL.position.set(-1 * scale, -0.2 * scale, -2.5 * scale);
group.add(nozL);
const nozR = new THREE.Mesh(nozGeo, accentMat);
nozR.rotation.x = Math.PI / 2;
nozR.position.set(1 * scale, -0.2 * scale, -2.5 * scale);
group.add(nozR);
return group;
}
function buildEnemyAircraft(color) {
// Simplified enemy model (red/orange)
const group = new THREE.Group();
const mat = new THREE.MeshPhongMaterial({
color: color,
specular: 0x333333,
shininess: 60,
flatShading: true
});
const fuselageMesh = new THREE.Mesh(new THREE.ConeGeometry(0.3, 4, 6), mat);
fuselageMesh.rotation.x = Math.PI / 2;
group.add(fuselageMesh);
const wingsMesh = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.06, 1), mat);
group.add(wingsMesh);
const vTailMesh = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.8, 0.5), mat);
vTailMesh.position.set(0, 0.4, -1.5);
group.add(vTailMesh);
return group;
}
};
// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
// Heads-Up Display rendering
// Updates flight data overlay every frame
Object.defineProperty(exports, "__esModule", { value: true });
exports.initHUD = initHUD;
exports.updateHUD = updateHUD;
let hudEl;
function initHUD() {
hudEl = document.getElementById('hud');
hudEl.innerHTML = `
<div id="hud-panel">
<div id="hud-top">
<div id="hud-speed"></div>
<div id="hud-pitch"></div>
<div id="hud-altitude"></div>
</div>
<div id="hud-center">
<div id="hud-crosshair">+</div>
<div id="hud-stall" style="display:none">⚠ STALL</div>
</div>
<div id="hud-bottom">
<div id="hud-throttle"></div>
<div id="hud-gforce"></div>
<div id="hud-mission"></div>
</div>
<div id="hud-controls" style="position:fixed;bottom:10px;left:10px;color:#aaa;font-size:11px;font-family:monospace;background:rgba(0,0,0,0.6);padding:8px;border-radius:4px;">
<b>Controls:</b> W/S Pitch | Q/E Roll | A/D Yaw | Shift Ctrl Throttle | C Camera
</div>
</div>
`;
}
function updateHUD(state, mission, aircraft) {
if (!hudEl)
return;
const speed = document.getElementById('hud-speed');
const pitch = document.getElementById('hud-pitch');
const alt = document.getElementById('hud-altitude');
const throttle = document.getElementById('hud-throttle');
const gforce = document.getElementById('hud-gforce');
const stall = document.getElementById('hud-stall');
const missionEl = document.getElementById('hud-mission');
if (speed)
speed.textContent = `SPD ${Math.round(state.airspeed * 3.6)} km/h`;
if (alt)
alt.textContent = `ALT ${Math.round(state.altitude)} m`;
if (pitch)
pitch.textContent = `PIT ${(state.angleOfAttack * 180 / Math.PI).toFixed(1)}°`;
if (throttle)
throttle.textContent = `THR ${(state.throttle * 100).toFixed(0)}% RPM ${Math.round(state.engineRPM)}`;
if (gforce) {
const gText = `G ${state.gForce.toFixed(1)}`;
gforce.textContent = gText;
gforce.style.color = state.gForce > 3 ? '#ff4444' : state.gForce > 2 ? '#ffaa00' : '#44ff44';
}
if (stall)
stall.style.display = state.isStalled ? 'block' : 'none';
if (missionEl) {
missionEl.textContent = `SCORE ${mission.score} | ${mission.type === 'takeoff' ? 'TAKEOFF/LANDING' : 'DOGFIGHT'}`;
}
}
};
// ── module: src/mission.ts ──
__mods["src/mission.ts"] = function (exports, require, module) {
"use strict";
// Mission system: Takeoff/Landing and Dogfight scenarios
Object.defineProperty(exports, "__esModule", { value: true });
exports.initMission = initMission;
exports.updateMission = updateMission;
exports.getMissionPhaseText = getMissionPhaseText;
function createEnemy(pos) {
return {
position: new THREE.Vector3(pos.x, pos.y, pos.z),
velocity: new THREE.Vector3(0, 0, 0),
quaternion: new THREE.Quaternion(),
alive: true,
health: 100,
lastShot: 0,
targetPos: new THREE.Vector3()
};
}
function initMission(missionType) {
if (missionType === 'takeoff') {
return {
type: 'takeoff',
waypoints: [
new THREE.Vector3(0, 5, 0), // runway start
new THREE.Vector3(0, 200, -500), // climb waypoint
new THREE.Vector3(0, 200, -2000), // cruise waypoint
new THREE.Vector3(0, 5, -3000), // landing approach
],
enemies: [],
score: 0,
phase: 'on_runway',
time: 0,
alive: true
};
}
else {
return {
type: 'dogfight',
waypoints: [],
enemies: [
createEnemy(new THREE.Vector3(500, 500, -500)),
createEnemy(new THREE.Vector3(-400, 600, -800)),
createEnemy(new THREE.Vector3(300, 400, -1200)),
createEnemy(new THREE.Vector3(-600, 550, -300)),
createEnemy(new THREE.Vector3(700, 450, -1000)),
],
score: 0,
phase: 'combat',
time: 0,
alive: true
};
}
}
function updateMission(mission, flight, dt) {
if (!mission.alive)
return;
mission.time += dt;
if (mission.type === 'takeoff') {
updateTakeoffMission(mission, flight, dt);
}
else {
updateDogfightMission(mission, flight, dt);
}
}
function updateTakeoffMission(mission, flight, dt) {
const idx = getWaypointIndex(mission, flight);
if (idx === 0) {
// On runway - need to accelerate and lift off
if (flight.altitude > 10 && flight.airspeed > 60) {
mission.phase = 'climbing';
mission.score += 100;
}
}
else if (idx === 1) {
// Climbing
if (flight.altitude > 150) {
mission.phase = 'cruising';
mission.score += 200;
}
}
else if (idx === 2) {
// Cruising
mission.score += dt * 10;
}
else if (idx === 3) {
// Landing approach
if (flight.altitude < 50 && flight.altitude > 0) {
mission.phase = 'landing';
if (flight.altitude < 10 && flight.airspeed < 80) {
mission.phase = 'landed';
mission.score += 500;
}
}
}
// Crash check
if (flight.airspeed > 30 && flight.velocity.y < -20 && flight.altitude < 5) {
mission.alive = false;
mission.phase = 'crashed';
}
}
function getWaypointIndex(mission, flight) {
for (let i = 0; i < mission.waypoints.length; i++) {
const wp = mission.waypoints[i];
const dist = Math.sqrt((flight.position.x - wp.x) ** 2 +
(flight.position.y - wp.y) ** 2 +
(flight.position.z - wp.z) ** 2);
if (dist < 500)
return i;
}
return mission.waypoints.length - 1;
}
function updateDogfightMission(mission, flight, dt) {
for (const enemy of mission.enemies) {
if (!enemy.alive)
continue;
// Simple AI: fly toward player
const dx = flight.position.x - enemy.position.x;
const dy = flight.position.y - enemy.position.y;
const dz = flight.position.z - enemy.position.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
// Maintain altitude, steer toward player
const speed = 150;
enemy.velocity.x += (dx / dist) * 2 * dt;
enemy.velocity.y += (dy / dist) * 0.5 * dt;
enemy.velocity.z += (dz / dist) * 2 * dt;
// Clamp speed
const vLen = Math.sqrt(enemy.velocity.x ** 2 + enemy.velocity.y ** 2 + enemy.velocity.z ** 2);
if (vLen > speed) {
const scale = speed / vLen;
enemy.velocity.x *= scale;
enemy.velocity.y *= scale;
enemy.velocity.z *= scale;
}
// Keep altitude above terrain
if (enemy.position.y < 100) {
enemy.velocity.y += 10 * dt;
}
enemy.position.x += enemy.velocity.x * dt;
enemy.position.y += enemy.velocity.y * dt;
enemy.position.z += enemy.velocity.z * dt;
// Update enemy quaternion to face movement direction
const dir = new THREE.Vector3(enemy.velocity.x, enemy.velocity.y, enemy.velocity.z);
if (dir.lengthSq() > 0.1) {
dir.normalize();
// Simple look-at: point forward axis toward velocity
enemy.quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.atan2(dir.x, dir.z));
}
// Shooting cooldown and collision
const shootDist = 500;
if (dist < shootDist && mission.time - enemy.lastShot > 2) {
enemy.lastShot = mission.time;
// Damage player
if (dist < 30) {
mission.score -= 50;
}
}
// Player collision with enemy
if (dist < 20 && flight.airspeed > 100) {
enemy.health -= 50 * dt;
if (enemy.health <= 0) {
enemy.alive = false;
mission.score += 300;
}
}
}
// Check if all enemies destroyed
const aliveCount = mission.enemies.filter(e => e.alive).length;
if (aliveCount === 0) {
mission.phase = 'victory';
mission.score += 1000;
}
// Player crash
if (flight.airspeed > 30 && flight.velocity.y < -20 && flight.altitude < 5) {
mission.alive = false;
mission.phase = 'crashed';
}
}
function getMissionPhaseText(mission) {
switch (mission.phase) {
case 'on_runway': return 'Taxi to takeoff speed';
case 'climbing': return 'Climb to altitude';
case 'cruising': return 'Cruise to landing zone';
case 'landing': return 'Approach for landing';
case 'landed': return '✓ Landed safely!';
case 'combat': return 'Destroy all enemies!';
case 'victory': return '✓ Mission complete!';
case 'crashed': return '✗ Crashed!';
default: return '';
}
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"ThinkingCap-Qwen3.6-27B-GGUF","provider":"brigid","persona":"typescript","sessionId":"d527410e-6f7a-4529-804e-857754ff1cc8","tokensIn":4217471,"tokensOut":38675,"tokensTotal":4256146,"cost":0,"turns":89,"toolCalls":90,"failedToolCalls":4,"timestamp":"2026-08-03T22:25:19.434Z"} -->