mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
release: v0.1.0 — one-click install, keyless boot, in-app Provider Settings (#142)
The first formal release. - One-click install via Pinokio; keyless boot lands on a live Esri World Imagery satellite globe with keyless terrain, with automatic OSM fallback and graceful degradation when terrain is unavailable. - Provider Settings (the POWER UP panel): add, replace, or remove API keys inside the app; credential files made owner-only before any secret is written; external keys shown read-only; the panel refuses shared or proxied servers. - Keyless capability responses for the optional HUD summary and place search. - Aircraft-identity voice answers cover operator, type, and route, and say so plainly when enrichment is unavailable. - README rewritten keyless-first; concise 0.1.0 changelog section; CI workflow included. Gates: 2,672 unit tests pass / 0 fail, build clean, tracking regression 108/108, map-source tray QA pass, setup doctor ready.
This commit is contained in:
1 parent
6d83bb6008
commit
b6da93b8ac
170 files changed
+5800
-853
No files matched your search
@@ -137,7 +137,7 @@ export const CLASS_MODEL_URL = {
|
||||
bizjet: '/models/airplane.glb', uav: '/models/airplane.glb',
|
||||
};
|
||||
|
||||
/** Real per-class GLBs (2026-08-15 Hangar fleet, selected assets; CC-BY 4.0 —
|
||||
/** Real per-class GLBs (2026-08-15 Hangar fleet, owner picks; CC-BY 4.0 —
|
||||
* provenance in public/models/README.md). Every asset is vertex-baked to the
|
||||
* airplane.glb convention: Y-up, X = length, Z = span, nose −X (so the layers'
|
||||
* MODEL_HEADING_OFFSET_DEG = 180 applies unchanged), origin at bbox centre,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* white / cyan-tracked / amber-military, plus .withAlpha fades) keeps working —
|
||||
* no per-glyph hardcoded colors that would fight the tint.
|
||||
*
|
||||
* MIXED-SET UPDATE (2026-08-15, selected asset set): airliner/widebody/
|
||||
* MIXED-SET UPDATE (2026-08-15, owner Hangar picks): airliner/widebody/
|
||||
* turboprop/helicopter use the "refined" recognition-chart redraw;
|
||||
* quadjet/glider use the "bold" chart-symbol redraw; light/fastjet keep the
|
||||
* original drawings. Raster fidelity doubled (192px source, same 96 coords).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/data/aisLiveVessels.test.mjs
|
||||
// Focused tests for the AIS feed-status derivation helper (Batch 10, finding H3/AIS)
|
||||
// and the vessel vertical-datum seam (2026-07-27 datum pass — see
|
||||
// the vessel datum contract in docs/CURRENT-STATE.md).
|
||||
// docs/superpowers/specs/2026-07-27-vessel-datum-design.md).
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as Cesium from 'cesium';
|
||||
|
||||
@@ -167,7 +167,7 @@ test('P0: a pre-disposal close event cannot erase the post-disposal socket', asy
|
||||
assert.deepEqual(context.adapter.debug().generations, [2],
|
||||
'the replacement must NOT reuse generation 1');
|
||||
|
||||
// The pre-disposal socket's close finally lands — review's exact interleaving.
|
||||
// The pre-disposal socket's close finally lands — the exact adversarial interleaving.
|
||||
first.flushClose();
|
||||
|
||||
const debug = context.adapter.debug();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* client-side in the layers ("how many flights over Texas?", "biggest fire
|
||||
* near LA?", "which ships are headed to Oakland?").
|
||||
*
|
||||
* Analyst-query behavior is documented in docs/CURRENT-STATE.md:
|
||||
* Design (owner-ratified, docs/voice-engine-evaluation-2026-07-23.md §5.3):
|
||||
* - ENGINE (this module) is pure query logic over plain record arrays; it
|
||||
* renders nothing. SURFACES (voice narration, panels, detection brackets)
|
||||
* consume the returned result set — the engine/surface seam is the
|
||||
|
||||
+15
-15
@@ -140,7 +140,7 @@ const GEO_PROGRESS_NOTIFY_BATCH_LIMIT = 10;
|
||||
// Throttle for placeholder repaints — the projection RAF loop must not
|
||||
// re-fill a 1080p canvas on every frame while a feed image is still loading.
|
||||
const PLACEHOLDER_REPAINT_MS = 750;
|
||||
// v1 key is retired dead data (product rule #3, §9.3 — WIPE CLEAN, no
|
||||
// v1 key is retired dead data (owner decision #3, §9.3 — WIPE CLEAN, no
|
||||
// legacy import): kept here only as a documented constant so nothing ever
|
||||
// re-reads it by accident. Exported for the unit suite's "v1 is ignored"
|
||||
// assertion; there is NO read path for this key anywhere in the module.
|
||||
@@ -366,7 +366,7 @@ let _cardFetchMode = 'steady';
|
||||
* 20/28/40 tiers resume when loading completes (see refreshAmbientCards).
|
||||
*/
|
||||
const CCTV_AMBIENT_CARD_DRAIN_CAP = 16;
|
||||
// Global static-frame pacing (field finding 3): the pacer ticks at the burst
|
||||
// Global static-frame pacing (owner finding 3): the pacer ticks at the burst
|
||||
// spacing (250 ms) but cardFetchPolicy gates launches — cold fill (selected
|
||||
// cards still missing their FIRST frame) allows up to 4 in-flight fetches at
|
||||
// 250 ms spacing; steady state keeps the salvaged Part C gate of at most one
|
||||
@@ -403,7 +403,7 @@ let _projectionOverlayOwnerId = null;
|
||||
* thumbnail absent because its monitor plane is the active representation.
|
||||
*/
|
||||
let _activeCameraCardEnabled = false;
|
||||
// Hover-summoned card (follow-up round 2, item B): pointing at a cardless camera
|
||||
// Hover-summoned card (owner round 2, item B): pointing at a cardless camera
|
||||
// icon shows its card immediately as a PINNED entry (budget-exempt, top
|
||||
// draw-pass declutter priority).
|
||||
/** Min spacing between hover scene.pick calls (event-driven, user gesture). */
|
||||
@@ -693,7 +693,7 @@ function safeWindowLocalStorage() {
|
||||
*
|
||||
* v2 entries carry provenance: `{ values: <7-field offsets>, source: 'manual',
|
||||
* savedAt: <epoch ms> }`. The v1 key (`CCTV_CALIBRATION_STORAGE_KEY_V1`) is
|
||||
* NEVER read here — product rule #3 (§9.3): wipe clean, no legacy import.
|
||||
* NEVER read here — owner decision #3 (§9.3): wipe clean, no legacy import.
|
||||
*
|
||||
* @param {{getItem:function}|null} [storage] - Injectable storage (defaults
|
||||
* to `window.localStorage`); lets the unit suite test this pure of a DOM.
|
||||
@@ -1186,7 +1186,7 @@ function buildCatalogFromSources(rawSources) {
|
||||
* tileset is present (OSM fallback) this returns true so ground sampling is
|
||||
* not permanently blocked.
|
||||
*
|
||||
* Task 5 (spec correction, spec §2): a HIDDEN tileset (`show === false`,
|
||||
* Task 5 (review correction, spec §2): a HIDDEN tileset (`show === false`,
|
||||
* i.e. a globe stack is active) must NOT report ready — Cesium 1.138's
|
||||
* the shared sampler can only inspect *visible* 3D tilesets, so a sample taken
|
||||
* against the hidden Google tileset would silently miss.
|
||||
@@ -1456,7 +1456,7 @@ function refreshProjectionTextures(record) {
|
||||
// Only swap when the canvas content actually changed since the last swap.
|
||||
// Frames land every ~10 s but this runs at 1 Hz — swapping an UNCHANGED
|
||||
// canvas re-uploads the texture for nothing, and each material image
|
||||
// reassignment is a flash opportunity on the live plane (field test
|
||||
// reassignment is a flash opportunity on the live plane (owner field test
|
||||
// 2026-07-04: intermittent white flashes on the monitor plane).
|
||||
if (runtime.canvasStamp === runtime.lastSwappedCanvasStamp) return;
|
||||
|
||||
@@ -1884,7 +1884,7 @@ function drawProjectionFrame(record) {
|
||||
// and a fresh 1920x1080 texture upload; the plane renders its white
|
||||
// base color (planeMaterial color = WHITE, alpha .95) for the frame or
|
||||
// two Cesium needs to rebind, which IS the periodic white flash from the
|
||||
// field tests (2026-07-04 and 2026-07-30).
|
||||
// owner field tests (2026-07-04 and 2026-07-30).
|
||||
const signature = projectionFrameSignature(runtime);
|
||||
runtime.drawnImageStamp = runtime.imageStamp;
|
||||
if (signature !== null && signature === runtime.lastFrameSignature) {
|
||||
@@ -2732,7 +2732,7 @@ function refreshHorizonCulling() {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Ambient card tier (2026-07-29 design — spec:
|
||||
// docs/CURRENT-STATE.md)
|
||||
// docs/superpowers/specs/2026-07-29-cctv-ambient-cards-design.md)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Returns (creating on demand) the stable frame slot for a camera id. */
|
||||
@@ -2805,7 +2805,7 @@ function refreshAmbientCards() {
|
||||
});
|
||||
}
|
||||
|
||||
// Field finding 4: current card holders rank with the 20% incumbency
|
||||
// Owner finding 4: current card holders rank with the 20% incumbency
|
||||
// distance discount, so a small camera move never batch-swaps the ring.
|
||||
// Item C: passing the viewport dims + per-candidate screen anchors routes
|
||||
// the budget fill through the screen-distribution grid, so periphery
|
||||
@@ -2840,7 +2840,7 @@ function refreshAmbientCards() {
|
||||
})),
|
||||
{ limit: cardLimit }
|
||||
);
|
||||
// Field finding 2: grace must never apply to the active camera — drop any
|
||||
// Owner finding 2: grace must never apply to the active camera — drop any
|
||||
// lingering grace entry and keep it out of the retained-card baseline.
|
||||
if (activeId) {
|
||||
_cardIds.delete(activeId);
|
||||
@@ -2911,7 +2911,7 @@ function pushAmbientCardEntries() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Throttled MOUSE_MOVE hover pass (follow-up round 2, item B): pointing at a
|
||||
* Throttled MOUSE_MOVE hover pass (owner round 2, item B): pointing at a
|
||||
* camera icon that has no card summons its card immediately. This is
|
||||
* EVENT-DRIVEN picking on a user gesture, not steady-state work — the
|
||||
* ≥120 ms throttle caps it at ~8 scene.pick calls/s while the pointer is
|
||||
@@ -3008,7 +3008,7 @@ function hoverFetchCardFrame(record) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Card-frame pacer tick (field finding 3): launches AT MOST one fetch per
|
||||
* Card-frame pacer tick (owner finding 3): launches AT MOST one fetch per
|
||||
* tick, with cardFetchPolicy deciding whether a launch is allowed. Cold fill
|
||||
* — any selected card still missing its FIRST frame — bursts up to 4
|
||||
* in-flight fetches at 250 ms spacing so arriving in a new area populates
|
||||
@@ -3592,7 +3592,7 @@ function ensureGizmo() {
|
||||
}
|
||||
|
||||
/**
|
||||
* §9.1 activation obstruction probe (LOCKED product rule): on camera
|
||||
* §9.1 activation obstruction probe (LOCKED owner decision): on camera
|
||||
* ACTIVATION only, fire ONE scene.pickFromRay along the frustum axis
|
||||
* (mount → cap-center direction). If it hits the tiles closer than the pose
|
||||
* range, clamp the plane's effective range just short of the first hit so the
|
||||
@@ -4362,7 +4362,7 @@ const cctvLayer = {
|
||||
// remain eligible for true empty-space deselection.
|
||||
const pickedId = resolvePickId(picked);
|
||||
if (pickedId !== null) return;
|
||||
// Item A (follow-up round 2): the scene pick found no camera — try the
|
||||
// Item A (owner round 2): the scene pick found no camera — try the
|
||||
// painted ambient cards. The cards canvas is pointer-events:none (this
|
||||
// handler owns the events), so a click landing on a card's rect selects
|
||||
// its camera exactly like a click on the icon. Cesium click positions
|
||||
@@ -4451,7 +4451,7 @@ const cctvLayer = {
|
||||
_removeFocusAppearListener = null;
|
||||
stopProjectionLoop();
|
||||
stopGeometryLoadQueue();
|
||||
// Ambient cards tear down COMPLETELY on disable (product design point 6):
|
||||
// Ambient cards tear down COMPLETELY on disable (owner design point 6):
|
||||
// source entries, pacer timer, in-flight handlers, and caches.
|
||||
teardownAmbientCards();
|
||||
hideCctvVisuals();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/data/cctv.test.mjs — CCTV v2 pure frustum geometry (computeFrustumGeometry).
|
||||
//
|
||||
// Locks the CCTV frustum geometry described in docs/CURRENT-STATE.md:
|
||||
// Locks the §2a math of docs/plans/2026-07-03-cctv-v2-design.md:
|
||||
// - the far-cap (monitor plane) corners lie ON the plane through capCenter
|
||||
// perpendicular to the frustum view axis (ε < 0.5 m) — this is the geometric
|
||||
// invariant that welds the wireframe corner rays to the plane entity;
|
||||
@@ -402,7 +402,7 @@ test('ground clamp lifts the CAP CENTER only — the rectangle stays rigid (true
|
||||
assert.equal(g.capCenter.alt, floor, 'cap center clamps exactly to the floor');
|
||||
// Corners derive rigidly from the lifted center: alt = floor ± cos(pitch)·halfH.
|
||||
// The bottom pair sits BELOW the floor (tiles occlude it) — per-corner clamping
|
||||
// is what flattened the wireframe into a fan (field test 2026-07-04).
|
||||
// is what flattened the wireframe into a fan (owner field test 2026-07-04).
|
||||
const upVert = Math.cos(toRad(-24)) * g.halfH;
|
||||
for (const key of ['tl', 'tr']) {
|
||||
assert.ok(Math.abs(g.corners[key].alt - (floor + upVert)) < 1e-6, `${key} alt ${g.corners[key].alt}`);
|
||||
@@ -415,7 +415,7 @@ test('ground clamp lifts the CAP CENTER only — the rectangle stays rigid (true
|
||||
test('clamped pose keeps corner/plane coincidence and the rigid 2·halfW × 2·halfH span', () => {
|
||||
// The wireframe corner rays must terminate exactly on the monitor plane's
|
||||
// corners AT THE DEFAULT AUSTIN POSE — this is the case that diverged by
|
||||
// ~47.5 m under per-corner clamping (field test 2026-07-04).
|
||||
// ~47.5 m under per-corner clamping (owner field test 2026-07-04).
|
||||
const g = computeFrustumGeometry(AUSTIN_FABRICATED_CAMERA, AUSTIN_GROUND);
|
||||
const d = viewDir(41, -24);
|
||||
const capEnu = enu(g.mount, g.capCenter);
|
||||
@@ -1090,7 +1090,7 @@ test('heading wrap: heading 350° produces a symmetric cap (left/right corners e
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Task 5 — calibration v2 store + CAL badge (design §3b/§3c as amended by the
|
||||
// LOCKED product rules §9.2/§9.3: wipe-clean v2 store, panel-only badge).
|
||||
// LOCKED owner decisions §9.2/§9.3: wipe-clean v2 store, panel-only badge).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Minimal in-memory localStorage stand-in for pure store-IO unit tests. */
|
||||
@@ -1167,7 +1167,7 @@ test('calibration v2: a corrupt v1 key never leaks into the v2 store (v1 is dead
|
||||
});
|
||||
// v2 key is untouched/empty — v1's presence must have zero effect.
|
||||
const restored = readCalibrationStoreV2(storage);
|
||||
assert.equal(restored.size, 0, 'v2 store must start empty — no legacy import (product rule #3, §9.3)');
|
||||
assert.equal(restored.size, 0, 'v2 store must start empty — no legacy import (owner decision #3, §9.3)');
|
||||
assert.ok(!restored.has('austin-42'));
|
||||
});
|
||||
|
||||
@@ -1190,7 +1190,7 @@ test('deriveCalBadge: RAW PRIOR for everything else (all Austin Open Data today)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Task 5 (height-datum fix): regime-aware ground resolution pure helpers.
|
||||
// the height-datum contract in docs/CURRENT-STATE.md.
|
||||
// docs/superpowers/specs/2026-07-05-entity-height-datum-design.md §2.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('surfaceRegimeKey: globe hidden (photoreal) → google-3d; globe visible → terrain-globe', () => {
|
||||
@@ -1236,7 +1236,7 @@ test('normalizeCoverageMode: garbage keeps the current mode', () => {
|
||||
assert.equal(normalizeCoverageMode(3, 'on'), 'on');
|
||||
});
|
||||
|
||||
// Unchanged-frame signature (white-flash fix, field test 2026-07-30)
|
||||
// Unchanged-frame signature (white-flash fix, owner field test 2026-07-30)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Builds an RGBA buffer from [r,g,b] triples. */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @module cctvCards
|
||||
* @description Screen-space thumbnail cards for the citywide ambient CCTV
|
||||
* tier described in `docs/CURRENT-STATE.md`.
|
||||
* tier (design: docs/superpowers/specs/2026-07-29-cctv-ambient-cards-design.md).
|
||||
* Replaces the rejected world-space static-plane ring with small canvas cards
|
||||
* anchored to each LOD-selected camera's screen position, showing its latest
|
||||
* paced static frame.
|
||||
@@ -18,10 +18,10 @@
|
||||
* persistence, retry pacing, cache pruning) and supplies ready-to-draw stable
|
||||
* frame-slot references to the host.
|
||||
*
|
||||
* Zero-flicker contract (product requirement):
|
||||
* Zero-flicker contract (owner requirement):
|
||||
* - An AMBIENT entry whose frame slot has no drawn frame yet (`stamp === 0`)
|
||||
* renders NOTHING — no placeholder, no chip. The camera icon alone carries
|
||||
* it. Sole documented exception (follow-up round 2, item B): a PINNED entry
|
||||
* it. Sole documented exception (owner round 2, item B): a PINNED entry
|
||||
* (hover-summoned, `entry.pinned === true`) paints its chrome immediately —
|
||||
* explicit user gesture wants instant feedback — with an empty thumb area
|
||||
* until its fast-tracked frame lands.
|
||||
@@ -48,7 +48,7 @@ export const CCTV_FRAME_CANVAS_W = 192;
|
||||
export const CCTV_FRAME_CANVAS_H = 108;
|
||||
/**
|
||||
* Min screen separation between accepted card anchors (greedy declutter).
|
||||
* Field test 2026-07-30: 130 read too sparse once the HUD safe-zone
|
||||
* Owner field test 2026-07-30: 130 read too sparse once the HUD safe-zone
|
||||
* filter started dropping cards as well. 112 still exceeds the card box width
|
||||
* (104 px) so accepted boxes cannot overlap. This is THE density knob.
|
||||
*/
|
||||
@@ -59,8 +59,8 @@ export const CCTV_CARD_SAFE_TOP_MAX_PX = 150;
|
||||
/** Bounded thumbnail cache (frame slots kept beyond the live card set). */
|
||||
export const CCTV_FRAME_CACHE_MAX = 96;
|
||||
|
||||
// ─── Altitude scaling (field test finding 5, 2026-07-29) ──────────────
|
||||
// Validated curve: cards are full size at street level, "start to get
|
||||
// ─── Altitude scaling (owner field-test finding 5, 2026-07-29) ──────────────
|
||||
// Owner-decided curve: cards are full size at street level, "start to get
|
||||
// smaller" from ~1,800 m, "scale down progressively" to ~0.45 by 6,000 m,
|
||||
// keep shrinking slightly and alpha-fade out across 7,500→9,500 m, and are
|
||||
// fully hidden above that ("just the icons" at the highest zooms).
|
||||
@@ -71,7 +71,7 @@ export const CCTV_CARD_FADE_END_M = 9_500;
|
||||
export const CCTV_CARD_SCALE_AT_MID = 0.45;
|
||||
export const CCTV_CARD_SCALE_MIN = 0.35;
|
||||
|
||||
// ─── Frame-fetch pacing (field test finding 3, 2026-07-29) ────────────
|
||||
// ─── Frame-fetch pacing (owner field-test finding 3, 2026-07-29) ────────────
|
||||
/** Steady-state global gate: one card-frame fetch per second. */
|
||||
export const CCTV_CARD_FETCH_STEADY_SPACING_MS = 1_000;
|
||||
/** Cold-fill burst spacing between fetch launches. */
|
||||
@@ -150,7 +150,7 @@ export function declutterCctvCards(candidates, { minSepPx = CCTV_CARD_MIN_SEP_PX
|
||||
}
|
||||
|
||||
/**
|
||||
* Altitude-driven card scale + opacity (field test finding 5,
|
||||
* Altitude-driven card scale + opacity (owner field-test finding 5,
|
||||
* 2026-07-29 — curve constants above). Piecewise, monotonic non-increasing
|
||||
* in both channels:
|
||||
* - ≤1,800 m: full size, fully opaque.
|
||||
@@ -178,7 +178,7 @@ export function cardScaleForAltitude(cameraHeightM) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cold-fill burst pacing policy (field test finding 3, 2026-07-29), as
|
||||
* Cold-fill burst pacing policy (owner field-test finding 3, 2026-07-29), as
|
||||
* a pure decision so the pacer tick stays trivially testable. While any
|
||||
* selected card still lacks its FIRST frame (`coldFill`), up to
|
||||
* `CCTV_CARD_FETCH_BURST_LIMIT` fetches may be in flight with
|
||||
|
||||
@@ -129,7 +129,7 @@ test('CCTV card module cannot resurrect a canvas, projection, listener, or priva
|
||||
for (const pattern of forbidden) assert.doesNotMatch(source, pattern);
|
||||
});
|
||||
|
||||
// ─── cardScaleForAltitude — the validated altitude curve (finding 5) ────
|
||||
// ─── cardScaleForAltitude — the owner-decided altitude curve (finding 5) ────
|
||||
|
||||
test('cardScaleForAltitude: full size and opacity at or below 1,800 m', () => {
|
||||
assert.deepEqual(cardScaleForAltitude(0), { scale: 1, alpha: 1 });
|
||||
@@ -137,7 +137,7 @@ test('cardScaleForAltitude: full size and opacity at or below 1,800 m', () => {
|
||||
assert.deepEqual(cardScaleForAltitude(NaN), { scale: 1, alpha: 1 });
|
||||
});
|
||||
|
||||
test('cardScaleForAltitude: hits the validated waypoints', () => {
|
||||
test('cardScaleForAltitude: hits the owner-decided waypoints', () => {
|
||||
const mid = cardScaleForAltitude(CCTV_CARD_SCALE_MID_M);
|
||||
assert.ok(Math.abs(mid.scale - CCTV_CARD_SCALE_AT_MID) < 1e-9, 'scale ~0.45 at 6,000 m');
|
||||
assert.equal(mid.alpha, 1);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @module cctvGizmo
|
||||
*
|
||||
* Direct-manipulation calibration gizmo for the CCTV layer (design:
|
||||
* the CCTV calibration contract in `docs/CURRENT-STATE.md`).
|
||||
* docs/superpowers/specs/2026-07-05-cctv-viewshed-gizmo-design.md §3c).
|
||||
*
|
||||
* Two layers:
|
||||
* - Pure drag math (this top section): ray↔axis closest-point, ray↔plane
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/data/cctvGizmo.test.mjs — pure drag math for the CCTV calibration gizmo
|
||||
// documented in docs/CURRENT-STATE.md.
|
||||
// (docs/superpowers/specs/2026-07-05-cctv-viewshed-gizmo-design.md §3c).
|
||||
//
|
||||
// Locks:
|
||||
// - closestParamOnAxis returns the metre-parameter along the AXIS of the
|
||||
|
||||
+12
-11
@@ -7,15 +7,16 @@
|
||||
* card set, keeps that set stable across small camera moves (eviction
|
||||
* grace), and paces static-frame refreshes per source.
|
||||
*
|
||||
* The engine retains zoom-scaled budgets, distance-ranked in-view selection,
|
||||
* an eviction-grace planner, and source-aware refresh cadences, retargeted
|
||||
* from the rejected world-space static-plane ring to the
|
||||
* Engine adapted from Manjunath's Part C work (`fix/cctv-part-c-review`):
|
||||
* the zoom-scaled budgets, distance-ranked in-view selection, the
|
||||
* eviction-grace planner, and the source-aware refresh cadences are his,
|
||||
* retargeted from the rejected world-space static-plane ring to the
|
||||
* screen-space thumbnail cards (see
|
||||
* the ambient-card behavior documented in `docs/CURRENT-STATE.md`).
|
||||
* docs/superpowers/specs/2026-07-29-cctv-ambient-cards-design.md).
|
||||
*/
|
||||
|
||||
// Ambient-card budgets (follow-up round 2, item C: raised 16/24/32 → 20/28/40 —
|
||||
// "a lot of empty space"). Tunable as a set, together with the card
|
||||
// Ambient-card budgets (owner round 2, item C: raised 16/24/32 → 20/28/40 —
|
||||
// "a lot of empty space"). Owner-tunable as a set, together with the card
|
||||
// scale waypoints (1,800/6,000/9,500 m, cctvCards.js) and
|
||||
// CCTV_CARD_MIN_SEP_PX: budgets say how many cameras HOLD cards, the
|
||||
// waypoints and separation say how many fit on screen.
|
||||
@@ -34,7 +35,7 @@ const PROVIDER_STATIC_REFRESH_MS = Object.freeze({
|
||||
|
||||
/**
|
||||
* Returns the bounded ambient-card budget for the current viewer height.
|
||||
* Card counts stay inside the 20..40 range (follow-up round 2): street level
|
||||
* Card counts stay inside the 20..40 range (owner round 2): street level
|
||||
* keeps the overlay sparse, metro scale earns the full ring.
|
||||
*
|
||||
* @param {number} cameraHeightM
|
||||
@@ -52,7 +53,7 @@ export function cctvLodBudgets(cameraHeightM) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Selection-level incumbency (field test finding 4, 2026-07-29): a
|
||||
* Selection-level incumbency (owner field-test finding 4, 2026-07-29): a
|
||||
* camera currently holding a card ranks with its distance discounted by this
|
||||
* factor, so a small camera move never batch-swaps the ring — a non-carded
|
||||
* camera displaces a carded one only when it is meaningfully (>20%) closer.
|
||||
@@ -144,17 +145,17 @@ export function blendCenterRankKm(
|
||||
return (1 - w) * km + w * spread * fraction;
|
||||
}
|
||||
|
||||
// Screen-distribution grid (follow-up round 2, item C — "a lot of empty space"):
|
||||
// Screen-distribution grid (owner round 2, item C — "a lot of empty space"):
|
||||
// pure nearest-first selection clusters winners at screen center (nearest ==
|
||||
// most central at typical view pitch) and leaves the periphery bare. The
|
||||
// viewport is bucketed into this grid and every occupied cell gets its best
|
||||
// candidate before global rank fills the rest. Tunable together with
|
||||
// candidate before global rank fills the rest. Owner-tunable together with
|
||||
// the budgets above.
|
||||
export const CCTV_CARD_GRID_COLS = 5;
|
||||
export const CCTV_CARD_GRID_ROWS = 4;
|
||||
|
||||
/**
|
||||
* Screen-space distribution pass (follow-up round 2, item C). Buckets the
|
||||
* Screen-space distribution pass (owner round 2, item C). Buckets the
|
||||
* viewport into a CCTV_CARD_GRID_COLS × CCTV_CARD_GRID_ROWS grid, assigns
|
||||
* each candidate to its cell (anchors just outside the viewport clamp to
|
||||
* the edge cells), ranks within cells by effective distance (`rankKm` —
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/data/cctvLod.test.mjs
|
||||
// Pure LOD-engine tests, adapted from earlier Part C suite
|
||||
// zoom-scaled card budgets, nearest-first in-view
|
||||
// Pure LOD-engine tests, adapted from Manjunath's Part C suite
|
||||
// (fix/cctv-part-c-review): zoom-scaled card budgets, nearest-first in-view
|
||||
// selection, video exclusion, the eviction-grace planner, and source-aware
|
||||
// static-frame pacing.
|
||||
import { test } from 'node:test';
|
||||
@@ -35,7 +35,7 @@ function candidates(count, options = {}) {
|
||||
}
|
||||
|
||||
test('cctvLodBudgets scales the card budget from 20 to 40 with view height', () => {
|
||||
// Follow-up round 2 (item C): budgets raised 16/24/32 -> 20/28/40.
|
||||
// Owner round 2 (item C): budgets raised 16/24/32 -> 20/28/40.
|
||||
assert.equal(CCTV_AMBIENT_CARD_MIN, 20);
|
||||
assert.equal(CCTV_AMBIENT_CARD_MID, 28);
|
||||
assert.equal(CCTV_AMBIENT_CARD_MAX, 40);
|
||||
@@ -71,7 +71,7 @@ test('selectCctvLod tolerates malformed candidate rows', () => {
|
||||
assert.deepEqual(selected.cardIds, ['cam-ok']);
|
||||
});
|
||||
|
||||
// ─── Selection-level incumbency (field test finding 4) ────────────────
|
||||
// ─── Selection-level incumbency (owner field-test finding 4) ────────────────
|
||||
|
||||
test('incumbentRankKm discounts incumbents by the 20% factor', () => {
|
||||
assert.equal(incumbentRankKm(10, false), 10);
|
||||
@@ -121,7 +121,7 @@ test('selectCctvLod: no incumbents means plain nearest-first (unchanged behavior
|
||||
assert.deepEqual(plain.cardIds, withEmpty.cardIds);
|
||||
});
|
||||
|
||||
// ─── distributeCctvCards — screen distribution (follow-up round 2, item C) ──────
|
||||
// ─── distributeCctvCards — screen distribution (owner round 2, item C) ──────
|
||||
// Grid: 5 cols x 4 rows. At viewW 1000 / viewH 800 each cell is 200x200 px.
|
||||
|
||||
test('distributeCctvCards spreads clustered candidates across cells', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @module cctvViewshed
|
||||
*
|
||||
* Viewshed presentation for the CCTV layer (design:
|
||||
* the CCTV viewshed geometry contract).
|
||||
* docs/superpowers/specs/2026-07-05-cctv-viewshed-gizmo-design.md §3a/§3b).
|
||||
*
|
||||
* Two responsibilities, both pure of layer state:
|
||||
* - Color identity: a stable per-camera hue (golden-angle spaced over the
|
||||
@@ -30,7 +30,7 @@ const LINE_ALPHA_ACTIVE = 1.0;
|
||||
* Stable hue (degrees, [0, 360)) for a camera's position in the id-sorted
|
||||
* catalog. Golden-angle spacing keeps any local cluster of neighbor cameras
|
||||
* visually separated; id-sorting makes the assignment deterministic across
|
||||
* sessions for a stable catalog (design §3a, open question Q4).
|
||||
* sessions for a stable catalog (design §3a, owner question Q4).
|
||||
* @param {number} index - Camera index in the id-sorted catalog.
|
||||
* @returns {number} Hue in degrees.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/data/cctvViewshed.test.mjs — viewshed hue assignment + frustum volume
|
||||
// geometry documented in docs/CURRENT-STATE.md.
|
||||
// geometry (docs/superpowers/specs/2026-07-05-cctv-viewshed-gizmo-design.md §3a/§3b).
|
||||
//
|
||||
// Locks:
|
||||
// - cameraHue is golden-angle spaced and deterministic (color identity is
|
||||
|
||||
@@ -42,7 +42,7 @@ for (const layer of LAYERS) {
|
||||
assert.match(regime, /if \(!_models3dEnabled\) return false;/,
|
||||
'OFF must keep Cockpit AIR contacts in 2D');
|
||||
assert.doesNotMatch(regime, /!_models3dEnabled\s*&&\s*!_cockpitContactMode/,
|
||||
'Cockpit must not bypass the user-visible Display toggle');
|
||||
'Cockpit must not bypass the owner-visible Display toggle');
|
||||
});
|
||||
|
||||
test(`${layer.name}: the pilot's own airframe stays hidden in cockpit`, () => {
|
||||
|
||||
@@ -92,7 +92,7 @@ export function rankContactMatch({ query, hex = '', callsign = '', registration
|
||||
* (`track_entity`) is a MUTATION fulfilling "follow that one" — returning an
|
||||
* ambiguity for the caller to resolve would cost a round-trip mid-demo, and
|
||||
* the model's observed response to a non-ok track result is to retry with
|
||||
* different guesses rather than to ask (field session 2026-08-21,
|
||||
* different guesses rather than to ask (owner field session 2026-08-21,
|
||||
* 23:48). So the lookup always commits. What it owes the caller is STABILITY:
|
||||
* hex is unique and always present, so the same query resolves to the same
|
||||
* contact for as long as both are loaded, instead of flipping between polls
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as Cesium from 'cesium';
|
||||
* Per-layer data attribution registered into Cesium's credit display.
|
||||
*
|
||||
* Legal requirement (see DATA_SOURCES.md, findings H10/H11 in
|
||||
* every third-party data layer this app can
|
||||
* docs/pre-ship-audit-2026-07-01.md): every third-party data layer this app can
|
||||
* display carries its own license and required attribution — ODbL (OSM
|
||||
* datacenters/dams, adsb.lol, Overpass roads), CC BY-NC-SA (TeleGeography
|
||||
* cables), NASA FIRMS, CelesTrak, USGS, City of Austin, GBFS operators, OpenSky.
|
||||
|
||||
@@ -30,7 +30,7 @@ export const AIRCRAFT_BRACKET_ALPHA_FLOOR = 0.35;
|
||||
* stays a pure policy module with no Cesium dependency; detectionPolicy.test.mjs
|
||||
* imports the real constant and pins the two together so they cannot drift.
|
||||
*
|
||||
* It MOVES WITH THE DEFAULT (0.05 → 0.03 → 0.01; final value 2026-08-24).
|
||||
* It MOVES WITH THE DEFAULT (0.05 → 0.03 → 0.01; owner final lock 2026-08-24).
|
||||
* That pin is the tripwire
|
||||
* for exactly this change, and the decision it forces is which of two things the
|
||||
* approval attaches to: the bracket BRIGHTNESS, or the slider POSITION. It is
|
||||
|
||||
@@ -36,7 +36,7 @@ test('the bracket floor anchor mirrors the real keyhole default it is calibrated
|
||||
|
||||
test('the default OUTSIDE setting reproduces the approved 0.35 floor exactly', () => {
|
||||
// Byte-identical at the default, at every keyhole alpha, with the setting
|
||||
// passed explicitly and with it omitted. This is the accepted look; only
|
||||
// passed explicitly and with it omitted. The owner approved this look; only
|
||||
// the off-default range is allowed to change.
|
||||
assert.equal(aircraftBracketAlphaFloor(KEYHOLE_OUTSIDE_OPACITY_DEFAULT), AIRCRAFT_BRACKET_ALPHA_FLOOR);
|
||||
for (const alpha of [0.01, 0.05, 0.2, 0.34, 0.35, 0.36, 0.7, 1]) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* 2. Free-form name/description text ("5TH ST / WEST AVE", "N LAMAR BLVD") is
|
||||
* full of STREET names that merely contain a cardinal word. Reading a bare
|
||||
* "West" there as a facing direction mis-orients the camera with false
|
||||
* confidence (59 of ~1000 Austin cameras hit this — field review
|
||||
* confidence (59 of ~1000 Austin cameras hit this — owner adversarial review
|
||||
* 2026-07-04). There, only explicit travel forms ("WESTBOUND"/"WB") count —
|
||||
* leave `allowBare=false` (the default).
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// directionToHeading — two matching modes. The regression that motivated the
|
||||
// split (adversarial field review, 2026-07-04): bare cardinal words were
|
||||
// split (owner adversarial review, 2026-07-04): bare cardinal words were
|
||||
// matched in free-form Austin camera names, so a street like "5TH ST / WEST
|
||||
// AVE" was mis-read as a west-facing camera with false high confidence.
|
||||
import { test } from 'node:test';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/data/fireAnchors.js — DEM ground anchors for rendered FIRMS detections
|
||||
// (field finding 2026-07-21: at close/oblique zoom over high country,
|
||||
// (owner field finding 2026-07-21: at close/oblique zoom over high country,
|
||||
// fire dots anchored at ellipsoid height 0 read as buried inside the terrain
|
||||
// ~1-2 km below the visible surface).
|
||||
//
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/data/fireAnchors.test.mjs — DEM ground anchors for rendered FIRMS
|
||||
// detections (field finding 2026-07-21: close-zoom fire dots read as
|
||||
// detections (owner field finding 2026-07-21: close-zoom fire dots read as
|
||||
// buried under high terrain because anchors sat at ellipsoid height 0).
|
||||
//
|
||||
// Locks the module's two jobs:
|
||||
|
||||
@@ -85,7 +85,7 @@ test('buildCellCard: plural noun, max FRP and newest age, accent passthrough', (
|
||||
assert.equal(card.accent, accentForSeverity('orange'));
|
||||
});
|
||||
|
||||
// Field finding 2026-07-21: anchors must sit on the DEM once the shared
|
||||
// Owner field finding 2026-07-21: anchors must sit on the DEM once the shared
|
||||
// ground floor is warm — and the cached per-fire position must re-anchor when
|
||||
// the floor lands AFTER the first (cold, height-0) render. Distinct coords
|
||||
// from every other test in this file (module caches persist across tests).
|
||||
|
||||
@@ -320,7 +320,6 @@ export function createFirmsHeatmapLayer({
|
||||
lastUpdate: _lastUpdate,
|
||||
loading: _loading,
|
||||
stale: _stale,
|
||||
keyRequired: _keyRequired,
|
||||
error: _keyRequired ? 'KEY REQUIRED' : (_stale ? staleText : _error),
|
||||
loadingLabel,
|
||||
};
|
||||
|
||||
+23
-29
@@ -17,7 +17,7 @@
|
||||
*
|
||||
* Press Escape or click empty space to deselect a tracked flight — the camera
|
||||
* is released IN PLACE (no flyTo), so the user keeps the context they were
|
||||
* looking at (product rule 2026-07-02).
|
||||
* looking at (owner decision 2026-07-02).
|
||||
*/
|
||||
import * as Cesium from 'cesium';
|
||||
import { aircraftIncludedInNearby } from './aircraftNearbyPolicy.js';
|
||||
@@ -100,14 +100,14 @@ const FOCUS_EVIDENCE_DEV = import.meta.env?.DEV === true;
|
||||
/** Amber tint for known-military aircraft rendered by this layer (matches the military layer's icon color). */
|
||||
const MIL_TINT = Cesium.Color.fromCssColorString('#FFB800');
|
||||
|
||||
// --- Ground traffic (product change 2026-07-03: "absolutely we should see planes
|
||||
// --- Ground traffic (owner reversal 2026-07-03: "absolutely we should see planes
|
||||
// taxiing and landing") -----------------------------------------------------------
|
||||
// Present-but-grounded planes are RENDERED instead of being skipped: same class
|
||||
// silhouette + rotation pipeline, clickable/trackable/detectable, sticky metadata
|
||||
// updating normally. Landing/takeoff is a TRANSITION — the on_ground flip restyles
|
||||
// the existing billboard in place, never a removal. Ground planes draw no trails
|
||||
// and are excluded from the ambient enrichment sweep (click-to-enrich still
|
||||
// works). In 3D mode they take model slots like airborne planes (product rule
|
||||
// works). In 3D mode they take model slots like airborne planes (owner decision
|
||||
// 2026-07-03 — no air/ground distinction), placed by the one-shot ground snap
|
||||
// (see _modelDisplayPosition).
|
||||
//
|
||||
@@ -121,7 +121,7 @@ const MIL_TINT = Cesium.Color.fromCssColorString('#FFB800');
|
||||
const GROUND_SCALE = 0.8;
|
||||
|
||||
/** Fleet (untracked) billboard tint: amber for known-military, white otherwise.
|
||||
* Ground traffic gets NO special tint (validated behavior 2026-07-03 field test). */
|
||||
* Ground traffic gets NO special tint (owner verdict 2026-07-03 field test). */
|
||||
function _fleetBillboardColor(icao24) {
|
||||
return isMilitaryIcao(icao24) ? MIL_TINT : Cesium.Color.WHITE;
|
||||
}
|
||||
@@ -131,7 +131,7 @@ function _fleetBillboardScale(icao24, klass) {
|
||||
return (CLASS_SCALE_2D[klass] || 1) * (_flightData.get(icao24)?.onGround ? GROUND_SCALE : 1);
|
||||
}
|
||||
|
||||
/** Depth-test policy for aircraft billboards. Round 5 (product invariant
|
||||
/** Depth-test policy for aircraft billboards. Round 5 (owner directive
|
||||
* 2026-07-06: "I just want the planes and their lines to ALWAYS be
|
||||
* visible... evenly applied"): EVERY contact renders depth-test-free at
|
||||
* every distance — grounded, low, and airborne alike. The photoreal mesh
|
||||
@@ -157,7 +157,7 @@ const MODEL_MIN_PX = 24; // floor so distant models stay visible WITHOUT
|
||||
// min-pixel blob (was 54 — far planes at the All radius became white
|
||||
// star-bursts); ~matches the 2D icon size so the model↔billboard read is consistent
|
||||
const TRACKED_MODEL_MIN_PX = 40; // keep the glTF silhouette comparable to the selected 2D glyph at handoff
|
||||
export const TRACKED_MODEL_MAX_PX = 200; // selected close-range tracked-target feel
|
||||
export const TRACKED_MODEL_MAX_PX = 200; // owner-selected close-range tracked-target feel
|
||||
const MODEL_NATIVE_RADIUS_M = 34.41;
|
||||
const MODEL_SCALE = 1; // airplane.glb is transform-applied and baked to real-world meters
|
||||
// Per-mode caps. Each model is its own draw call (no instancing yet), so these bound the frame cost.
|
||||
@@ -246,7 +246,7 @@ const _modelGen = new Map();
|
||||
/** Lifecycle epoch; bumped on destroy so an in-flight load from a PREVIOUS init can't settle
|
||||
* against a new lifecycle's globals (which destroy cleared). Captured by _ensureModel. */
|
||||
let _modelEpoch = 0;
|
||||
/** DEFAULT-ON in PROXIMITY (product invariant 2026-08-22). A fresh boot never runs
|
||||
/** DEFAULT-ON in PROXIMITY (owner directive 2026-08-22). A fresh boot never runs
|
||||
* layer-state restoration, so this initializer — not the codec — is what the app
|
||||
* actually starts with; it must stay in lockstep with the `models3d` default in
|
||||
* `layerState.js` and `this._models3dEnabled` in ui.js, or the DISPLAY rail would
|
||||
@@ -457,7 +457,7 @@ const TRACKED_BILLBOARD_SCALE_BY_DISTANCE = new Cesium.NearFarScalar(
|
||||
);
|
||||
|
||||
function _normalBillboardScaleByDistance() {
|
||||
// Preserve the established close-range 3× scale. Any smaller user-visible
|
||||
// Preserve the established close-range 3× scale. Any smaller owner-visible
|
||||
// default belongs in a separate evidence-backed proposal.
|
||||
return new Cesium.NearFarScalar(1000, 3.0, 8000000, 0.5);
|
||||
}
|
||||
@@ -599,7 +599,7 @@ let _trailBackfillToken = 0;
|
||||
const RENDER_DELAY_SEC = 30;
|
||||
/** @constant {number} Polls an aircraft may miss before removal (transient OpenSky dropouts). */
|
||||
const MISSING_POLL_LIMIT = 3;
|
||||
// --- Landed-plane fast cull (field report 2026-07-02: "phantom" planes
|
||||
// --- Landed-plane fast cull (owner field report 2026-07-02: "phantom" planes
|
||||
// lingered ~2 min at airports after touchdown). OpenSky's on_ground flag LAGS
|
||||
// the actual landing, so a landed plane's last airborne-classified fixes show
|
||||
// it low + slow on the runway; when such a plane then drops out of the poll,
|
||||
@@ -652,7 +652,8 @@ function _approxDistanceKm(lat1, lon1, lat2, lon2) {
|
||||
function _likelyLanded(icao24) {
|
||||
const info = _flightData.get(icao24);
|
||||
if (!info) return false;
|
||||
// Round 7: the fast cull only applies to contacts that were AIRBORNE
|
||||
// Round 7 (owner: "fewer planes than OpenSky's own map" + "parked planes
|
||||
// never heal"): the fast cull only applies to contacts that were AIRBORNE
|
||||
// this session — its original target, the post-LANDING ghost. OpenSky's
|
||||
// ground coverage flaps constantly, so fast-culling every grounded contact
|
||||
// put parked planes in an evict/re-enter churn: each re-entry was a
|
||||
@@ -1579,14 +1580,14 @@ function _noteTrackedModelLoadFailure(url, err) {
|
||||
|
||||
/**
|
||||
* The TRACKED aircraft's own model regime — DEFAULT-ON, camera-distance driven
|
||||
* (product invariant 2026-08-19). Unlike the fleet, this does NOT consult the
|
||||
* (owner directive 2026-08-19). Unlike the fleet, this does NOT consult the
|
||||
* DISPLAY-rail `models3d` toggle: the selected contact is a single model, it is
|
||||
* what the camera is pointed at, and zooming in on a target should resolve it
|
||||
* into an aircraft without the operator arming anything. The toggle keeps
|
||||
* owning the FLEET (`_modelRegimeActive`), which is the draw-call budget.
|
||||
*
|
||||
* Thresholds + hysteresis live in trackedModelRegime.js: enter at
|
||||
* TRACKED_MODEL_ENTER_ALT_M (150_000 m — the playtested swap distance,
|
||||
* TRACKED_MODEL_ENTER_ALT_M (150_000 m — the owner's playtested swap distance,
|
||||
* deliberately NEARER than the fleet's 800 km ceiling this used to inherit),
|
||||
* hand back only above TRACKED_MODEL_EXIT_ALT_M, so orbiting AT the boundary
|
||||
* cannot flap billboard↔model. See that module's header for why the tracked
|
||||
@@ -1897,7 +1898,7 @@ const FLOOR_EASE_EPSILON_M = 0.02;
|
||||
/**
|
||||
* The floor to stand a grounded contact on while its own cell is unresolved.
|
||||
*
|
||||
* Product invariant (2026-08-21, after a Re:Earth outage buried a parked contact
|
||||
* Owner directive (2026-08-21, after a Re:Earth outage buried a parked contact
|
||||
* at a Texas field): "hold the last known altitude until a fresh one comes in.
|
||||
* Never render otherwise." Two tiers, strongest first:
|
||||
* own — a floor this contact's OWN cell resolved to while it stood there.
|
||||
@@ -1976,7 +1977,7 @@ function _dropHeldFloor(state) {
|
||||
/** @constant {number} How long a retired hold stays usable as a rehydration
|
||||
* seed — three poll intervals.
|
||||
*
|
||||
* Deleting the state outright was the first cut, and a field observation found
|
||||
* Deleting the state outright was the first cut, and an owner sighting found
|
||||
* what that costs: VIR138M at JFK, 45 kt down the runway, "clearly on good
|
||||
* ground, then suddenly popped below the ground, then popped back up".
|
||||
* OpenSky's `on_ground` flag is not clean through a rotation — it flaps — and
|
||||
@@ -2129,7 +2130,7 @@ function _floorGroundedDisplayPosition(icao24, info, pos, modelOwnsVisual, nowMs
|
||||
next.heldActive = Number.isFinite(effective);
|
||||
}
|
||||
// The floor moved DOWN under a contact that was standing on a BORROWED one.
|
||||
// Dropping it by that difference in a single tick is the snap product behavior requires
|
||||
// Dropping it by that difference in a single tick is the snap the owner asked
|
||||
// not to have, so approach it instead. Two ways in, and both need it:
|
||||
// - the real floor arrives below the hold (releasing the hold);
|
||||
// - a re-probe finds a LOWER neighbour than the one being held, which the
|
||||
@@ -2309,12 +2310,6 @@ export function _clearDisplayFloorStateForTest() {
|
||||
_displayFloorState.clear();
|
||||
}
|
||||
|
||||
/** Test hook: drops cached model ground snaps so a browser-harness scenario
|
||||
* cannot inherit another scenario's per-contact measurement. */
|
||||
export function _clearGroundSnapStateForTest() {
|
||||
_groundSnap.clear();
|
||||
}
|
||||
|
||||
/** Spec identity for a LOADED model: URL and scale together (same-URL classes
|
||||
* differ by scale — airliner vs quadjet both ship airplane.glb). */
|
||||
const _specKeyFor = (klass) => {
|
||||
@@ -2341,7 +2336,7 @@ function _syncModelToClass(icao24) {
|
||||
}
|
||||
}
|
||||
|
||||
/** IR hot-target mode (field test 2026-08-16): the NVG/FLIR post-styles
|
||||
/** IR hot-target mode (owner playtest 2026-08-16): the NVG/FLIR post-styles
|
||||
* map LUMINANCE, so mid-gray textured models read cold and vanish into
|
||||
* terrain. While a boost style is active every model renders flat white
|
||||
* (hottest); per-spec color/tint restores on style exit. Driven by ui.js
|
||||
@@ -2679,7 +2674,7 @@ function _fleetTick() {
|
||||
// not just at the handoff below, or accumulated conversions would starve
|
||||
// ordinary contacts of 3D models. (The handoff guard stays as defence.)
|
||||
if (isTr3b(icao)) continue;
|
||||
// Ground planes compete for model slots like everyone else (product rule
|
||||
// Ground planes compete for model slots like everyone else (owner decision
|
||||
// 2026-07-03: "3D mode is respected regardless of whether a plane is on the
|
||||
// ground or in the air — no distinction"). The cap + nearest-first ordering
|
||||
// below already bound airport clusters; grounded placement is handled by the
|
||||
@@ -2782,7 +2777,7 @@ function _fleetTick() {
|
||||
cameraHeightM: camera.positionCartographic?.height,
|
||||
});
|
||||
_billboardLimbScale.set(bb, treatment.factors.scale);
|
||||
// Two-tier glyph raster (field test 2026-08-16): the billboard atlas
|
||||
// Two-tier glyph raster (owner playtest 2026-08-16): the billboard atlas
|
||||
// has no mipmaps, so no single texture stays crisp across the ~25–150
|
||||
// device-px range scaleByDistance produces. Swap between the 64 px fleet
|
||||
// raster and the 192 px close raster on the billboard's ACTUAL on-screen
|
||||
@@ -3079,7 +3074,7 @@ async function _backfillTrail(icao24, token, oldestFixEpochSec) {
|
||||
// and floor every waypoint at it so low baro segments never dive below the
|
||||
// mesh; a no-baro waypoint (predominantly taxi/ground segments in /tracks)
|
||||
// sits ON the surface when the floor is known.
|
||||
// Round-2 fix: the
|
||||
// Round-2 fix (owner: "trails suddenly much shorter / not loading"): the
|
||||
// resolve is BOUNDED (≤1.2 s), not a blocking await — a cold Re:Earth
|
||||
// lookup across a long path could stall the paint for seconds-to-timeout.
|
||||
// Paint with whatever cells are warm; the resolve keeps filling the cache
|
||||
@@ -3891,7 +3886,6 @@ const flightsLayer = {
|
||||
// Browser-harness seam: isolates synthetic display-floor scenarios without
|
||||
// changing any production lifecycle or cache policy.
|
||||
_clearDisplayFloorStateForTest,
|
||||
_clearGroundSnapStateForTest,
|
||||
/** @type {number} Polling interval (ms) between update() calls */
|
||||
updateInterval: 30000,
|
||||
|
||||
@@ -4053,7 +4047,7 @@ const flightsLayer = {
|
||||
* reconcile them with the billboard collection.
|
||||
*
|
||||
* Handles HTTP 429 (rate-limit), 401/403 (auth), and transient errors
|
||||
* with exponential-ish backoff. On success, adds, updates, or removes
|
||||
* with exponential-ish backoff. On success, adds/updates/removes
|
||||
* billboards and position history, triggers lerp blending for the
|
||||
* tracked aircraft, and updates its label text.
|
||||
*
|
||||
@@ -4247,7 +4241,7 @@ const flightsLayer = {
|
||||
// on_ground surface prior: ONLY synchronous warm-cache reads here —
|
||||
// never a per-aircraft network fetch inside the poll loop (see the
|
||||
// batch resolve call below, which fills this cache for NEXT poll). A
|
||||
// Round 5 SIMPLIFICATION (product invariant: one floor, evenly applied):
|
||||
// Round 5 SIMPLIFICATION (owner directive: one floor, evenly applied):
|
||||
// the grounded surface is the round-4 choke point and nothing else —
|
||||
// rendered-mesh cell first, real (never fallback-poisoned) DEM cell
|
||||
// second. The old exact-5-decimal warm chain is GONE: it minted a new
|
||||
@@ -4553,7 +4547,7 @@ const flightsLayer = {
|
||||
// is about to delete (billboard restore, DR cache reset), and we must
|
||||
// never leave the camera mid-follow with stale tracking state. The
|
||||
// camera is then RELEASED IN PLACE — it stays where the follow left
|
||||
// it, fully free (product rule 2026-07-02: no overview flyTo).
|
||||
// it, fully free (owner decision 2026-07-02: no overview flyTo).
|
||||
if (icao24 === _trackedIcao) {
|
||||
_clearTracking(false, { evicted: true });
|
||||
}
|
||||
|
||||
@@ -885,13 +885,13 @@ test('display floor: the cached output is dropped when the floor changes under i
|
||||
|
||||
// --- F8: hold the last known floor through a floor-data gap ----------------
|
||||
//
|
||||
// Field incident 2026-08-21: four `[terrain-heights-proxy] refresh incomplete`
|
||||
// Owner incident 2026-08-21: four `[terrain-heights-proxy] refresh incomplete`
|
||||
// events in a row (Re:Earth timing out), and a parked contact at a Texas field
|
||||
// popped BELOW the photoreal mesh for a few seconds. A cold cell used to mean
|
||||
// "no clamp", which is only safe if the un-clamped height is a real reading —
|
||||
// and for a grounded contact reporting no altitude it is the geoid, tens of
|
||||
// metres under the mesh inland. The product must hold the last known altitude
|
||||
// until fresh floor evidence arrives.
|
||||
// metres under the mesh inland. Owner: "hold the last known altitude until a
|
||||
// fresh one comes in. Never render otherwise."
|
||||
|
||||
test('display floor: a cold cell HOLDS the last floor that resolved for this contact', () => {
|
||||
_clearDisplayFloorStateForTest();
|
||||
@@ -1052,7 +1052,7 @@ test('display floor: an on_ground FLAP mid-takeoff-roll never dips below the run
|
||||
// from the resolved surface to baro + geoid N — which at a sea-level field IS
|
||||
// the geoid. Deleting the hold on the airborne poll made that switch visible:
|
||||
// the contact came back grounded with no prior, outrunning its own floor
|
||||
// cells at 23 m/s, and sat under the runway (field observation, VIR138M).
|
||||
// cells at 23 m/s, and sat under the runway (owner sighting, VIR138M).
|
||||
const GROUND = -28.5, GEOID = -32.5, LON = -73.78;
|
||||
reportMeshFloorCell(40.64, LON, GROUND); // only the cell it STARTED on is warm
|
||||
let lat = 40.64;
|
||||
|
||||
+5
-4
@@ -5,14 +5,15 @@
|
||||
// Caltrans/TfL camera priors) give ORTHOMETRIC height (H, "height above mean
|
||||
// sea level"). N is the local geoid undulation — the gap between the WGS84
|
||||
// ellipsoid and the geoid (~mean sea level) surface, ranging roughly
|
||||
// -106..+85 m worldwide. See docs/CURRENT-STATE.md.
|
||||
// -106..+85 m worldwide. See docs/plans/2026-07-05-entity-height-datum-fix.md.
|
||||
//
|
||||
// The implementation uses `egm96-universal` (npm, MIT, embeds the NGA
|
||||
// Decision rule (task brief): try `egm96-universal` (npm, MIT, embeds the NGA
|
||||
// EGM96 15' grid) as a lazy dynamic import so its ~2.7 MB grid data-chunk
|
||||
// never lands in the eager Vite bundle. Only fall back to vendoring the NGA
|
||||
// grid ourselves if the package fails tests, isn't browser-safe, or bloats
|
||||
// the eager bundle. It passed the browser-safety, accuracy, and bundle checks,
|
||||
// so this file is a thin wrapper around it — no vendored fallback is needed.
|
||||
// the eager bundle. `egm96-universal` passed all three checks (see the
|
||||
// task report), so this file is a thin wrapper around it — no vendored
|
||||
// fallback was needed.
|
||||
//
|
||||
// egm96-universal's `meanSeaLevel(lat, lon)` already returns exactly N in
|
||||
// metres (relative to WGS84 ellipsoid) with internal longitude
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/data/geoid.test.mjs — EGM96 geoid-undulation lookup.
|
||||
// src/data/geoid.test.mjs — EGM96 geoid-undulation lookup (docs/plans/2026-07-05-entity-height-datum-fix.md Task 1).
|
||||
//
|
||||
// Locks the module's public interface (later tasks — aircraft altitude
|
||||
// correction, CCTV terrain fallback — call this verbatim):
|
||||
@@ -7,7 +7,7 @@
|
||||
// orthometricToEllipsoidal(hMslM, latDeg, lonDeg): number hMslM + N
|
||||
//
|
||||
// Tolerance is loose (±2.5 m) by design: the bundled grid is EGM96 while the
|
||||
// reference values are Re:Earth's EGM2008 — the two
|
||||
// plan's "Verified facts" reference values are Re:Earth's EGM2008 — the two
|
||||
// models differ by up to ~1 m, and the brief's own tolerance absorbs that
|
||||
// spread rather than asserting exact agreement.
|
||||
import { test } from 'node:test';
|
||||
@@ -100,7 +100,7 @@ test('the reported SFO cockpit OSD height turns into a small positive MSL number
|
||||
await ensureGeoidReady();
|
||||
const n = geoidHeight(SFO.lat, SFO.lon);
|
||||
assert.ok(n < -25 && n > -40, `SFO undulation should be strongly negative, got ${n}`);
|
||||
// The screenshot showed ALT: -15m ellipsoidal over the SFO deck.
|
||||
// The owner's screenshot: ALT: -15m ellipsoidal over the SFO deck.
|
||||
const displayed = ellipsoidalToMslDisplayM(-15, n);
|
||||
assert.ok(
|
||||
displayed > 10 && displayed < 25,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/data/groundFloor.js — coarse ground-floor clamp for entity render
|
||||
// heights (field-test round 2026-07-06).
|
||||
//
|
||||
// Two field findings drove this module:
|
||||
// Two owner findings drove this module:
|
||||
// - RS46 (military H60): baro-only low-altitude contacts near steep terrain
|
||||
// render INSIDE the hillside (no alt_geom → baro+N is off by more than the
|
||||
// local relief).
|
||||
@@ -118,7 +118,7 @@ export const NEIGHBOR_FLOOR_MIN_SAMPLES = 2;
|
||||
*
|
||||
* An earlier cut leaned HIGH, reasoning from the locked "never below the
|
||||
* visible surface" principle. That principle is about a contact's OWN measured
|
||||
* ground; applied to a BORROWED cell it inverts, and a field test found
|
||||
* ground; applied to a BORROWED cell it inverts, and an owner playtest found
|
||||
* why — planes floating in midair at terminal gates. The two errors are not
|
||||
* symmetric:
|
||||
* - Too LOW is inert. `displayFloorHeightM` only ever RAISES a position, so a
|
||||
@@ -376,7 +376,7 @@ export function allocateCorridorCells(
|
||||
return out;
|
||||
}
|
||||
|
||||
// --- Mesh-floor cells (round 4, validated design) ---------------------
|
||||
// --- Mesh-floor cells (round 4, owner-approved design) ---------------------
|
||||
// The Re:Earth DEM is BARE EARTH; the visible world in the google-3d regime
|
||||
// is the photogrammetric MESH, which sits above it (measured ~17 m at the
|
||||
// Austin airport apron). DEM-flooring therefore still buried sprites/trails
|
||||
|
||||
@@ -597,7 +597,7 @@ test('neighborFloorM takes the apron, not the roof, at a structure edge', () =>
|
||||
reportMeshFloorCell(30.201, -97.66, 120); // the apron
|
||||
reportMeshFloorCell(30.199, -97.66, 205); // the terminal roof next door
|
||||
// A parked contact is on the apron; it is never on the roof. Leaning high
|
||||
// here is what put planes in midair at gates during the field test.
|
||||
// here is what put planes in midair at gates during the owner playtest.
|
||||
assert.equal(neighborFloorM({ lat: 30.2, lon: -97.66 }), 120);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Mapped-installation proxy persistence (field test 2026-08-18: "search
|
||||
// Mapped-installation proxy persistence (owner playtest 2026-08-18: "search
|
||||
// nearby sites" was slow because every look around paid a live Overpass round
|
||||
// trip, and the 5-minute memory tier died with the dev server).
|
||||
//
|
||||
|
||||
@@ -183,7 +183,7 @@ function integerOption(key, token, defaultValue) {
|
||||
|
||||
const OPTION_GROUPS = Object.freeze({
|
||||
flights: Object.freeze([
|
||||
// Product invariant 2026-08-22: the fleet's 3D models are DEFAULT-ON in
|
||||
// Owner directive 2026-08-22: the fleet's 3D models are DEFAULT-ON in
|
||||
// PROXIMITY mode. Proximity is itself the altitude/count gate — models only
|
||||
// materialize once the camera is close enough and only for the nearest
|
||||
// contacts in view — so "on" costs nothing at globe scale, and an operator
|
||||
|
||||
@@ -332,7 +332,7 @@ test('compact URL omits absent-meaning option state and still resolves to it', (
|
||||
});
|
||||
|
||||
test('a fresh boot starts 3D aircraft ON in proximity — codec, both layers, and the rail agree', async () => {
|
||||
// Product invariant 2026-08-22: the DISPLAY-rail 3D toggle defaults ON with mode
|
||||
// Owner directive 2026-08-22: the DISPLAY-rail 3D toggle defaults ON with mode
|
||||
// `proximity`, because proximity is itself the budget — models materialize only
|
||||
// below the fleet altitude ceiling and only for the nearest MODEL_MAX in view,
|
||||
// so "on" costs nothing at globe scale and `all` stays a deliberate opt-in.
|
||||
|
||||
@@ -51,7 +51,7 @@ const DEFAULT_OVERLAY_HOST = Object.freeze({
|
||||
});
|
||||
|
||||
/**
|
||||
* Build the validated local-infrastructure card copy.
|
||||
* Build the owner-approved local-infrastructure card copy.
|
||||
* @param {object} properties Unwrapped GeoJSON feature properties.
|
||||
* @param {string} layerId Local layer id.
|
||||
* @returns {{title:string,details:string[]}}
|
||||
@@ -317,7 +317,7 @@ export function createLocalGeoJsonLayer({
|
||||
* camera is parked. One timer for the whole layer (not per record) — the
|
||||
* retry pass walks every record anyway. (perf rebase 2026-08-17)
|
||||
*
|
||||
* Two gates keep this from becoming an idle leak (second review):
|
||||
* Two gates keep this from becoming an idle leak (review round 2):
|
||||
* - CAPABILITY: without `scene.sampleHeightSupported` the sample can never
|
||||
* succeed, so a timer here would re-arm on every requested frame,
|
||||
* forever. Records simply stay at ellipsoid height — exactly the
|
||||
|
||||
@@ -149,7 +149,7 @@ async function createRealLocalLayerHarness({
|
||||
};
|
||||
}
|
||||
|
||||
test('local infrastructure card copy uses the validated source fields', () => {
|
||||
test('local infrastructure card copy uses the owner-approved source fields', () => {
|
||||
assert.deepEqual(localInfrastructureOverlayCopy({
|
||||
tags: {
|
||||
name: 'DFW-1',
|
||||
@@ -740,7 +740,7 @@ test('disable cancels a pending ground-retry render', async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
// ── The retry must be able to STOP (second review) ───────────────────────────
|
||||
// ── The retry must be able to STOP (review round 2) ───────────────────────────
|
||||
//
|
||||
// The retry above arms itself off its own requested frame, so anything that
|
||||
// makes the sample permanently impossible turns it into a perpetual-motion
|
||||
|
||||
@@ -29,7 +29,7 @@ const MAX_SAMPLES_PER_CALL = 40;
|
||||
/** @constant {number} Only cells within this range of the viewer subpoint are
|
||||
* sampled. Round-5 hardening: tightened 40 → 15 km — beyond the streamed
|
||||
* fine-LOD area a probe returns COARSE-tile heights (not undefined), and the
|
||||
* one-shot latch made those permanent (follow-up round 5: grounded planes stuck
|
||||
* one-shot latch made those permanent (owner round 5: grounded planes stuck
|
||||
* at a uniform wrong height). */
|
||||
const MAX_SAMPLE_DIST_KM = 15;
|
||||
/** @constant {number} No sampling when the camera is above this height — the
|
||||
@@ -155,8 +155,9 @@ export function sampleMeshFloorCells(scene, points, { excludeObjects = [], viewe
|
||||
// the sample point: walk the tileset tree to the tile containing the
|
||||
// coordinate and require its `geometricError` to be below a threshold tied
|
||||
// to the accuracy the caller needs, before trusting the sample. That is
|
||||
// feasible and deliberately deferred until rendered-mesh sampling can be
|
||||
// validated consistently across all consumers.
|
||||
// feasible and deliberately deferred — see the P2 in
|
||||
// the project roadmap, which bundles it with the fly_route probe so
|
||||
// one post-launch pass settles rendered-mesh sampling everywhere.
|
||||
reportValidatedMeshFloorCell(cell.lat, cell.lon, height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ const DEFERRED_DEPENDENCIES = ['ais-live-vessels', 'military-installations'];
|
||||
const DEPENDENCIES = [...AIRCRAFT_DEPENDENCIES, ...DEFERRED_DEPENDENCIES];
|
||||
const AWARENESS_REFRESH_MS = 750;
|
||||
/** @constant {number} Refresh cadence while the camera pose is CHANGING.
|
||||
* Field test 2026-08-18: the Contacts direction arrows and card readouts
|
||||
* Owner playtest 2026-08-18: the Contacts direction arrows and card readouts
|
||||
* "feel sluggish when you look around" — at the parked 750 ms cadence the
|
||||
* arrows lag the view by up to three quarters of a second. */
|
||||
const AWARENESS_MOTION_REFRESH_MS = 175;
|
||||
@@ -444,7 +444,7 @@ export function buildAwarenessContextSnapshot(results, navigation = {}, { subjec
|
||||
* They used to be separate. The panel read live billboard positions through
|
||||
* `getNearby` with a 20 000 cap; the analyst re-derived its own answer from
|
||||
* last-fix coordinates over a 2 000-record slice. Same question, same centre,
|
||||
* two numbers — and in the live trial the spoken answer (15) and the panel
|
||||
* two numbers — and in the owner's trial the spoken answer (15) and the panel
|
||||
* (111) disagreed badly enough that the model narrated the difference away.
|
||||
* Routing both through here makes them the same number BY CONSTRUCTION, so
|
||||
* they cannot drift again.
|
||||
|
||||
@@ -1080,7 +1080,7 @@ test('awareness clears are scoped to the selected source layer', () => {
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// BEGIN Contact-readout presence block.
|
||||
// BEGIN Contact-readout presence block — fix/context-panel-next-subjects.
|
||||
// Integrators: this whole delimited block belongs to the Contact-panel
|
||||
// CONTACT LOST work. Keep it intact and keep any concurrent branch's own
|
||||
// additions at the END of the file, so the two never collide.
|
||||
@@ -1483,7 +1483,7 @@ test('production eviction sites actually tag their clears', () => {
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// END Contact-readout presence block.
|
||||
// END Contact-readout presence block — fix/context-panel-next-subjects.
|
||||
// ===========================================================================
|
||||
|
||||
test('cockpit blocks only non-aircraft Context camera flights', () => {
|
||||
|
||||
+16
-16
@@ -95,7 +95,7 @@ const MIL_ICON_COLOR = Cesium.Color.fromCssColorString('#FFB800');
|
||||
/** @constant {Cesium.Color} Lighter amber tint applied to the actively tracked aircraft */
|
||||
const TRACKED_ICON_COLOR = Cesium.Color.fromCssColorString('#FFD166');
|
||||
|
||||
// --- Ground traffic (product change 2026-07-03; mirror of flights.js) ---------------
|
||||
// --- Ground traffic (owner reversal 2026-07-03; mirror of flights.js) ---------------
|
||||
// adsb.lol/readsb flags ground traffic with alt_baro === "ground" (no separate
|
||||
// boolean). Such aircraft are RENDERED instead of floating at the 3 km altitude
|
||||
// fallback: same silhouette + rotation pipeline, clickable/trackable/detectable,
|
||||
@@ -105,7 +105,7 @@ const TRACKED_ICON_COLOR = Cesium.Color.fromCssColorString('#FFD166');
|
||||
// decision 2026-07-03 — no air/ground distinction), placed by the one-shot
|
||||
// ground snap (see _modelDisplayPosition).
|
||||
//
|
||||
// TINT: full-strength amber, same as airborne (validated behavior 2026-07-03 field
|
||||
// TINT: full-strength amber, same as airborne (owner verdict 2026-07-03 field
|
||||
// test: the day-1 slate-gray 50%-alpha muted tint was unreadable — "in NYC I can
|
||||
// barely see them"). "On the ground" reads from the ×0.8 scale + missing trail;
|
||||
// "feed-dropped, coasting" stays the 45%-alpha stale fade.
|
||||
@@ -113,7 +113,7 @@ const TRACKED_ICON_COLOR = Cesium.Color.fromCssColorString('#FFD166');
|
||||
const GROUND_SCALE = 0.8;
|
||||
|
||||
/** Depth-test policy for aircraft billboards (mirror of flights.js — see the
|
||||
* full rationale there). Round 5 (product invariant 2026-07-06): EVERY contact
|
||||
* full rationale there). Round 5 (owner directive 2026-07-06): EVERY contact
|
||||
* renders depth-test-free at every distance — a uniform always-visible rule;
|
||||
* the fleet tick's horizon occluder still removes far-side contacts. */
|
||||
function _groundDepthDistance() {
|
||||
@@ -131,7 +131,7 @@ const JET_MODEL_URL = '/models/jet.glb';
|
||||
const MODEL_ALT_CEIL_M = 800000; // m: below this camera altitude, draw 3D models (raised so it's easy to trigger)
|
||||
const MODEL_MIN_PX = 24; // floor so distant models stay visible without ballooning into a min-pixel blob (mirror of flights.js, whose models now share this layer's ~30 m world size)
|
||||
const TRACKED_MODEL_MIN_PX = 40; // keep the glTF silhouette comparable to the selected 2D glyph at handoff
|
||||
export const TRACKED_MODEL_MAX_PX = 200; // selected close-range tracked-target feel
|
||||
export const TRACKED_MODEL_MAX_PX = 200; // owner-selected close-range tracked-target feel
|
||||
const MODEL_NATIVE_RADIUS_M = 29.83;
|
||||
// jet.glb is transform-applied at real-world scale — native bounding radius
|
||||
// 29.83 m at scale 1. ×1 → ~22–43 m aircraft across CLASS_SCALE_3D, matching
|
||||
@@ -164,14 +164,14 @@ const PLANE_MODEL_URL = '/models/airplane.glb';
|
||||
const PLANE_MODEL_SCALE = 1;
|
||||
const PLANE_NATIVE_RADIUS_M = 34.41;
|
||||
const PLANE_BELLY_OFFSET_NATIVE = 6.719;
|
||||
/** Per-class model spec for THIS layer (2026-08-16, field test ask:
|
||||
/** Per-class model spec for THIS layer (2026-08-16, owner playtest ask:
|
||||
* military contacts should read as their WEIGHT CLASS, always in this layer's
|
||||
* amber). Real Hangar GLBs serve the classes they cover (meters, nose −X →
|
||||
* 180° offset); airliner/quadjet/glider get the shared 747 silhouette
|
||||
* (airplane.glb — C-5M/RC-135-style heavies stop rendering as bizjets);
|
||||
* fastjet and unknown keep jet.glb with the same 180° offset. The MIX tint stays
|
||||
* dominant everywhere — military is amber, tracked is TRACKED_ICON_COLOR,
|
||||
* and the tint must dominate any livery. Specs are
|
||||
* and the tint must dominate any livery (owner: "stays yellow"). Specs are
|
||||
* static per class — memoized (the fleet pass asks at 12 Hz per model). */
|
||||
const _specCache = new Map();
|
||||
function _modelSpec(klass) {
|
||||
@@ -213,7 +213,7 @@ const _modelGen = new Map();
|
||||
/** Lifecycle epoch; bumped on destroy so an in-flight load from a PREVIOUS init can't settle
|
||||
* against a new lifecycle's globals (which destroy cleared). Captured by _ensureModel. */
|
||||
let _modelEpoch = 0;
|
||||
/** DEFAULT-ON in PROXIMITY (product invariant 2026-08-22). A fresh boot never runs
|
||||
/** DEFAULT-ON in PROXIMITY (owner directive 2026-08-22). A fresh boot never runs
|
||||
* layer-state restoration, so this initializer — not the codec — is what the app
|
||||
* actually starts with; it must stay in lockstep with the `models3d` default in
|
||||
* `layerState.js` and `this._models3dEnabled` in ui.js, or the DISPLAY rail would
|
||||
@@ -384,7 +384,7 @@ const TRACKED_BILLBOARD_SCALE_BY_DISTANCE = new Cesium.NearFarScalar(
|
||||
|
||||
function _normalBillboardScaleByDistance() {
|
||||
// Match commercial flights and preserve the established close-range 3×
|
||||
// default. Smaller user-visible scaling must be proposed separately.
|
||||
// default. Smaller owner-visible scaling must be proposed separately.
|
||||
return new Cesium.NearFarScalar(1000, 3.0, 8000000, 0.5);
|
||||
}
|
||||
|
||||
@@ -515,7 +515,7 @@ let _trailBackfillToken = 0;
|
||||
const RENDER_DELAY_SEC = 15;
|
||||
/** @constant {number} Polls an aircraft may miss before removal (transient adsb.lol dropouts). */
|
||||
const MISSING_POLL_LIMIT = 3;
|
||||
// --- Landed-plane fast cull (mirror of flights.js; field report
|
||||
// --- Landed-plane fast cull (mirror of flights.js; owner field report
|
||||
// 2026-07-02: "phantom" planes lingered ~2 min at airports after touchdown).
|
||||
// The feed's ground flag lags the actual landing, so a landed plane's last
|
||||
// airborne fixes show it low + slow on the runway; when it then drops out of
|
||||
@@ -1357,11 +1357,11 @@ function _noteTrackedModelLoadFailure(url, err) {
|
||||
|
||||
/**
|
||||
* The TRACKED aircraft's own model regime — DEFAULT-ON, camera-distance driven
|
||||
* (product invariant 2026-08-19). Mirror of flights.js: this does NOT consult the
|
||||
* (owner directive 2026-08-19). Mirror of flights.js: this does NOT consult the
|
||||
* DISPLAY-rail `models3d` toggle, which keeps owning the FLEET
|
||||
* (`_modelRegimeActive`) and its draw-call budget. Thresholds + hysteresis live
|
||||
* in trackedModelRegime.js — enter at TRACKED_MODEL_ENTER_ALT_M (150_000 m, the
|
||||
* playtested swap distance, deliberately NEARER than the fleet's 800 km
|
||||
* owner's playtested swap distance, deliberately NEARER than the fleet's 800 km
|
||||
* ceiling this used to inherit), hand back only above
|
||||
* TRACKED_MODEL_EXIT_ALT_M so a boundary orbit cannot flap billboard↔model.
|
||||
* First-person means your own airframe is not drawn in cockpit — the eye sits
|
||||
@@ -1839,7 +1839,7 @@ function _fleetTick() {
|
||||
// 3D-model eligibility: by DISTANCE (mode's add/keep band) with ON-SCREEN PRIORITY under the cap —
|
||||
// mirror of flights.js. FOUR visible-first passes: (1) KEEP on-screen modeled; (2) ADD on-screen
|
||||
// new in add radius; (3) KEEP off-screen modeled; (4) ADD off-screen new with leftover slots. KEEP
|
||||
// is split by frustum so an off-screen retained model can't starve an on-screen plane (review).
|
||||
// is split by frustum so an off-screen retained model can't starve an on-screen plane (review finding).
|
||||
let modelEligible = null;
|
||||
if (useModels) {
|
||||
const cap = _modelCap();
|
||||
@@ -1856,7 +1856,7 @@ function _fleetTick() {
|
||||
// it must not occupy a CAP SLOT either (mirror of flights.js) — excluded
|
||||
// at selection time, not just at the handoff below.
|
||||
if (isTr3b(icao)) continue;
|
||||
// Ground planes compete for model slots like everyone else (product rule
|
||||
// Ground planes compete for model slots like everyone else (owner decision
|
||||
// 2026-07-03, mirror of flights.js — no air/ground distinction; grounded
|
||||
// placement is handled by the one-shot ground snap in _modelDisplayPosition).
|
||||
const d2 = Cesium.Cartesian3.distanceSquared(camPos, bb.position);
|
||||
@@ -2235,7 +2235,7 @@ async function _backfillTrail(icao24, token, oldestFixEpochSec) {
|
||||
// 'ground'/null point sits ON the local surface — the old fixed 50 m
|
||||
// sentinel rendered ~1.5 km underground at Kirtland AFB (field ~1590 m
|
||||
// ellipsoidal) and dragged the whole pattern-work loop with it.
|
||||
// Round-2 fix: the
|
||||
// Round-2 fix (owner: "trails suddenly much shorter / not loading"): the
|
||||
// resolve is BOUNDED (≤1.2 s), not a blocking await — a cold Re:Earth
|
||||
// lookup across a long path could stall the paint for seconds-to-timeout.
|
||||
// Paint with whatever cells are warm; the resolve keeps filling the cache
|
||||
@@ -3193,7 +3193,7 @@ const militaryFlightsLayer = {
|
||||
// is about to delete (billboard restore, DR cache reset), and we must
|
||||
// never leave the camera mid-follow with stale tracking state. The
|
||||
// camera is then RELEASED IN PLACE — it stays where the follow left
|
||||
// it, fully free (product rule 2026-07-02: no overview flyTo).
|
||||
// it, fully free (owner decision 2026-07-02: no overview flyTo).
|
||||
if (icao24 === _trackedIcao) {
|
||||
_clearTracking(false, { evicted: true });
|
||||
}
|
||||
@@ -3549,7 +3549,7 @@ const militaryFlightsLayer = {
|
||||
// card, in the analyst's answer, and in the Contacts list. Matching only
|
||||
// callsigns meant "follow 6606" — and the analyst → track_entity handoff
|
||||
// the tool instructions prescribe — answered "nothing matched" for the
|
||||
// very identity the app had just shown (field session 2026-08-21,
|
||||
// very identity the app had just shown (owner field session 2026-08-21,
|
||||
// 23:48: three failed track_entity retries before a fallback stuck).
|
||||
// Ranking is shared with the flights layer (contactMatch.js) so the two
|
||||
// cannot disagree, and it is strictly tiered so a registration can never
|
||||
|
||||
@@ -449,7 +449,7 @@ test('real military track path creates no native label and publishes every cache
|
||||
* and then hand that identity to track_entity. The analyst's `id` is a DISPLAY
|
||||
* label — callsign, else registration, else hex — while the lookup matched
|
||||
* callsigns and hex only, so a callsign-less contact came back as its tail
|
||||
* number and "Nothing matched" (field session 2026-08-21, 23:48: three
|
||||
* number and "Nothing matched" (owner field session 2026-08-21, 23:48: three
|
||||
* failed retries before a fallback stuck).
|
||||
*/
|
||||
test('a callsign-less contact is findable by the tail number the app displays', () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ const CLASS_BY_MILITARY_TAG = {
|
||||
};
|
||||
|
||||
/**
|
||||
* How an UNNAMED feature reads on the map (field test 2026-08-18: the old
|
||||
* How an UNNAMED feature reads on the map (owner playtest 2026-08-18: the old
|
||||
* fallback surfaced "range (10981656305)" — an OSM primary key shown to a human
|
||||
* as if it were a place name).
|
||||
*
|
||||
|
||||
@@ -309,7 +309,7 @@ function renderRecords() {
|
||||
* `resolveGroundFloorCellsBounded` gives up after FLOOR_RESOLVE_DEADLINE_MS so
|
||||
* a cold DEM can never hold the dots hostage — but the resolve keeps running
|
||||
* and lands seconds later, and without this the records it covers stay pinned
|
||||
* at ellipsoid height 0, sitting visibly under the 3D tiles (field test
|
||||
* at ellipsoid height 0, sitting visibly under the 3D tiles (owner playtest
|
||||
* 2026-08-18: "orange dots at the bottom").
|
||||
*
|
||||
* This is the render -> warm -> re-render chain FIRMS already uses, with one
|
||||
|
||||
@@ -763,8 +763,8 @@ test('the trail head grows continuously across the envelope, never in one step',
|
||||
// heading then rotated into world space, putting the trail out to one side and
|
||||
// flipping which side as the course changed.
|
||||
//
|
||||
// A single-heading harness cannot catch this: at one heading a wrongly-framed
|
||||
// offset can coincidentally point
|
||||
// A single-heading harness cannot catch this, which is exactly how it reached
|
||||
// the owner: at one heading a wrongly-framed offset can coincidentally point
|
||||
// aft. So this sweeps headings and every shipped asset, and asserts the
|
||||
// property directly — the anchor lies in the model's longitudinal/vertical
|
||||
// plane, with NO lateral component.
|
||||
|
||||
@@ -88,7 +88,7 @@ export function courseBetweenCartesians(from, to, minChordM = 25) {
|
||||
* track only (chord weight 0). ≈30 kt. */
|
||||
export const COURSE_TRACK_ONLY_MPS = 15.4;
|
||||
/** At/above this displayed ground speed the course source is the chord only
|
||||
* (weight 1) — the field-validated regime; behavior is unchanged there.
|
||||
* (weight 1) — the regime the owner field-approved; behavior unchanged there.
|
||||
* ≈50 kt. */
|
||||
export const COURSE_CHORD_ONLY_MPS = 25.7;
|
||||
/** Below this displayed speed neither chord nor reported track means anything
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* neighborhood boundaries that OSM tags as label-nodes-only (Chinatown, the Marina, the
|
||||
* Mission, …). It sits AHEAD of the live-Overpass / synthesis path in the resolver, so
|
||||
* covered neighborhoods resolve instantly to a REAL boundary with no network dependency
|
||||
* because the live Overpass path is slow and inconsistent for neighborhoods.
|
||||
* (the live-Overpass path is slow/flaky for neighborhoods — see docs/field-test-2-analysis.md).
|
||||
*
|
||||
* Source-agnostic: each city is a `{name, geometry}` GeoJSON file in
|
||||
* `local_data/neighborhoods/`. Swap the file (e.g. to public-domain DataSF) without
|
||||
|
||||
@@ -4,6 +4,7 @@ import createViteConfig, {
|
||||
adsbLolFallbackAnchor,
|
||||
coalesceProxyRequest,
|
||||
launchLibraryRequestHeaders,
|
||||
keylessGooglePlacesResponse,
|
||||
LL2_CACHE_TTL_MS,
|
||||
readResponseJsonCapped,
|
||||
regionalBriefHasAnySource,
|
||||
@@ -11,6 +12,18 @@ import createViteConfig, {
|
||||
validRegionalPoint,
|
||||
} from '../../vite.config.js';
|
||||
|
||||
test('missing Google place context is a quiet keyless capability, not a 503', () => {
|
||||
assert.deepEqual(keylessGooglePlacesResponse(undefined), {
|
||||
statusCode: 200,
|
||||
payload: { configured: false, error: null, places: [] },
|
||||
});
|
||||
assert.deepEqual(keylessGooglePlacesResponse(' '), {
|
||||
statusCode: 200,
|
||||
payload: { configured: false, error: null, places: [] },
|
||||
});
|
||||
assert.equal(keylessGooglePlacesResponse('configured-key'), null);
|
||||
});
|
||||
|
||||
test('regional proxy rejects absent and blank coordinates instead of coercing them to zero', () => {
|
||||
assert.equal(validRegionalPoint(new URLSearchParams('longitude=12.5')), null);
|
||||
assert.equal(validRegionalPoint(new URLSearchParams('latitude=12.5')), null);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/data/renderAltitude.js — pure priority-chain helper for aircraft render
|
||||
// altitude.
|
||||
// altitude (docs/plans/2026-07-05-entity-height-datum-fix.md Task 6).
|
||||
//
|
||||
// The globe needs ELLIPSOIDAL height (h = H + N). OpenSky's `geo_altitude`
|
||||
// (state-vector index 13) is already WGS84 geometric/ellipsoidal — the
|
||||
@@ -78,7 +78,7 @@ export function reuseGroundedSurfaceM(currentM, previousM) {
|
||||
* contact's cells went cold, and this guess overwrote a height that had been
|
||||
* sitting correctly on the mesh — dropping it through the ground until the
|
||||
* proxy recovered. With `priorRenderM` present the caller's existing sticky
|
||||
* fallback holds that height instead, which is what product behavior requires for:
|
||||
* fallback holds that height instead, which is what the owner asked for:
|
||||
* "hold the last known altitude until a fresh one comes in."
|
||||
*
|
||||
* @param {object} params
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// src/data/renderAltitude.test.mjs
|
||||
// docs/plans/2026-07-05-entity-height-datum-fix.md Task 6.
|
||||
//
|
||||
// Locks pickRenderAltitudeM's priority chain — the SINGLE source of truth for
|
||||
// where a flights-layer aircraft (and its dead-reckoned/tracked-camera
|
||||
@@ -182,7 +183,7 @@ test('taxiing aircraft over nonzero terrain resolves every poll after the first
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// geoidSurfaceLastResortM — the geoid guess must never outrank what the
|
||||
// contact already knows (field incident 2026-08-21: a Re:Earth outage plus
|
||||
// contact already knows (owner incident 2026-08-21: a Re:Earth outage plus
|
||||
// this guess dropped a parked contact through the mesh at a Texas field).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ test('selected satellite params survive delayed arrival and yield to newer expli
|
||||
|
||||
test('a tracked docked cluster consolidates its companions onto one card', () => {
|
||||
// ISS and everything berthed to it are separate real tracks at one position,
|
||||
// so their ambient labels stack underneath the tracked card. Product decision:
|
||||
// so their ambient labels stack underneath the tracked card. Owner ruling:
|
||||
// consolidate them as secondary info on that card, and suppress only those
|
||||
// members — never unrelated satellites that merely happen to be nearby.
|
||||
const entity = { gevLabelModel: { title: 'OLD', details: ['? km'] } };
|
||||
|
||||
@@ -30,7 +30,7 @@ test('the guard is an Earth-sized band, not just a non-zero check', () => {
|
||||
// Earth actually occupies, because the values BETWEEN "zero" and "the globe"
|
||||
// are the ones that fail quietly.
|
||||
const belowTheFloor = [
|
||||
// the regression probe: finite, non-zero, and 6,378 km underground. A bare
|
||||
// The adversarial probe: finite, non-zero, and 6,378 km underground. A bare
|
||||
// non-zero check accepts it, and the shipped path then reverse-geocodes
|
||||
// 0°, 0° as if the operator were looking at the Gulf of Guinea.
|
||||
new Cesium.Cartesian3(500, 0, 0),
|
||||
|
||||
@@ -54,8 +54,10 @@ export const CABLE_SWEEP_MOTION_EPSILON_M = 250;
|
||||
* layer alone cost ~9.5 ms/frame during camera motion. The depth cue this
|
||||
* trades away (photoreal tiles occluding label TEXT at low, city-level
|
||||
* cameras) matches the sibling dams/datacenters sources, which shipped
|
||||
* host-composited under the same rule; the anchor points/stems remain
|
||||
* Cesium-native and depth-tested.
|
||||
* host-composited under the same ruling; the anchor points/stems remain
|
||||
* Cesium-native and depth-tested. See
|
||||
* the world-overlay consolidation design notes ("Depth-testing
|
||||
* decision") for both dated decisions.
|
||||
*/
|
||||
export const CABLE_LABEL_DEPTH_DECISION = Object.freeze({
|
||||
option: 2,
|
||||
@@ -207,7 +209,7 @@ export function createCableOverlayPublisher({
|
||||
* real `MAP_STACKS` so the omission is caught loudly.
|
||||
*/
|
||||
const CABLE_GLOBE_STACK_IDS = Object.freeze(
|
||||
new Set(['bing-aerial', 'bing-labels', 'osm']),
|
||||
new Set(['bing-aerial', 'bing-labels', 'esri-imagery', 'osm']),
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -151,7 +151,7 @@ test('cable ground lines classify against exactly the active surface on every st
|
||||
Cesium.ClassificationType.CESIUM_3D_TILE,
|
||||
);
|
||||
// Every globe stack renders imagery on the shown globe — terrain pass only.
|
||||
for (const stackId of ['bing-aerial', 'bing-labels', 'osm']) {
|
||||
for (const stackId of ['bing-aerial', 'bing-labels', 'esri-imagery', 'osm']) {
|
||||
assert.equal(
|
||||
cableClassificationTypeForStack(stackId),
|
||||
Cesium.ClassificationType.TERRAIN,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// src/data/terrainHeights.js — batched, cached client terrain-height resolver.
|
||||
// src/data/terrainHeights.js — batched, cached client terrain-height resolver
|
||||
// (docs/plans/2026-07-05-entity-height-datum-fix.md Task 3).
|
||||
//
|
||||
// Resolves ELLIPSOIDAL ground height per (lat, lon) via the server-side
|
||||
// `/api/terrain/heights` proxy (Task 2 — Re:Earth `heights.json`, disk-cached,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/data/terrainHeights.test.mjs — batched, cached client terrain-height
|
||||
// resolver.
|
||||
// resolver (docs/plans/2026-07-05-entity-height-datum-fix.md Task 3).
|
||||
//
|
||||
// Locks the module's public interface (Task 5/6 call it verbatim):
|
||||
// resolveEllipsoidalGround(coords: [{lat, lon, sourceOrthometricM?}])
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from './terrainHeights.js';
|
||||
|
||||
const AUSTIN = { lat: 30.2672, lon: -97.7431 };
|
||||
const AUSTIN_GEOID_N = -26.9; // Cross-checked against Re:Earth reference data.
|
||||
const AUSTIN_GEOID_N = -26.9; // docs/plans/2026-07-05-entity-height-datum-fix.md verified facts
|
||||
|
||||
/** Installs a fake fetch for the duration of `fn`, restoring the original after. */
|
||||
async function withFakeFetch(fakeFetch, fn) {
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
*
|
||||
* 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` (product invariant), so the
|
||||
* 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 (product invariant, 2026-08-19).
|
||||
* 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. Field test 2026-08-20 rejected that: the
|
||||
* 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
|
||||
@@ -49,7 +49,7 @@
|
||||
export const FLEET_MODEL_ALT_CEIL_M = 800_000;
|
||||
|
||||
/** Camera altitude (m) at or below which the TRACKED contact takes its 3D model.
|
||||
* Field test ruling 2026-08-20: 2D still reads correctly at ~600_000 m, and
|
||||
* 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;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// src/data/trackedModelRegime.test.mjs
|
||||
//
|
||||
// Zoom-driven 2D↔3D for the TRACKED contact (product invariant 2026-08-19).
|
||||
// Zoom-driven 2D↔3D for the TRACKED contact (owner directive 2026-08-19).
|
||||
//
|
||||
// Two things are pinned here, and both are behavioural rather than cosmetic:
|
||||
//
|
||||
// 1. The POLICY — thresholds and hysteresis — as pure math in
|
||||
// trackedModelRegime.js. The enter ceiling is the playtested swap
|
||||
// trackedModelRegime.js. The enter ceiling is the owner's playtested swap
|
||||
// distance, deliberately much NEARER than the fleet ceiling, and the exit
|
||||
// ceiling is deliberately higher than the enter ceiling. A regression that
|
||||
// collapsed the two thresholds back into one would silently reintroduce
|
||||
@@ -62,8 +62,8 @@ const WELL_INSIDE_M = TRACKED_MODEL_ENTER_ALT_M / 2;
|
||||
// 1. Policy: thresholds + hysteresis math
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('the tracked 3D takeover sits at the playtested swap distance', () => {
|
||||
// Field test 2026-08-20: an earlier 1_000_000 m ceiling "pops to 3D far
|
||||
test('the tracked 3D takeover sits at the owner-playtested swap distance', () => {
|
||||
// Owner playtest 2026-08-20: an earlier 1_000_000 m ceiling "pops to 3D far
|
||||
// too early" — 2D still reads correctly at ~600_000 m and the swap belongs at
|
||||
// ~150_000 m. These are the numbers the operator judged by eye, so they are
|
||||
// pinned literally rather than derived from anything.
|
||||
@@ -75,7 +75,7 @@ test('the tracked 3D takeover sits at the playtested swap distance', () => {
|
||||
});
|
||||
|
||||
test('the tracked contact swaps NEARER than the fleet — a recorded inversion, not a bug', () => {
|
||||
// Consequence of the selected threshold, spelled out so it cannot be "tidied
|
||||
// Consequence of the owner's number, spelled out so it cannot be "tidied
|
||||
// away": with the DISPLAY-rail 3D toggle ON, camera altitudes between the
|
||||
// tracked ceiling and the fleet ceiling draw surrounding contacts as models
|
||||
// while the SELECTED one is still a billboard. The fleet pass skips the
|
||||
|
||||
@@ -124,7 +124,7 @@ test('civilian and military click handlers apply duration only at the deselect b
|
||||
);
|
||||
});
|
||||
|
||||
test('civilian and military tracked model caps both expose the selected 200 px feel', () => {
|
||||
test('civilian and military tracked model caps both expose the owner-selected 200 px feel', () => {
|
||||
assert.equal(CIVIL_TRACKED_MODEL_MAX_PX, 200);
|
||||
assert.equal(MILITARY_TRACKED_MODEL_MAX_PX, 200);
|
||||
});
|
||||
+7
-7
@@ -223,7 +223,7 @@ let _uncoveredMode = 'sim';
|
||||
* + stop-and-go creep; 'heatline' = congestion corridor polylines; 'both';
|
||||
* 'none' = shipped main behavior. Live mode only — the keyless simulation
|
||||
* never has `road.flow`, so every jamViz path is unreachable there.
|
||||
* Default 'density' — A/B verdict 2026-07-23 (heatline stays available
|
||||
* Default 'density' — owner A/B verdict 2026-07-23 (heatline stays available
|
||||
* via setParams).
|
||||
* @type {'none'|'density'|'heatline'|'both'}
|
||||
*/
|
||||
@@ -270,7 +270,7 @@ let _activeBucketColors = { ...FLOW_BUCKET_COLORS };
|
||||
/**
|
||||
* @const {number} Minimum base pixel size for COLORED dots while a styled
|
||||
* preset is active — residential-road dots spawn at 4 px and vanish into
|
||||
* post-FX pixelation; presence is the dots' whole job there (follow-up round
|
||||
* post-FX pixelation; presence is the dots' whole job there (owner round
|
||||
* 2). Sim dots and the normal profile keep SIZE_BY_TYPE untouched.
|
||||
*/
|
||||
const STYLED_MIN_BASE_PX = 5;
|
||||
@@ -1253,7 +1253,7 @@ export function trafficFeedPresentation({
|
||||
const mode = liveMode ? 'live' : 'sim';
|
||||
if (liveMode && flowError) {
|
||||
// One string for both fields. The manager's meta line renders `error` and
|
||||
// drops `loadingLabel` in its error branch, so the SIMULATED copy
|
||||
// drops `loadingLabel` in its error branch, so the owner's SIMULATED copy
|
||||
// has to BE the error text or the steady state reverts to a bare
|
||||
// "TomTom daily budget reached" that never says what is on screen.
|
||||
const degraded = `SIMULATED — ${flowError}`;
|
||||
@@ -1269,7 +1269,7 @@ export function trafficFeedPresentation({
|
||||
};
|
||||
}
|
||||
// Keyless simulation — one terse line that names the mode and the remedy
|
||||
// (the copy shape). The chip's own progress text carries "working";
|
||||
// (owner's copy shape). The chip's own progress text carries "working";
|
||||
// this line must never imply a live feed.
|
||||
return {
|
||||
mode,
|
||||
@@ -2351,13 +2351,13 @@ const trafficLayer = {
|
||||
if (params.uncoveredRoads === 'sim' || params.uncoveredRoads === 'hide') {
|
||||
_uncoveredMode = params.uncoveredRoads;
|
||||
}
|
||||
// Jam-viz prototype toggle (A/B): 'none' = shipped main behavior;
|
||||
// Jam-viz prototype toggle (owner A/B): 'none' = shipped main behavior;
|
||||
// live mode only, applies on the next camera-driven load like the
|
||||
// uncoveredRoads param above.
|
||||
if (['none', 'density', 'heatline', 'both'].includes(params.jamViz)) {
|
||||
_jamViz = params.jamViz;
|
||||
}
|
||||
// Preset-aware dot styling kill switch (A/B): 'off' forces the
|
||||
// Preset-aware dot styling kill switch (owner A/B): 'off' forces the
|
||||
// shipped palette under every post-FX preset. Applies immediately via
|
||||
// in-place restyle — no refetch — so A/B legs share identical dots.
|
||||
if (params.presetDots === 'on' || params.presetDots === 'off') {
|
||||
@@ -2415,7 +2415,7 @@ const trafficLayer = {
|
||||
};
|
||||
// Live mode: the detection bracket carries the congestion signal —
|
||||
// its canvas sits ABOVE the post-FX chain, so tier colors survive
|
||||
// every preset (follow-up round 2: "bounding boxes do the heavy
|
||||
// every preset (owner round 2: "bounding boxes do the heavy
|
||||
// lifting"). Keyless mode sets no tier: contacts keep the stock
|
||||
// 'vehicle' bracket and the keyless experience stays untouched.
|
||||
if (_liveMode) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* 0.72 / jam 0.49 — under NVG a jam renders DIMMER than free flow. CRT
|
||||
* keeps hue but its 5-px pixelation + dithering shred 4–6 px dots.
|
||||
*
|
||||
* Encoding per profile (validated behavior 2026-07-23 round 2: "just bright
|
||||
* Encoding per profile (owner verdict 2026-07-23 round 2: "just bright
|
||||
* dots" — a luminance RAMP failed in the field because dim free-flow dots
|
||||
* read as dark holes on NVG-bright roads; classification is the detection
|
||||
* brackets' job via `trafficBucketTier`, presence is the dots' job):
|
||||
|
||||
@@ -39,7 +39,7 @@ test('sim/uncovered dots (null bucket) are untouched under EVERY style — keyle
|
||||
}
|
||||
});
|
||||
|
||||
test('mono: EVERY colored dot is a bright white core (follow-up round 2: "just bright dots")', () => {
|
||||
test('mono: EVERY colored dot is a bright white core (owner round 2: "just bright dots")', () => {
|
||||
for (const s of MONO_STYLES) {
|
||||
for (const b of BUCKETS) {
|
||||
const rgba = presetDotRgba(s, b);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 2026-07-06).
|
||||
*
|
||||
* One trail = one ENTITY polyline. Round 6 replaced the faded per-vertex
|
||||
* Primitive for two product invariants from the field:
|
||||
* Primitive for two owner directives from the field:
|
||||
* - "the line must ALWAYS be visible": the Primitive's depthFailAppearance
|
||||
* did not reliably render segments below the photoreal mesh — entity
|
||||
* polylines with `depthFailMaterial` DO (in-repo proof: CCTV's frustum
|
||||
@@ -36,7 +36,7 @@ let _trailSeq = 0;
|
||||
/** @constant {number} Alpha where the trail passes the depth test. */
|
||||
const TRAIL_ALPHA = 0.85;
|
||||
/** @constant {number} Alpha where the trail is behind/below scene geometry —
|
||||
* still visible, but readable as occluded. */
|
||||
* visible (owner: never vanish) but readable as occluded. */
|
||||
const TRAIL_OCCLUDED_ALPHA = 0.4;
|
||||
/** @constant {number} Squared distance (m^2) below which consecutive points are merged. */
|
||||
const MIN_SEGMENT_DISTANCE_SQ = 0.01;
|
||||
@@ -72,10 +72,11 @@ export function createTrail(viewer, { color, width = 2.5 }) {
|
||||
positions: new Cesium.CallbackProperty(() => current, false),
|
||||
width,
|
||||
material: baseColor.withAlpha(TRAIL_ALPHA),
|
||||
// The locked rule (round 6): a segment below the photoreal
|
||||
// The owner-locked rule (round 6): a segment below the photoreal
|
||||
// mesh renders dimmed — it must never disappear into the ground.
|
||||
depthFailMaterial: baseColor.withAlpha(TRAIL_OCCLUDED_ALPHA),
|
||||
// Round 8: NONE draws straight 3D chords between waypoints — over a
|
||||
// Round 8 (owner: UAL1104's Pacific trail "cutting through the
|
||||
// globe"): NONE draws straight 3D chords between waypoints — over a
|
||||
// sparse trans-oceanic trace a single segment spans hundreds of km
|
||||
// and tunnels through the planet. GEODESIC subdivides each segment
|
||||
// along the curved surface (heights interpolated), so long legs hug
|
||||
|
||||
Reference in new issue
Block a user