// 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);