- Detailed flight dynamics: aero lift/drag/side-force with smooth stall, control-surface moments, stability derivatives, engine spool with prop torque and gyro precession, inertial coupling, ISA atmosphere, ground effect, 3-point gear handling, landing vs crash classification. - Pure ESM, zero build step; three.js vendored locally. Deterministic simulation; physics core is Node-testable (14 node:test cases). - Procedural 8 km world with runway, water, sky dome, cloud wrap; DOM instrument panel (attitude indicator, tapes, VSI, lamps, stall warning). - Controls: WASD/arrows, Q/E rudder, Shift/Ctrl throttle, F flaps, G gear, B brakes, C camera (chase/cockpit/orbit), R reset, P pause, D debug.
74 lines
3.8 KiB
JavaScript
74 lines
3.8 KiB
JavaScript
// Geometry sanity: the wing/stab chords must run AFT (−X), and flaps/
|
||
// ailerons/elevator must hinge at the actual trailing edges. Runs the vendored
|
||
// three.js in Node — no DOM needed.
|
||
import * as THREE from '../vendor/three.module.min.js';
|
||
import { buildAircraftModel } from '../src/aircraft-model.js';
|
||
|
||
const { group, animate } = buildAircraftModel();
|
||
const bbox = (m) => new THREE.Box3().setFromObject(m); // fresh Box3 per call
|
||
|
||
let fail = 0;
|
||
const check = (name, cond, detail) => {
|
||
console.log((cond ? 'PASS' : 'FAIL') + ' ' + name + (detail ? ' (' + detail + ')' : ''));
|
||
if (!cond) fail++;
|
||
};
|
||
|
||
// Identify meshes by geometry parameters.
|
||
const meshes = [];
|
||
group.traverse((o) => { if (o.isMesh) meshes.push(o); });
|
||
const byBox = (w, h, d) => meshes.filter((m) => {
|
||
const p = m.geometry.parameters;
|
||
return p && p.width === w && p.height === h && p.depth === d;
|
||
});
|
||
const wing = meshes.find((m) => m.geometry.type === 'ExtrudeGeometry' && Math.abs(m.position.y) < 0.001 && m.position.x > 0 && m.position.z > 0.5);
|
||
const stab = meshes.find((m) => m.geometry.type === 'ExtrudeGeometry' && m.position.x < -2 && m.position.z > -0.2);
|
||
const flaps = byBox(0.45, 1.15, 0.045);
|
||
const ails = byBox(0.42, 2.0, 0.045);
|
||
const elevs = byBox(0.32, 4.0, 0.06);
|
||
|
||
if (!wing || !stab) { console.log('FAIL could not locate wing/stab meshes'); process.exit(1); }
|
||
const wb = bbox(wing), sb = bbox(stab);
|
||
|
||
console.log('wing bbox: x [' + wb.min.x.toFixed(2) + ', ' + wb.max.x.toFixed(2) + '] z [' + wb.min.z.toFixed(2) + ', ' + wb.max.z.toFixed(2) + ']');
|
||
console.log('stab bbox: x [' + sb.min.x.toFixed(2) + ', ' + sb.max.x.toFixed(2) + ']');
|
||
|
||
check('wing chord runs AFT (LE x=0.65 > TE x≈−1.30)', Math.abs(wb.max.x - 0.65) < 0.02 && wb.min.x < -1.25, 'LE=' + wb.max.x.toFixed(2) + ' TE=' + wb.min.x.toFixed(2));
|
||
const wingTE = wb.min.x;
|
||
|
||
for (const f of flaps) {
|
||
const fb = bbox(f);
|
||
check('flap hinges at wing TE and sweeps AFT', Math.abs(fb.max.x - wingTE) < 0.03 && fb.min.x < fb.max.x - 0.4,
|
||
'hinge=' + fb.max.x.toFixed(2) + ' aftEnd=' + fb.min.x.toFixed(2));
|
||
check('flap sits behind the wing LEADING edge (not at the front!)', fb.max.x < wb.max.x - 1.5);
|
||
}
|
||
for (const a of ails) {
|
||
const ab = bbox(a);
|
||
check('aileron hinges at wing TE and sweeps AFT', ab.max.x > -1.35 && ab.max.x < -1.1 && ab.min.x < ab.max.x - 0.35,
|
||
'hinge=' + ab.max.x.toFixed(2));
|
||
check('aileron behind the wing LE', ab.max.x < wb.max.x - 1.5);
|
||
}
|
||
check('two flaps + two ailerons built', flaps.length === 2 && ails.length === 2, flaps.length + '/' + ails.length);
|
||
|
||
const elev = elevs[0];
|
||
if (elev) {
|
||
const eb = bbox(elev);
|
||
check('elevator hinges at stab TE (−3.08) and sweeps AFT',
|
||
Math.abs(eb.max.x - sb.min.x) < 0.03 && eb.min.x < eb.max.x - 0.25,
|
||
'hinge=' + eb.max.x.toFixed(2) + ' stabTE=' + sb.min.x.toFixed(2));
|
||
}
|
||
|
||
// Deflection directions: drive animate() and read the sign of each surface.
|
||
const inputs = { elevator: 1, aileron: 1, rudder: 0.5, flaps: 1, gear: true, brakes: false };
|
||
animate({ rpm: 0.5, flaps: 1, gearDown: true }, inputs, 0.1);
|
||
const find = (w, h, d) => byBox(w, h, d)[0];
|
||
const elevMesh = find(0.32, 4.0, 0.06);
|
||
const flapMesh = find(0.45, 1.15, 0.045);
|
||
const ailS = ails.find((m) => m.position.y > 0);
|
||
const ailP = ails.find((m) => m.position.y < 0);
|
||
check('elevator TE UP on nose-up command (rotation.y < 0)', elevMesh.rotation.y < 0, elevMesh.rotation.y.toFixed(3));
|
||
check('flaps TE DOWN when deployed (rotation.y > 0)', flapMesh.rotation.y > 0, flapMesh.rotation.y.toFixed(3));
|
||
check('starboard aileron TE UP on positive input (rotation.y < 0)', ailS.rotation.y < 0, ailS.rotation.y.toFixed(3));
|
||
check('port aileron TE DOWN on positive input (rotation.y > 0)', ailP.rotation.y > 0, ailP.rotation.y.toFixed(3));
|
||
|
||
console.log(fail === 0 ? '\nALL GEOMETRY CHECKS PASSED' : '\n' + fail + ' CHECK(S) FAILED');
|
||
process.exit(fail === 0 ? 0 : 1); |