Files
gods-eye-view/src/tooling/trackArrivalObservation.mjs
T
Said Turksever 01c764d4fd fix(track): read a late arrival frame before timing it out, so a measured wrong angle is never retried away
- observeArrivalRotation reads the sample before the deadline check and keeps
  a late frame's angle on the timeout result; arrivalAngleViolated then makes
  it final. Regressions: wrong late reveal and wrong late settle, each followed
  by a clean second attempt, still fail; a clean late frame stays retryable.
- Overpass admission tests: a monotonic per-test clock removes order
  dependence on the module-level limiters; the proxy test covers ::1 and
  ::ffff:127.0.0.1 sockets; a new test pins the proxied: upstream bucket.
- Retry edges (wrong on-time reveal, maxAttempts, inclusive 10° limit) and a
  check that every name the track harness imports is exported.
- Docs: nginx needs Host, X-Forwarded-Proto and X-Forwarded-For; Docker bridge
  networking makes every host client one remote peer; list the exact
  forwarding headers; owner misses can also see 503.
2026-10-26 14:10:52 +00:00

173 lines
5.8 KiB
JavaScript

/**
* 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. 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;
/** A measured rotation error at or above this fails the arrival check. */
export const ARRIVAL_MAX_ERROR_DEG = 10;
/** Attempts allowed when every earlier one timed out without a wrong angle. */
export const ARRIVAL_MAX_ATTEMPTS = 2;
/**
* Observe the first completed moveEnd frame and the first subsequent reveal.
* The settle deadline starts when moveEnd is forced, before its rotation
* baseline; the reveal deadline restarts at the settle frame, which is where
* the contact is hidden. `read` snapshots visibility and rotation error
* against the current position.
*/
export function observeArrivalRotation({
scene,
startMoveEnd,
startReveal,
read,
now = () => performance.now(),
setTimer = setTimeout,
clearTimer = clearTimeout,
}) {
return new Promise((resolve, reject) => {
const startedAt = now();
let phaseStartedAt = startedAt;
let moveEnd = null;
let moveEndMs = null;
let revealFrames = 0;
let done = false;
let remove;
let timer;
const cleanup = () => {
done = true;
remove?.();
clearTimer(timer);
};
const finish = (result) => {
if (done) return;
cleanup();
resolve(result);
};
// A timeout keeps every angle it measured: the settle angle before a
// reveal timeout, and the angle on a late frame itself (a late frame is
// read first; the idle refresh can only repair an angle, so a wrong one
// seen late was wrong on time too). `arrivalAngleViolated` then makes the
// timeout final instead of retryable.
const expired = (late = null) => ({
error: `arrival ${moveEnd ? 'reveal' : 'moveEnd'} observation exceeded ${ARRIVAL_OBSERVATION_MS} ms`,
elapsedMs: now() - startedAt,
moveEndMs,
...(moveEnd
? { dMoveEnd: moveEnd.rotationError }
: late
? { dMoveEnd: late.rotationError }
: {}),
...(moveEnd && late?.shown ? { dReveal: late.rotationError } : {}),
flipFrame: -1,
});
const armDeadline = () => {
clearTimer(timer);
timer = setTimer(() => finish(expired()), ARRIVAL_OBSERVATION_MS);
};
remove = scene.postRender.addEventListener(() => {
if (done) return;
try {
const at = now();
const elapsedMs = at - startedAt;
const sample = read();
if (at - phaseStartedAt >= ARRIVAL_OBSERVATION_MS) {
finish(expired(sample));
return;
}
if (moveEnd === null) {
moveEnd = sample;
moveEndMs = elapsedMs;
phaseStartedAt = at;
armDeadline();
startReveal();
} else {
revealFrames++;
if (sample.shown) {
// Keep the FIRST visible result, including a wrong angle. Never
// wait for a subsequent tick to repair a failed reveal.
finish({
dMoveEnd: moveEnd.rotationError,
dReveal: sample.rotationError,
shown: sample.shown,
flipFrame: revealFrames,
moveEndMs,
revealMs: elapsedMs - moveEndMs,
elapsedMs,
});
return;
}
}
scene.requestRender();
} catch (error) {
cleanup();
reject(error);
}
});
armDeadline();
try {
startMoveEnd();
scene.requestRender();
} catch (error) {
cleanup();
reject(error);
}
});
}
/**
* Whether an observation measured a rotation error over the threshold on any
* frame it captured, including the settle frame of a reveal that then timed
* out. Such an observation is a demonstrated failure, never inconclusive.
*/
export function arrivalAngleViolated(observation) {
const limit = (ARRIVAL_MAX_ERROR_DEG * Math.PI) / 180;
return [observation?.dMoveEnd, observation?.dReveal].some(
(error) => Number.isFinite(error) && error >= limit,
);
}
/**
* Observe arrival rotation, retrying only an inconclusive timeout. A wall-clock
* timeout means the frame landed where the idle refresh could already have
* repaired it, so it earns another attempt; an observation that measured a
* wrong angle (or succeeded) is final, so a later clean attempt can never
* replace it. `prepare` runs before every attempt; the result carries
* `attempts`.
*/
export async function observeArrivalRotationWithRetry({
prepare = async () => {},
observe = observeArrivalRotation,
maxAttempts = ARRIVAL_MAX_ATTEMPTS,
...options
}) {
let observed;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
await prepare();
observed = { ...(await observe(options)), attempts: attempt };
if (!observed.error || arrivalAngleViolated(observed)) break;
}
return observed;
}
/** Test the captured frames without retrying a bad first observation. */
export function arrivalRotationPassed(observation) {
return (
!!observation &&
!observation.error &&
observation.shown === true &&
observation.moveEndMs < ARRIVAL_OBSERVATION_MS &&
observation.revealMs < ARRIVAL_OBSERVATION_MS &&
Number.isFinite(observation.dMoveEnd) &&
Number.isFinite(observation.dReveal) &&
!arrivalAngleViolated(observation)
);
}