# Shared aircraft build contract

This flight game separates aircraft specifications, model adapters, shared presentation and mission environments. The T-38 and F-16 use the same presentation systems. Future aircraft must supply validated data and a rig adapter; choosing an airfield or carrier must never select aircraft geometry or physics implicitly.

## Runtime template

1. Define flight characteristics with `FlightAirframes.define(id, spec)` in `airframes.js`. `aero.js` and `dynamics.js` integrate all aircraft with the same equations. Internal distances, mass and forces are SI; player-facing instruments remain imperial.
2. Define visual bindings with `FlightVisuals.define(id, spec)` in `aircraft-visuals.js`. Copy the JSON template in `tools/flight-assets/aircraft-template.json`. Definitions are deeply frozen. Unvalidated objects and missing wheel/strut meshes fail at load.
3. Source model axes are +X aft, +Y up, +Z port. Jet/presentation axes are +X right, +Y up, +Z aft. Wheel and intake bindings use source coordinates; exhaust, cockpit and condensation bindings use presentation coordinates. Never bake a mission transform into the model.
4. Build control/gear hinges in the adapter, then call `FlightPresentation.bind(jet, model, definition)` **before batching**. Preserve its detail group and every animated hinge from batching. Store it in `jet.userData.presentation`; store the adapter's animate function in `jet.userData.animate`.
5. The shared frame order is authored control/gear animation → wheel rotation and suspension → impact effects → condensation → camera → contact shadows, LOD and heat distortion → draw. Physics owns the aircraft position; suspension offsets only its displayed body.
6. Use `FlightPresentation.canopy(definition)` for optical glazing. Material role regexes identify paint, rubber, metal and hot metal; original livery, normal and AO maps are retained. A material name must not match conflicting roles. Author actual panel seams and nozzle geometry in the source asset; procedural roughness is not a substitute for model accuracy.

## Features being delivered in this pass

- Shared paint/metal/rubber finishes and canopy optics; recessed intake liners; wheel hub rings, fasteners, hydraulic lines and fittings.
- Per-wheel ground contact, stable focused sunlight and environment reflections.
- Airfield drainage, weathering, service equipment and vegetation with spatial detail culling.
- Radius-correct wheel spin, damped suspension, restrained cockpit vibration and depth-aware exhaust shimmer.
- Three parked-aircraft detail levels, shared geometry/materials, low-quality distance thresholds and inexpensive low rendering.

The close-up player model retains its rig and full control surfaces. Static parked LODs intentionally have no animated controls or cockpit instruments. Aircraft-specific differences belong in definition data or their source rig adapters, not shared presentation/physics code.

## Build and validation

The portable LOD pipeline, manifests and verification commands are recorded below as implementation is completed. Rebuild source assets from their corresponding source ZIPs. Keep provenance, upstream licensing and editable source together; update source archives whenever runtime or derived assets change. Do not describe a result as AAA solely because these systems exist: assess silhouette, close-up materials, lighting, animation and frame pacing in the running game.

## Delivered implementation — October 18, 2026

| Request | Shared implementation | Per-aircraft data |
| --- | --- | --- |
| 2 — Surface detail | `presentation.js`: intact authored seams/stencils/AO, paint finish grain, heat-tinted metal, tapered inner intake liners, machined hub rings/bolts and strut plumbing | Material-role patterns, intake center/size/depth, wheel and strut mesh names |
| 3 — Light and shadow | Shared optical canopy, constant cockpit fill light, wheel contact shadows following surface slope, texel-aligned sun shadows, static apron and carrier reflection probes applied only to aircraft materials | Canopy tint/opacity, cockpit eye, aircraft dimensions |
| 4 — Airbase | `airfield-detail.js`: spatially batched drainage grates, pavement cracks/spills, rounded tool cabinets/casters, compressor tanks/gauges/hoses, gravel, instanced perimeter vegetation and distant terrain macro variation | None; the environment is independent of aircraft selection |
| 5 — Motion and effects | Radius-based wheel rotation; airborne spin-down; damped oleo compression; body settling; small cockpit vibration; depth-masked exhaust shimmer; impact-driven bounded rebound and skid attitude | Wheel axes/radii/travel; exhaust position/direction/radius/length |
| 6 — Performance | `fleet.js`: cached near/medium/far parked templates sharing geometry/materials; distance-based culling; high-only microdetail and vegetation; low mode omits AO/heat/shadows | LOD URLs and switching distances |

`World.registerAircraft(id, jet)` registers a loaded model. `selectMission(training, id)` selects an environment and registered aircraft independently; its default ID keeps the existing two missions unchanged. The mission state must use the matching `FlightAirframes` definition. Rendering, camera, condensation, contact and heat effects consume the selected model's visual definition. Current mission scripts still provide their own objectives, start positions and collision environment.

Wheel fixtures are attached to the original animated parent hierarchy. Suspension never changes flight forces or landing detection. Its visual travel is bounded by each wheel's configured stroke; the body shifts down by the mean travel. Wheel contact shadows sample the environment surface at the wheel position. At sea, an absent deck returns `null`, suppressing those shadows.

Heat distortion is fused into the existing FXAA pass, limited to four configured outlets. It samples the normal-pass depth texture to preserve foreground aircraft edges. Shimmer is disabled with no fuel, after a crash, in the menu/inspection/cockpit and on Low. No new render pass or changing light count is introduced during flight or at impact. Wing condensation retains the reduced opacity, height, width and lifetime from the preceding subtlety pass.

## Rebuild static LODs

From the repository root, using Blender 5.2 and Python 3:

```sh
blender -b --python tools/flight-assets/build_lods.py -- tools/flight-assets/f16.json
blender -b --python tools/flight-assets/build_lods.py -- tools/flight-assets/t38.json
python3 tools/flight-assets/refresh_sources.py
```

The same script imports the source GLB, removes manifest-selected cockpit/pilot detail, decimates geometry, limits texture resolution, joins by material and exports in unchanged source coordinates. It writes a report containing the input/output SHA-256 hashes, triangle counts and byte sizes. Near models are derived from the live rig at runtime with separate parked materials; medium/far files are static. Never animate parked LOD exports as player aircraft.

