test(track): freshen rotation passes before the arrival probe and retry an inconclusive timeout

A headless SwiftShader run showed a 926 ms first frame on the military probe,
which no wall-clock budget can accept: the 1 s idle refresh may already have
run inside it. Raise moveEnd and wait one frame before each attempt so the
settle frame is measured against a pass that just ran, and treat a timeout
(never a wrong angle) as inconclusive with one more attempt. Document why the
per-phase budget stays at half the refresh interval.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Fable 5.1 committed 2026-10-21 16:17:37 +01:00
1 parent 9059cd942f
commit e3c4e88d6f
2 files changed
+43 -25

No files matched your search

+38 -23
View File
@@ -2770,28 +2770,43 @@ async function main() {
const bb = findBB(id);
if (!bb || !bb.show) return { error: `${id} missing/hidden` };
const r0 = bb.rotation; // settled tamper origin; correctness uses a fresh projection below
const observed = await observeArrivalRotation({
scene: v.scene,
startMoveEnd() {
// Only moveEnd can repair the tampered angle at the idle pose.
bb.rotation = r0 + 1.5;
v.camera.moveEnd.raiseEvent();
},
startReveal() {
// Hide immediately after the confirmed settle frame. The next
// eligible fleet tick must reveal AND re-aim in that same tick.
bb.rotation = r0 + 1.5;
bb.show = false;
},
read() {
return {
shown: bb.show,
rotationError: angDiff(bb.rotation,
screenProjectedRotation(v.scene, bb.position, course, null)),
};
},
});
const hasModel = !!(window.__g3dFindModel && window.__g3dFindModel(id));
// A wall-clock timeout is inconclusive, not a wrong angle: the frame
// landed where the 1 s idle refresh could already have repaired it
// (seen once on headless SwiftShader: a 926 ms first frame). Such an
// observation gets one more attempt; a wrong first angle never does.
let observed;
for (let attempt = 1; attempt <= 2; attempt++) {
// Freshen both layers' rotation passes right before the probe, so the
// settle frame is measured against a pass that just ran rather than
// one left over from the previous probe — with the camera idle, a
// layer otherwise runs a pass only every ROTATION_REFRESH_MS.
v.camera.moveEnd.raiseEvent();
await nextFrames(1);
observed = await observeArrivalRotation({
scene: v.scene,
startMoveEnd() {
// Only moveEnd can repair the tampered angle at the idle pose.
bb.rotation = r0 + 1.5;
v.camera.moveEnd.raiseEvent();
},
startReveal() {
// Hide immediately after the confirmed settle frame. The next
// eligible fleet tick must reveal AND re-aim in that same tick.
bb.rotation = r0 + 1.5;
bb.show = false;
},
read() {
return {
shown: bb.show,
rotationError: angDiff(bb.rotation,
screenProjectedRotation(v.scene, bb.position, course, null)),
};
},
});
observed.attempts = attempt;
if (!observed.error) break;
}
return { r0, ...observed, hasModel };
};
const flightCourse = window.__SYNTH.flights.find((f) => f.icao === 'aaa002')?.track ?? 0;
@@ -2801,8 +2816,8 @@ async function main() {
military: await probe('bbb101', militaryCourse),
};
});
const fmtArr = (a) => (!a ? `no result (${arrival?.error || 'phase error'})` : a.error ? `${a.error}; elapsed=${Number(a.elapsedMs).toFixed(1)} ms`
: `settle-err=${(a.dMoveEnd * 180 / Math.PI).toFixed(1)}° reveal-err=${(a.dReveal * 180 / Math.PI).toFixed(1)}° shown=${a.shown} flipFrame=${a.flipFrame} settle=${a.moveEndMs.toFixed(1)} ms reveal=${a.revealMs.toFixed(1)} ms total=${a.elapsedMs.toFixed(1)} ms hasModel=${a.hasModel}`);
const fmtArr = (a) => (!a ? `no result (${arrival?.error || 'phase error'})` : a.error ? `${a.error}; elapsed=${Number(a.elapsedMs).toFixed(1)} ms attempts=${a.attempts}`
: `settle-err=${(a.dMoveEnd * 180 / Math.PI).toFixed(1)}° reveal-err=${(a.dReveal * 180 / Math.PI).toFixed(1)}° shown=${a.shown} flipFrame=${a.flipFrame} settle=${a.moveEndMs.toFixed(1)} ms reveal=${a.revealMs.toFixed(1)} ms total=${a.elapsedMs.toFixed(1)} ms attempts=${a.attempts} hasModel=${a.hasModel}`);
const TAMPER_DEG = 1.5 * 180 / Math.PI; // ≈ 86° — an uncorrected nose stays this far off
record(`arrival: moveEnd + first reveal both re-aim before idle refresh, flights (tamper was ${TAMPER_DEG.toFixed(0)}°)`,
arrivalRotationPassed(arrival.flights), fmtArr(arrival.flights));
+5 -2
View File
@@ -2,8 +2,11 @@
* Wall-clock budget for EACH observed frame, regardless of FPS. Both the settle
* frame (measured from the forced moveEnd) and the reveal frame (measured from
* the settle frame that hid the contact) must land inside it, so neither can be
* repaired by the one-second idle rotation refresh before it is read — while a
* ~300 ms software-rendered frame (headless SwiftShader) still fits twice.
* repaired by the one-second idle rotation refresh before it is read. The
* refresh clock starts at the fleet tick (preRender) of the frame the budget is
* measured from, so the budget must leave room for that frame's own render
* time: half the refresh interval tolerates frames up to ~500 ms, and a ~300 ms
* software-rendered frame (headless SwiftShader) fits with margin.
*/
export const ARRIVAL_OBSERVATION_MS = 500;