Files
gods-eye-view/src/data/trackedModelRegime.js
T

84 lines
4.6 KiB
JavaScript

/**
* Zoom-driven 2D↔3D policy for the TRACKED contact (both flight layers).
*
* The fleet's 3D models stay behind the DISPLAY-rail "3D" toggle (`models3d`)
* — hundreds of GLBs are a draw-call budget decision the operator owns. Since
* 2026-08-22 that toggle DEFAULTS ON in `proximity` (owner directive), so the
* fleet is armed on a fresh boot; proximity is itself the budget, admitting only
* the nearest MODEL_MAX in view below the fleet ceiling. The contact you have
* SELECTED is still a separate case: it is exactly one model, it is what the
* camera is pointed at, and the operator's expectation is that zooming in on a
* target resolves it into an aircraft. So the tracked contact's handoff is
* DEFAULT behaviour, driven purely by camera distance and never consulting the
* toggle at all (owner directive, 2026-08-19).
*
* Threshold history — the tracked contact used to inherit the fleet ceiling
* `MODEL_ALT_CEIL_M = 800_000` m, and a first pass raised it to 1_000_000 m to
* make the airframe arrive sooner. Owner playtest 2026-08-20 rejected that: the
* model "pops to 3D far too early" — a 26 m airframe held at its minimum pixel
* size from ~1 Mm out reads as a floating toy, not an aircraft. The ruling is a
* MUCH closer swap: 2D is correct at ~600_000 m, and the handoff belongs at
* ~150_000 m. That is the constant below.
*
* NOTE — the tracked contact now enters 3D *later* (nearer) than the FLEET does.
* Cruise altitudes between 150_000 m and the fleet's 800_000 m ceiling draw the
* surrounding contacts as models while the SELECTED one is still a billboard
* (the fleet pass skips the tracked icao, so nothing double-draws — it is purely
* an ordering difference). As of the 2026-08-22 default flip this is what an
* operator sees WITHOUT arming anything, where before it needed the toggle on;
* the inversion itself is unchanged and is still a consequence of the owner's
* number, not an accident. It is pinned in trackedModelRegime.test.mjs so a
* later "cleanup" cannot erase it without reading this note. Aligning the two is
* a fleet-side decision and is explicitly out of scope here (fleet-wide auto-3D
* is a separate post-launch change).
*
* HYSTERESIS — the enter and exit thresholds are deliberately asymmetric. A
* single threshold makes an orbit AT the boundary flap billboard↔model every
* time the camera's altitude wobbles across it (the tracked orbit is a real
* camera motion, not a static pose), which is the same flicker the fleet's
* ADD/KEEP radii exist to prevent. Once the model owns the visual it keeps it
* until the camera climbs 15% past the enter ceiling.
*
* Pure functions + constants so the policy is unit-testable without a viewer;
* the layers own the latch (one per selection) and the Cesium plumbing.
*/
/** The FLEET's ceiling (`MODEL_ALT_CEIL_M` in both layers), duplicated here only
* so the relationship in the note above is assertable. The fleet's own constant
* is unchanged and remains the single source for fleet behaviour. */
export const FLEET_MODEL_ALT_CEIL_M = 800_000;
/** Camera altitude (m) at or below which the TRACKED contact takes its 3D model.
* Owner playtest ruling 2026-08-20: 2D still reads correctly at ~600_000 m, and
* the swap belongs at ~150_000 m. */
export const TRACKED_MODEL_ENTER_ALT_M = 150_000;
/** Exit is 15% looser than enter — the anti-flap band for a boundary orbit. */
export const TRACKED_MODEL_EXIT_RATIO = 1.15;
/** Camera altitude (m) the camera must climb past before the model hands back to the billboard. */
export const TRACKED_MODEL_EXIT_ALT_M =
TRACKED_MODEL_ENTER_ALT_M * TRACKED_MODEL_EXIT_RATIO; // 172_500
/**
* Whether the tracked contact's 3D model owns the visual at this camera altitude.
*
* Hysteretic: the caller feeds back the previous answer, and that selects which
* threshold applies. Inside the band [enter, exit) the answer is simply "whatever
* it already was", so a camera loitering on the boundary never toggles.
*
* A non-finite height (no viewer yet, camera not positioned) reads as "infinitely
* far out" → inactive, matching the `?? Infinity` the layers used before.
*
* @param {number} cameraHeightM - Camera altitude above the ellipsoid, in metres.
* @param {boolean} [wasActive=false] - The regime's answer on the previous evaluation.
* @returns {boolean} True when the tracked 3D model should own the visual.
*/
export function trackedModelZoomActive(cameraHeightM, wasActive = false) {
if (!Number.isFinite(cameraHeightM)) return false;
return (
cameraHeightM <
(wasActive ? TRACKED_MODEL_EXIT_ALT_M : TRACKED_MODEL_ENTER_ALT_M)
);
}