| Aircraft | Near visible triangles | Medium | Far | Texture cap medium / far |
| --- | ---: | ---: | ---: | --- |
| F-16 | 51,217 | 11,551 | 4,662 | 1024 / 256 |
| T-38 | 205,360 | 36,347 | 9,835 | 1024 / 256 |

High switches at approximately 213 and 853 feet; Low switches at 85 and 512 feet. Model silhouettes/origins must remain within the tested half-meter/0.3-meter tolerances between levels. The runtime detail tiles cull at 755 feet on High and 295 feet on Low. Distances in JSON/code remain SI for Three.js and physics consistency.

## Future aircraft acceptance checklist

1. Start with `tools/flight-assets/aircraft-template.json`; replace every measurement, mesh name, material pattern and attachment coordinate from the aircraft's actual model. It is a schema example based on the T-38, not universal aircraft specifications.
2. Preserve real control surfaces and gear door sequences in the adapter. Confirm neutral pose and full-travel hinge sweeps before adding finishes. Bind presentation before static batching and preserve dynamic groups.
3. Add a per-aircraft LOD build manifest using the same keys as `f16.json`/`t38.json`. Check omit patterns against the source hierarchy; never remove canopy or wing silhouette geometry accidentally. Export the original authored gear-down pose.
4. Add the new loader to the loading sequence, register it with the World and give the mission its matching physics definition. Keep cockpit eye, vapor geometry, exhaust outlets, camera size, wheel contact height and material-role choices in the visual definition.
5. Extend the actual-GLB test to include the new loader. Verify dimensions, visible triangles, LOD bounds, wheel radius/rotation, suspension limits, all moving control surfaces, gear stow and collision heights. Missing required wheel/strut bindings must throw rather than silently omit features.
6. Inspect the real model from front/rear/side, gear extended/retracted and cockpit views; run takeoff/landing and an impact in High and Low. Check that cockpit glass, parked materials, gear motion and shadows survive mission switching. Inspect browser errors for shader compilation or non-finite geometry.
7. Add original author/license/source hashes; refresh corresponding source ZIPs with browser code, these build scripts/manifests and reports. Keep each hosted file below 25 MiB.

## Verification commands and limits

```sh
node tools/flight-presentation-test.cjs
node tools/flight-airframes-test.cjs
node tools/f16-aircraft-test.cjs
node tools/t38-aircraft-test.cjs
node tools/t38-training-test.cjs
node tools/f16-carrier-test.cjs
node tools/f16-camera-test.cjs
node tools/f16-effects-test.cjs
node tools/flight-power-vapor-test.cjs
node tools/talon-hangar-test.cjs
node tools/f16-carrier-scene-test.cjs
node tools/f16-audio-test.cjs
node tools/es2019-check.mjs
node tools/gen-home-lists.mjs --check
```

The presentation test loads both shipped GLBs and their real LODs. It checks material/geometry sharing, detail switching, gear/suspension/reset behavior, contact suppression over water, finite transforms, heat projection and independent aircraft/environment selection. A third, four-wheel/three-engine aircraft with rotated parent groups exercises the same code and validates rejection of missing bindings. The existing gear tests sweep every retraction frame and check control-surface travel. Flight tests fly the qualification route and carrier recovery using player inputs.

Chrome 76 syntax/API compatibility is checked; actual 2019 Chromebook frame rates still require measurements on that hardware. The browser flight model remains gameplay-calibrated. These additions are a rendering/detail pass, not a claim of independently validated AAA production quality or aircraft engineering accuracy. A cockpit art rebuild remains a separate task.


## Hercules extension and future propeller aircraft

Added 2026-10-18. This is the third actual aircraft on the same runtime; no
C-130-specific force equations were added. `airframes.js` adds numeric engine
fields `shaftPowerW` (per engine, zero for jets) and `propEfficiency` (0–1).
`dryN` is the static propeller thrust cap. `aero.js` uses the smaller of that cap
and shaft power × efficiency / advance speed, then the common throttle-squared,
engine-count, damage and altitude factors. The inverse throttle solver uses the
same factors. Set `wetN === dryN` to disable afterburner. The generic validation
rejects negative shaft power and impossible efficiency. Jets retain their
existing force path. Fuel remains a game percent budget, not a mass-balance fuel
system. The Hercules uses a 55,000 kg mission mass and a calibrated 2.5 G control
limit; those are scenario/handling settings, not an asserted maximum loading.

`rig.js` is a reusable animation-track evaluator: authored node, property,
rotation/translation type, axis, factor/offset, limits and optional piecewise
curve. The importer creates pivot hierarchies in XML order, and the runtime
applies values to their captured rest transforms. Node lookup handles Three.js
name sanitization. Keep rig groups, propeller pivots and wheel pivots outside
static batching. One adapter maps the flight state to source properties; physics
never depends on mesh names. The C-130 importer also preserves glass corners and
keeps original cockpit liners single-sided, so refinement must not shrink a
liner across the pilot sightline. Guard this with a cockpit ray test.

The Hercules definition binds six tires, four exhaust outlets, its cockpit eye
and dimensions. Propeller mesh/blur groups live in its adapter, with only their
RPM visual state tied to engine spool. Military fittings are one preserved scene
group; switching livery changes cached maps/roughness and that group's visibility.
Never recolor parked fleet materials by changing the player’s materials. Fleet
LOD loading can inherit an authored runtime paint map when a static export lacks
one. Shared crash wing partitioning now accepts source-coordinate bounds; its
old F-16/T-38 defaults are unchanged.

`airfield-flight.js` supplies a reusable non-aerobatic runway circuit using the
shared flight core and airfield ground/impact rules. It accepts an aircraft ID;
route, brief and landing envelope currently describe the Hercules mission.
`world.js` registers this aircraft independently of the airbase environment and
sizes inspection, chase and crash cameras from the aircraft dimensions. When
adding another mission, give it distinct score and qualification keys and avoid
awarding the T-38 qualification from a different aircraft's result.

