mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
101 lines
2.8 KiB
JavaScript
101 lines
2.8 KiB
JavaScript
/** A reveal must beat the one-second idle rotation refresh, regardless of FPS. */
|
|
export const ARRIVAL_OBSERVATION_MS = 500;
|
|
|
|
/**
|
|
* Observe the first completed moveEnd frame and the first subsequent reveal.
|
|
* The deadline starts when moveEnd is forced, before its rotation baseline, so
|
|
* waiting for either frame cannot let the one-second idle refresh mask a bug.
|
|
* `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 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);
|
|
};
|
|
const expired = () => ({
|
|
error: `arrival ${moveEnd ? 'reveal' : 'moveEnd'} observation exceeded ${ARRIVAL_OBSERVATION_MS} ms`,
|
|
elapsedMs: now() - startedAt,
|
|
moveEndMs,
|
|
flipFrame: -1,
|
|
});
|
|
remove = scene.postRender.addEventListener(() => {
|
|
if (done) return;
|
|
try {
|
|
const elapsedMs = now() - startedAt;
|
|
if (elapsedMs >= ARRIVAL_OBSERVATION_MS) {
|
|
finish(expired());
|
|
return;
|
|
}
|
|
const sample = read();
|
|
if (moveEnd === null) {
|
|
moveEnd = sample;
|
|
moveEndMs = elapsedMs;
|
|
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);
|
|
}
|
|
});
|
|
timer = setTimer(() => finish(expired()), ARRIVAL_OBSERVATION_MS);
|
|
try {
|
|
startMoveEnd();
|
|
scene.requestRender();
|
|
} catch (error) {
|
|
cleanup();
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Test the captured frames without retrying a bad first observation. */
|
|
export function arrivalRotationPassed(observation) {
|
|
return (
|
|
!!observation &&
|
|
!observation.error &&
|
|
observation.shown === true &&
|
|
observation.elapsedMs < ARRIVAL_OBSERVATION_MS &&
|
|
(observation.dMoveEnd * 180) / Math.PI < 10 &&
|
|
(observation.dReveal * 180) / Math.PI < 10
|
|
);
|
|
}
|