`airfield-cargo.js` is aircraft-independent scenery: one batched building group,
separate distance-culled equipment tiles and a rotating radar. Stand clearance
and roofs have geometry tests; structures are mirrored in the shared airfield
collision function. No aircraft adapter owns buildings or runway collision.

Additional acceptance checks:

```sh
node tools/c130-aircraft-test.cjs
node tools/c130-flight-test.cjs
```

The real GLB test checks published overall dimensions, all six ground contacts,
four four-blade propellers, control/gear transforms across travel, cargo ramp,
propeller blur and both livery/fitting states. The circuit test flies from rest
through all six gate planes, lands and brakes to a stop using ordinary inputs;
it also checks manual power, no reheat, fuel exhaustion and another custom
propeller aircraft using the same equations. Audio tests include the new
recordings and reject any jet-layer leakage into the propeller configuration.
The four older hangars and new cargo terminal are tested for continuous roofs.

Build and source files: `tools/flight-assets/c130/{convert.py,refine.py,README.md}`,
`tools/flight-assets/c130.json`, `assets/C130-SOURCE-MANIFEST.json` and
`assets/c130-source.zip`. Run package_source.py for initial upstream packaging,
then refresh_sources.py to synchronize all three aircraft's corresponding-source
archives with the final shared runtime and LOD recipes. The legacy transport is
explicitly labeled C-130T Fat Albert; do not reuse its four-blade model as a J
variant without rebuilding engines, props and other variant-specific geometry.

## Close inspection and rendering audit (2026-10-18)

The Hercules refinement now adds windshield-surface-projected wipers, nacelle
latches, hydraulic lines and torque links. Gear fittings are converted from world
coordinates into the original actuator parent; never attach retracting hardware
to the static airframe. Recompute sharp edges after subdivision, because imported
sharp-edge flags can leave facets across an otherwise refined shell. The GLB test
checks both wipers, latch count, hose attachment ancestry, cockpit sightline and
the full gear sweep. Rebuild both parked LODs after geometry changes.

Military paint is a deterministic 4096px canvas atlas with access covers, fastener
rows, stencils, repaint variation and restrained staining. Both configurations
have a separate linear roughness map, while their color maps remain sRGB.
Maintain that color-space distinction on future aircraft. The finish is visible
at inspection distance without depending on geometry for every fastener.

Visual definitions accept optional `lights`, each with a presentation-coordinate
`position`, numeric `color`, and `kind` (`navigation`, `beacon`, or `strobe`).
The common presentation layer runs steady navigation lights and timed flashes,
turns them off on static displays/lost aircraft, and hides exterior lights in the
cockpit. These emissive lenses add no dynamic lighting passes. F-16, T-38,
Hercules and the custom aircraft fixture exercise the same implementation.

Rendering acceptance includes camera changes without a browser resize. The
vendored SSAO pass caches its initial near/far and projection matrices; the shared
world now synchronizes them before rendering. `tools/flight-rendering-test.cjs`
verifies reconstruction in carrier, airfield, cockpit and portrait projections.
Terrain with a pavement-discard beauty shader is excluded from the override
normal pass. Coplanar samples are rejected, and distant ambient occlusion fades
out; thin distant features must not turn into repeating shadows. Cargo cladding
uses the airfield's PBR texture set at physical scale, and apron slabs vary in
color with antialiased joints rather than repeating the exact same tile.

High graphics now uses a four-sample offscreen beauty target on WebGL 2. Canvas
`antialias: true` does not antialias an ordinary render target: leaving the beauty
pass single-sampled made distant cladding, fence strands and pavement joints
shimmer even after AO was corrected. WebGL 1 retains the compatible FXAA path;
Low keeps its direct render path. Recheck this when changing post-processing.


### Reusable performance / auxiliary-system contract

- Put rated per-engine newtons, shaft watts, spool time constants, aerodynamic
  coefficients, mass and takeoff speeds in `airframes.js`. Do not scale velocity
  in aircraft adapters. Jets use rated dry/wet thrust; props use the lower of
  their static thrust cap and shaft power × efficiency / airspeed.
- `aero.js` owns all acceleration, rollout, ramp travel and booster burn time.
  Runway integration moves with ground velocity; lift/drag use relative wind.
  Set the mission wind explicitly. Tire brake force decreases with wing lift.
- Optional numeric `boost` fields: `thrustN` (total), `duration`, `minSpeed`,
  `minPower`. Default zeros disable the feature. A mission/configuration sets
  `boostEnabled`; `igniteBoost` validates state/speed/spool and consumes the
  one-shot `boostUsed` latch. `engine` advances burn exactly once per tick.
  Rocket impulse remains independent of engine fuel, spool and altitude lapse.
- Optional `cargo` fields: `travelRate`, `drag`, `cautionSpeed`. Rendering reads
  `rampAmount`; the physical systems tick owns it. Inspection explicitly calls
  `CarrierAero.systems`. Never actuate a control from a render-frame delta.
- `FlightBoostVisual(jet, stations)` accepts aircraft-local nozzle mounts, +Z aft.
  It pools flame meshes and 192 smoke particles at load. The aircraft adapter
  toggles fit visibility for configurations and passes simulation time so pause
  freezes effects. Future aircraft can reuse this visual independently of the
  flight equations. Jets without boosters need no new render module.
- Preserve configuration on flight resume. Reuse O for cargo and J for the
  optional booster, including visible buttons. Do not overload the Retry key.
- Add meaningful rollout/stop, rated dry/wet thrust, impulse conservation,
  wind, fuel-exhaustion and in-flight ramp-drag checks. The performance test
  includes a fourth, synthetic aircraft with different cargo and boost data
  to ensure these systems do not branch on Hercules or a mission name.


### Ground operation, atmosphere and finish template (2026-10-18)

Every aircraft uses the same `CarrierAero` functions. No aircraft IDs belong in
force calculations or `ground-ops.js`. Define these numeric profile fields:

- `massKg` is the full configured sortie mass; `fuel.capacityKg` is included in
  that mass. Current mass subtracts consumed fuel and adds `state.payloadKg`.
  Zero capacity preserves custom fixtures that have no modeled fuel mass.
- `engine.propDiameterM`, `propRpm`, `propEfficiency`, `shaftPowerW` define a
  propeller. `reverseFraction` defaults to zero for jets. Optional reverse uses
  the same engine spool/fuel model; a model adapter reads `reverseAmount` to
  articulate propeller pitch. Never apply thrust in an animation adapter.
- `ground.wheelbaseM`, `steerAngle`, `steerFadeSpeed`, `grip`, `cornering`, existing
  rolling/brake/rotation fields define tire behavior. `noseSteer`, signed
  `groundForward`, `groundAcceleration`, `groundLateral`, `tireSlip`, `brakeHeat`
  are shared simulation outputs for animation, sound and instruments.
- `limits.maxMach`, `gearIAS`, `flapIAS` are aircraft-specific game envelopes.
  `temperatureOffsetC`, world wind, and `runwayWetness` are environment inputs.
  Display IAS in mph, mass in lb and temperature in °F. Internal quantities stay SI.

For an airfield departure call `FlightGroundOps.prepare(sim)` after construction.
The reusable ground states are parked → taxi → launch → flying. `parking`
validates its allowed states; runway entry preserves power and momentum. Route geometry,
pavement footprint and ground collision callbacks belong to the airport.
Talon route starts at (285, 950), follows Alpha past (65, 1100), and guides
lineup at (0, 1000). Markers are optional guidance, never movement gates. New airports must provide their own route/footprint rather than
changing airframe profiles. This module currently supplies Talon geometry.
Practice and physics-only constructors retain their established runway starts.

Visual schema additions: wheel `steering: true` identifies nose wheels; other
wheels remain unsteered. Set `braked: false` on unbraked nose wheels to omit
caliper hardware. Parent-space up and axle axes preserve arbitrary model
rig transforms. Wheel spin is separate from steering. `gearPanels` lists
existing authored meshes whose material finish is treated; missing bindings
fail loading. `cargoCabin` optionally supplies source-space center, length,
width and height for quilted liners, side seats, framing and ceiling lamps.
It does not change the existing cargo-door rig or place fittings in the cockpit.
Omit cabin data on aircraft without a cargo cabin. All detail joins by material
and uses the shared close-detail visibility budget. Paint retains its authored
livery, normal and AO maps; microtexture fades with distance.

Hercules' inherited tie-down meshes were 0.48 m below the actual cargo floor.
The adapter corrects them once at load, before batching. This runtime correction
is part of corresponding source; do not also add it to the upstream GLB without
removing the adapter correction. New cargo assets should author these correctly.

Terrain uses three differently oriented texture samples, smooth stochastic
blend weights, broad land-color variation and distance normal attenuation.
Pavement masks must match geometry and the ground-physics footprint. The Alpha
corner has a widened paved fillet for the Hercules. Paint sits above the apron,
not below it. Drains, repairs and skid marks are batched; 240 distant trees use
instanced trunks and crowns. Exclude those instances and the moving taxi marker
from static merges. No additional dynamic lights or unbounded particle arrays.

Required regression additions: route-to-takeoff with real steering/throttle,
manual power and continuous rolling departure, future-aircraft fixture, wet/dry/reverse stops,
hot/light takeoff, ISA density and fuel mass; visual tests for nose-only
steering, wind-independent wheel rotation, suspension bounds, gear closure and
unchanged aircraft silhouette. Keep the original full mission and crash suites.

### Shared atmosphere and airfield activity contract (2026-10-18)

- `weather.js` exports `FlightWeather.presets`, `apply(state, index)` and
  `get(state)`. Apply once to every new mission/menu state. This sets
  `weatherIndex`, `temperatureOffsetC`, `runwayWetness`, and wind together;
  rendering/audio must read this contract, not infer weather from aircraft IDs.
  Existing mission states retain their preset when resumed from the menu.
- `FlightWeather.Visual.update` consumes the simulation state, pause-aware dt,
  actual camera, airport context and quality. Fixed rain geometry has 1,100
  streaks (300 Low); endpoints wrap as a pair. Rain clips to ground and away
  from the camera, stops above the cloud ceiling, and hides inside covered
  buildings. New airports must replace the current shelter/ground masks.
- `surface(material)` composes with an existing `onBeforeCompile` and cache
  key. Apply it after other surface hooks, once per exposed pavement material.
  Patch masks share world coordinates with `addPuddles`; hangar footprints
  remain dry. Ripples fade at subpixel scale to avoid distant shimmer.
- `airfield-lighting.js` chooses clear/overcast sky, apron, cargo, hangar and
  carrier probes by world location and height. No aircraft IDs. Two fixed
  point lights keep shader light counts constant; don't allocate lights on
  weather changes, crashes or aircraft switches. Probe capture and wet/Low
  shader warming happen behind the loading screen, never in a flight tick.
- `airfield-activity.js` consumes presentation `definition.span` and `.length`.
  Crew clearance and tug stopping envelopes therefore work for future models.
  Vehicles follow a closed service route at about 5.6 mph, towing follows the
  previous route position, and wheel rotation follows distance traveled.
  Keep dynamic limbs, wheels and trailers out of static `F16Model.batch` calls.
  New airfields need a service route checked against parked fleet, buildings,
  pavement and taxi routes. Do not place dynamic traffic on runways.
- Environment motion accumulates dt; a paused frame passes zero. Cargo radar
  uses that same clock. Don't use `performance.now()` for environment motion.
- `audio.js` issues ground messages only in flight and deduplicates per sortie.
  Taxi-out and departure calls follow movement states; no clearance requests or waits.
  Procedural rain has its own filtered loop, cabin/shelter attenuation, and
  remains audible with stopped engines. Mute/pause uses the shared master gain.
- Re-run `tools/flight-environment-test.cjs` and `tools/f16-audio-test.cjs` along
  with ground, presentation, scene, camera, effects and mission regressions.
  Environment tests include an unknown 180-foot-span aircraft fixture, so
  future-aircraft independence is exercised, not merely assumed. Inspect both
  graphic modes, dry/wet transitions, the three current aircraft, paused
  traffic, and actual shader logs in a browser. Refresh all source ZIPs.

Limits: probes do not update with moving crew; planar reflections cover Talon
apron only; Low uses environment reflections; cloud layer is not volumetric;
crew and vehicles have no collision damage behavior. Cockpit interactivity is
not part of this pass.

### Airbase facility tiles

`airfield-facilities.js` builds airport scenery independently of aircraft:
15 spatial tiles, with separate structural and close-hardware groups. Static
meshes are batched by material; one 16-cell sign atlas avoids a material per
sign. Shared PBR cladding uses world-space scale. Exposed concrete registers
through `wetMaterials` with the existing weather shader. `update(camera,
quality)` keeps structural silhouettes to 4,921 ft on High / 1,804 ft on Low;
small hardware appears within 853 ft / 279 ft. No runtime geometry allocation.

Keep new objects outside the aircraft stands and the shared ground route.
Geometry and `TalonTraining.structure` collision bounds must move together;
the five fuel tanks now run along the west side of the service road, not across
it. Container aisles remain open. Test actual batched geometry, not only object
names: `node tools/airfield-facilities-test.cjs` casts rays through protected
stand/taxi/road areas and verifies tank/storage positions, finite transforms,
draw-call/triangle budgets and High/Low culling. The current addition is 140
batched meshes and about 98,700 triangles before distance culling. Also run the
hangar-roof, ground/taxi, T-38/Hercules flight and environment regression tests,
then visually inspect both graphics modes and refresh the source archives.

### Level and aircraft selection

`missions.js` owns the menu catalog, level eligibility, mission factory, and
score namespaces. Pick a level first, then an aircraft. Talon Airbase accepts
T-38, F-16 and Hercules; Carrier Ops accepts the F-16. A T-38 at the airbase can
select either the original aerobatic qualification or the shared six-gate
circuit. Selecting a level or aircraft never starts a sortie. The factory tags
states with `level` and `exercise`; preserve those tags on resume and use them
with `aircraft` for record keys. Legacy T-38 qualification, Hercules circuit /
practice, and F-16 carrier records retain their original keys. New airbase
records are separate per aircraft and from carrier/qualification records.

For another aircraft, add its presentation metadata and eligibility to the
catalog and its menu card, then register its physics/presentation as described
above. Do not select an aircraft from `training` or `circuit`: those flags
identify the environment and exercise. Cargo/reverse/reheat UI follows profile
capabilities, and radio identification follows the selected aircraft. The
runway circuit constructor takes the aircraft ID; its rotation, liftoff,
approach, practice-entry power and touchdown speeds derive from the profile.
`AirfieldCircuit.envelope(state)` is the common source for briefings and
warnings. Touchdown speed checks use indicated airspeed. Its speed ratios and
wing-area-based gate clearance are game calibration, not published flight
manual limits. Practice power balances current drag through the shared inverse
engine model. Full sorties always begin parked with zero throttle.

Run `tools/flight-missions-test.cjs`: it flies all three aircraft through six
actual gate crossings and runway landing/braking using control inputs and
manual throttle, without moving the aircraft or skipping stages. It also checks
practice initialization, manual departure, eligibility and record isolation.
`flight-ground-test.cjs` now drives every aircraft from its stand through
continuous lineup and takeoff in both handling modes. Verify browser
navigation, resume, aircraft-specific equipment, livery and phone layouts too.

### Flight-line architecture and surface cleanup

`airfield-architecture.js` adds five spatial tiles: four hangar facades and the
apron service lane. Reuse this airport component for every aircraft. Glazed
sliding doors, deep structural framing, tracks, roof bracing and bridge cranes
use shared materials; eight painted labels share one atlas. Stand corners,
service-road paint, crossings, crew walkways, equipment boundaries and tire
arcs are paint-height geometry so they cannot obstruct aircraft. Cranes remain
within the existing hangar collision volume. The addition has 42 batched
meshes / 23,814 triangles before culling; close hardware uses 787 ft High /
279 ft Low distance limits. No per-frame mesh creation.

Apron slab seams now come from a single world-space shader instead of two
competing 6- and 8-meter grids. Aggregate, slab variation, seams, edge wear and
broad mottling share that coordinate system. Building/concrete environment
intensity is reduced independently of aircraft reflections. Weather still
composes over the pavement shader. Run the actual-geometry clearance and
budget checks in `airfield-facilities-test.cjs`, plus hangar roof and weather
regressions; inspect the additions in High and Low before refreshing archives.

### Control directions and fast response

Use one aircraft-space convention for every adapter: forward is −Z, right is
+X, up is +Y. Positive rudder/yaw is a right turn: the rudder's trailing edge
moves toward +X. Positive elevator/pitch is pull-up: the tail trailing edge
moves up. Positive aileron/roll banks right: right trailing edge up, left down.
Flaps and leading-edge flaps droop; split speed brakes open outward.

The T-38 and Hercules source rigs use the opposite rudder-property sign, so
those adapters negate `state.rudder`. Keep the shared yaw equations unchanged.
F-16 hinge lookup accepts both authored names and
`THREE.PropertyBinding.sanitizeNodeName(name)`: GLTFLoader removes periods from
names such as `Rudder.002`. Bind every actual mesh before batching; all six
rudder pieces and both aft fin-band pieces must remain animated. The narrow
leading-edge seal rotates with the rudder but lies on the swept hinge, so its
small displacement is not a trailing-edge direction measurement.

Primary actuator rates and rotational response remain aircraft-profile data.
The jets reach 90% primary-surface command within 125 ms; Hercules within
192 ms. Opposite commands reverse within 250 / 383 ms. Maximum roll/yaw rates,
G/AoA limits, flap/gear travel and engine spool remain independent. Simple mode
uses profile `simplePitchResponse` / `simpleBankResponse` with exponential,
frame-rate-independent smoothing; defaults are 6 / 7 per second and Hercules
uses 4 / 4.5. Include positive-value validation for future definitions.

Run `tools/flight-surfaces-test.cjs` on the shipped GLBs with browser-sanitized
node names. It checks actual trailing-edge motion in both command directions,
neutral return, flap/leading-edge/brake directions, and complete F-16 rudder
binding. `tools/flight-response-test.cjs` checks command/reversal latency,
physical pitch/roll/yaw direction, Simple-mode response, configuration timing,
and a future aircraft profile. Re-run handling-limit and full mission tests
when changing response parameters; quicker onset must not defeat the envelope.

### Continuous taxi and force-based runway departure

Runway departure uses no launch key or scripted hold. Wheel brakes follow the slow command; automatic parking holds at idle. `ground-ops.js` advances route guidance
without changing throttle, spool, velocity, or parking brake. Runway entry
changes the HUD state to `launch` without requiring route completion. Direct
runway starts enter ground roll on the next tick; the throttle stays manual.
S reduces power and applies brakes at the airbase and in flight; Space remains a brake alias and the carrier catapult command.

`CarrierAero.groundRoll` scales elevator authority continuously with dynamic
pressure relative to wing loading and retains the gear-limited maximum pitch.
`FlightGroundOps.liftOff` uses the shared lift coefficient, flap position, wing
damage, density, fuel/payload mass, attitude and vertical thrust. Wheel contact
ends when upward force exceeds weight; vertical velocity starts from that net
acceleration, with no minimum speed, pitch gate, or upward velocity kick.
`rotateSpeed` and `liftOffSpeed` remain advisory/profile reference values for
mission approach envelopes, never takeoff gates. This is a simplified contact
model, not a full landing-gear moment solver. Future aircraft inherit it.

Ground regressions cover both modes on all aircraft, route shortcuts, continuous
throttle/momentum, low-speed elevator response, flaps, hot/light takeoff, and
changing advisory speeds without changing physical liftoff. Keep runway braking,
carrier catapult, training, circuit, audio, and surface-direction tests passing.

Departure HUD status also activates for a moving, high-power run outside the
runway footprint; runway entry accepts either direction. These presentation
transitions never gate aerodynamic liftoff. Parking brakes apply wheel friction,
but their boolean flag must not veto liftoff when upward forces exceed weight.
The ground regression includes 300 mph pull-up cases in every aircraft and both
handling modes, with missed route markers, varied headings, and brakes set.

Airbase start position is a shared menu preference (`flightDeparture`), defaulting
to runway. Only an APRON start calls `FlightGroundOps.prepare`; runway uses the
mission constructor's aligned, idle ground state. Both use the same physics.
Taxi routes may guide map/pavement rendering, but never display a required
checkpoint arrow or gate departure. Keep this choice independent of aircraft ID.


S is the shared slow/brake command: it reduces throttle and applies wheel brakes
on the ground or airbrake in flight. Releasing S releases service braking; the
throttle remains at its selected position. The touch brake uses the same command.
Airbase parking automatically holds below 0.3 mph at idle, releases with power or
reverse, and has no separate button/key. Carrier catapult launch remains Space.

Full-mode pitch lead is capped at 20% of the commanded envelope's available turn
rate, independent of the actuator/rotational response gain. Do not multiply the
unbounded alpha error by `pitchResponse`: that caused the nose to snap upward
and then lose most of its apparent authority. Shared sustained-pull regressions
cover positive/negative input on all three aircraft; G/AoA, stalls, and energy
limits remain active. Simple mode still holds limited pitch/bank by design.


## Shared response, destruction and mission contract

- `airframes.js` contains per-aircraft response data. `rollRate` is radians/sec;
  `rollGearScale`, `rollAlphaStart`, `rollAlphaEnd`, and `rollAlphaScale` schedule
  roll authority by configuration and alpha. `pitchLead` bounds the short-period
  lead relative to sustainable turn rate. Use `FlightAirframes.define` so new
  profiles receive defaults and validation. Never branch on aircraft ID inside
  shared `dynamics.js` to tune a new aircraft. Preserve load/alpha limits.
- Every registered player aircraft is passed to `crash.breakup.register(jet)`
  while loading. `breakup.js` partitions actual model triangles into spatial
  sections, sharing immutable vertex attributes/materials and storing separate
  index buffers. At impact it captures current transforms, livery and visibility
  so retracted gear stays hidden. No mesh generation, texture loading or shader
  compilation is allowed on the impact frame. `prepareEffects` warms the pools.
- Destructive obstacle impacts, severe vertical impacts and explicit explosive
  damage replace the intact airframe with independently moving sections. Lower
  energy crashes retain the existing wing separation/skid behavior. Retry clears
  fragment pools; aircraft adapters restore their normal hinges. The crash
  camera follows the debris center. Test real geometry for all three aircraft
  with `node tools/f16-effects-test.cjs`; visually inspect the deterministic
  `/tools/flight-impact-preview.html` fixture at 0.25 and 1 second.
- `intercept.js` owns Mission 2's orbit, acquisition, ammunition, missile guidance
  and confirmed-hit objective. It composes the existing carrier Flight class;
  aircraft forces remain in shared dynamics. `intercept-visual.js` reads that
  state and pools four missile/trail visuals. It loads a fully rigged F-16 bogey,
  animates its surfaces and reuses the same breakup system. Future missions must
  not move the player aircraft directly or change its engine power to hit a cue.
- Missile parameters are gameplay values, not weapon-system specifications:
  15° acquisition cone, 3.1-mile range, 1.1-second acquisition, four rounds,
  one launch per press of E or the touch button. A swept relative-motion hit
  check prevents tunneling. No automatic firing. Airborne guidance to the
  recovery approach is advisory; the real gear/hook/wire checks decide recovery.
- `flight-intercept-test.cjs` covers acquisition, range, no launch on deck,
  finite ammunition, held-button behavior, moving-target hits, practice mode,
  arrested landing and a continuous complete sortie. Existing scores are left
  intact; the intercept uses `f16CarrierInterceptHighScore`.


## Shared loading, inputs and cargo mission template (October 19, 2026)

- Add future aircraft loaders to `World.ensureAircraft` and preserve `once` request coalescing. Build each level with `FlightLoading.sequence`; use `FlightLoading.step` between CPU/GPU preparation tasks. Do not preload unrelated levels or force every registered aircraft visible. Parked fleet dependencies must load before the active base's reflection probes.
- Warm actual material hooks and both shadow variants in small batches behind the loading screen. Capture each probe/weather variant in a separate task. Restore visibility, culling, callbacks, render targets and shadow settings in `finally`, including on failure. Keep the flight loop and input inactive until preparation completes. Aircraft switching, returning from inspection and resume must not build or fetch duplicates.
- GLTF textures share image/sampler/color-space-compatible entries per parser. Preserve distinct sampler settings, linear data maps and texture transforms. Use `F16Model.map` for identical loose surface maps; its cache includes repeat, encoding, flip and anisotropy. Do not mutate a cached map's sampling settings afterward. Keep static geometry attributes available for fleets and breakup pools.
- Runtime T-38 and Hercules normal maps are resampled to 2048×2048, with an indexed 256-color PNG encoding. The Hercules military canvas uses its native 2048-coordinate artwork directly. Retain editable source and document any future runtime texture resizing; visual inspection is required after changing resolutions.
- `FlightStick` ramps normalized pitch/bank commands over 0.6 seconds, centers in 0.12 seconds and crosses center on reversal. It does not alter aircraft maximum rates or rudder authority. Advance it once per fixed physics step; reset on pause, blur, restart, menu or control-mode changes. Keyboard and touch use the same path. S reduces throttle first and requests brakes only at idle. The runway integrator applies wheel braking without deploying airbrake surfaces; flying aircraft use their own airbrake actuator/drag definitions.
- Carrier launch requires player-set throttle and engine spool; never advance throttle or trigger catapult launch automatically. Keep mission selection and high-score keys distinct for recovery and intercept.
- Cargo objectives belong in mission adapters, not aircraft force code. `AirfieldDrop.Flight` requires a cargo-capable profile, extends `AirfieldCircuit.Flight`, and uses its shared `missionProgress` hook. Supplies contribute through `payloadKg`; release only removes the corresponding payload. Keep ramp travel/drag in the aircraft profile. Visual pallets and parachutes are pooled and pause with simulation time. Future cargo aircraft can reuse this mission after defining a ramp and validating the drop envelope for that aircraft.
- Carrier missiles belong inside the shared configuration panel, with no duplicate key list under the radio. Test the whole panel against radio, throttle and radar bounds at desktop, Chromebook and portrait sizes.


### Cargo navigation and aircraft tuning

- Tune roll/pitch authority in each aircraft profile, never by mission/livery branches in shared dynamics. Hercules uses a 36°/s clean roll cap and stronger low-speed elevator command, with the original load/stall limits. Verify both sustained response and cargo-speed pitch buildup; keep the shared keyboard/touch input ramp.
- Cargo route points are horizontal fly-bys, independent of altitude. Do not reuse precision-training gate apertures for hidden navigation checks. Only the actual release envelope checks drop altitude. Preserve precision rings in qualification/circuit missions.
- Every cargo navigation instruction supplies a destination, heading/turn direction, distance in miles and a conditional climb/descend/hold altitude. Label absolute waypoint altitude MSL and drop height AGL. The diamond points at the actual supply zone during the drop run; the map shows the route, drop zone and line to the active destination.
- An overrun without all six releases immediately returns guidance to the run-in. Never advance aircraft position, throttle or payload to satisfy a waypoint. Test high departures, an incomplete pass, unsafe releases and a complete sortie using ordinary pilot inputs.


## F-22 and reusable thrust-vector integration

- Register each aircraft profile in `airframes.js`, mission metadata and score
  keys in `missions.js`, visual metadata in `aircraft-visuals.js`, and the lazy
  loader in `World.ensureAircraft`. Add its menu button to selection and loading
  state handling. Preserve level-first selection and capability checks.
- Optional `controls.thrustVectorRate` defaults to zero. Shared full-mode dynamics
  add elevator-driven, power-dependent pitch authority at low indicated speed,
  fading it out as aerodynamic authority increases. Keep the existing pitch-rate
  and positive/negative load limits. No aircraft-ID checks belong in dynamics.
  Future vectoring aircraft must independently calibrate this simplified term.
- Bake projected paint UVs before LOD export. Runtime-only UV generation leaves
  static parked variants without the same finish. Preserve named control nodes,
  material roles, cockpit/pilot groups and source object metadata in conversion.
  Keep animated parts out of static batching. The F-22 cockpit screen UVs use
  local mesh bounds and the correct orientation for the pilot's viewpoint.
- Derive hinges from authored axes; do not guess signs from left/right labels.
  Test transformed trailing-edge motion, tire contact and the entire gear sweep.
  The F-22 source uses opposite signs for right flaps and leading edges, and
  opposite axes for upper/lower nozzle petals. Its adapter resolves those signs.
- Geometry licensing does not imply texture/audio licensing. Pin used source
  files and derived hashes. Exclude separately restricted assets; package editable
  source, original notices and conversion recipes alongside original artwork.
- `f22-aircraft-test.cjs` checks actual GLB dimensions, all three tire contacts,
  twelve surface/nozzle directions, 101 gear poses, cockpit visibility and LOD
  silhouettes. `f22-flight-test.cjs` checks manual power, hold-only afterburner,
  finite fuel, load/rate limits and power-dependent vector authority. Include
  every new aircraft in mission/response regressions. `f22-browser-test.mjs`
  checks real loading, three viewport sizes, keyboard takeoff, inspection,
  cockpit, quality switching and aircraft switching without shader errors.


### Reusable finish, cockpit and sound refinements

- `material.userData.flightFinishOverrides` can override the shared material
  role's metalness, roughness and environment intensity. Authored bump/roughness
  maps remain intact. `F16Model.batch` preserves RGB vertex shading, normalizing
  byte/short color attributes before merging and supplying white for uncolored
  vertices. Never discard baked shading when batching a new aircraft.
- F-22 `bake_occlusion.py` casts twelve deterministic short-range hemisphere rays
  per exterior vertex. Store results in `CavityAO`, export active vertex colors,
  and regenerate both LODs. Exclude glass occluders. This is local cavity shading,
  not a baked moving shadow; landing gear and control hinges remain movable.
- `f22-detail.js` builds original finish maps and a shared cockpit label atlas.
  Keep screen and control nodes outside cockpit static batches. Live screens use
  one atlas at ten updates per second only when visible in cockpit/inspection.
  Display real state and imperial values; never populate fake targets or engine
  temperatures. Bind stick, throttle and pedal motion to existing actuators.
- `FlightSoundProfiles` defines optional aircraft layers by file, power band,
  gain, fore/aft balance and pitch schedule. `ensureAircraft` coalesces lazy
  decode requests and retains standard sounds until all custom layers succeed.
  Aircraft switches, fuel exhaustion, mute, reheat release and cockpit routing
  must be tested. Do not identify designed layers as authentic engine recordings.
- Keep the sound-design recipe, original licensed inputs, generated WAVs and hash
  manifest together in corresponding source. F-22 uses NumPy and the already
  shipped GPL jet recordings; no noncommercial upstream sound assets are used.

### Shared fleet sound, damage and presentation contract (October 21, 2026)

Apply this contract to every new aircraft; do not add mission-specific copies.

- `CarrierAero.condition(state, component)` reads normalized damage, with absent
  fields treated as healthy. Components are `engine`, `elevator`, `aileron`,
  `rudder`, `gear`, `left`, `right` and `structure`. Physics owns wear; renderers
  and mixers must never accumulate it. A new sortie creates fresh damage state.
- Sustained loads above `aero.damageG` damage structure, a wing and controls.
  Sustained speed more than 3% above `limits.maxMach` damages controls, structure
  and operating engines. Gear wear starts above 120% of `limits.gearIAS`.
  These are deterministic game calibrations, not certified failure envelopes.
- Actuator damage reduces both travel and servo speed. Use actual `elevator`,
  `aileron` and `rudder` outputs to animate hinges, including in Simple controls.
  Simple pitch/bank response also loses authority; changing modes is not a repair.
- Engine damage lowers attainable spool and thrust. At 95% damage, engine thrust,
  powered engine sound and afterburner stop; fuel exhaustion is a separate cause.
  Engine damage is currently a shared engine-group condition: independent engine
  failures, asymmetric thrust and fire suppression remain unmodeled. Engine
  instruments must display the resulting spool, not requested throttle.
- `CarrierAero.gearStep` owns gear travel. Damage slows it and at 80% jams it at
  its actual position. Never show DOWN just because the lever commands it. Gear
  damage reduces braking and steering grip and increases rolling resistance.
- Call `CarrierAero.touchdown` once on first contact. Runway touchdowns above
  approximately 590 ft/min add damage; the carrier threshold is approximately
  1,180 ft/min. Existing catastrophic impact rules remain. Runway gear collapses
  when touchdown pushes damage above 92%; lesser damage persists into rollout.
- `damageStatus` supplies a shared persistent fault readout and the F-22 status
  display. Keep immediate stall/terrain/overspeed warnings separate so they
  cannot erase a lasting fault. Actual gear position and engine spool appear
  in the same HUD for all aircraft.
- Shared presentation binds bounded suspension sag, cockpit vibration, material
  scuff uniforms and a fixed exhaust-smoke pool to physical damage. Smoke uses
  validated `FlightVisuals.exhausts` positions/directions and is suppressed in
  inspection, cockpit view, empty-fuel states and crash effects. Prepare its
  geometry and shader with the aircraft; never allocate effects on impact.
  Static parked LODs exclude this effect. Adapters rotate both damaged wing groups
  from their saved rest transforms; retry restores the original geometry.
- `FlightSoundProfiles[id]` may specify `cockpitExterior`, `cockpitCutoff`,
  `exteriorCutoff`, and `cabin` (sample-name to interior-send gain). Optional
  `layers` retain lazy loading. Cabin sends are low-pass filtered independently
  of spatial exterior audio. F-16 uses its dedicated interior recordings;
  T-38 and Hercules use filtered sends of their licensed aircraft recordings.
  F-22 still uses designed layers derived from F-16 recordings, not an
  authenticated F119 recording. Do not claim otherwise.
- Spatial exhaust audio derives its centroid/direction from visual definitions,
  so new aircraft do not inherit F-16 coordinates. Doppler and directional gains
  remain shared. Continuous tire rumble and lateral/braking scrub use actual
  ground velocity, wheel load, surface and damage; they persist with engines off
  and stop airborne. Gear motor audio follows actual movement and stops on a jam;
  the lock sound requires an actual fully up/down endpoint.
- Hercules propeller animation and audio retain idle rotation with fuel and
  functioning engines, without advancing the throttle. Low/high graphics use
  the same physical state; only detail visibility changes.

Regression commands for this contract:

```sh
node tools/flight-damage-test.cjs
node tools/f16-audio-test.cjs
node tools/flight-presentation-test.cjs
node tools/c130-aircraft-test.cjs
node tools/f22-aircraft-test.cjs
node tools/flight-input-test.cjs
node tools/flight-response-test.cjs
node tools/flight-ground-test.cjs
node tools/flight-missions-test.cjs
node tools/f16-effects-test.cjs
node tools/flight-fleet-browser-test.mjs
node tools/es2019-check.mjs
```

The damage suite includes a future three-engine profile, and presentation tests
include a custom four-wheel/three-exhaust model. Browser checks load all four
actual aircraft, activate damage, inspect cockpit/HUD placement and verify reset
without JavaScript or shader errors. These checks establish reusable behavior;
they do not certify photorealism or aircraft flight-manual fidelity.
