Read flight and military billboard state from Core's mirror

Flights and military now add their billboards through addBillboard and
write show, position, scale, rotation, size and distance scaling through
setBillboard, which keeps Core's copy in src/data/billboardMirror.js.
Their reads go to billboardState instead of the Cesium billboard, so the
layers no longer read back what they pushed to the renderer.

A mirrored billboard needs an explicit width and height, because unset
Cesium reports the loaded image's size. Colour is not mirrored yet: the
cyber-sonar theme writes it too and tells the writers apart by the live
value, so it moves with the effects port.
This commit is contained in:
Sameh Khamis committed 2026-11-03 10:56:08 -08:00
1 parent a40e5c334c
commit 1cfff5803a
23 files changed
+424 -172

No files matched your search

+4
View File
@@ -1315,6 +1315,7 @@
"src/layers/flights/policy.js",
"src/layers/flights/queries.js",
"src/layers/flights/rendering.js",
"src/data/billboardMirror.js",
"src/layers/aircraft/visualState.js",
"src/layers/flights/state.js",
"src/layers/flights/testing.js",
@@ -1375,6 +1376,7 @@
"src/layers/military/policy.js",
"src/layers/military/queries.js",
"src/layers/military/rendering.js",
"src/data/billboardMirror.js",
"src/layers/aircraft/visualState.js",
"src/layers/military/state.js",
"src/layers/military/testing.js",
@@ -2320,6 +2322,7 @@
"src/data/aircraftMeta.js",
"src/data/aircraftNearbyPolicy.js",
"src/data/aircraftRecession.js",
"src/data/billboardMirror.js",
"src/data/aisLiveVessels.js",
"src/data/alprCameras.js",
"src/data/analystEngine.js",
@@ -3284,6 +3287,7 @@
"src/data/aircraftMeta.js",
"src/data/aircraftNearbyPolicy.js",
"src/data/aircraftRecession.js",
"src/data/billboardMirror.js",
"src/data/cctvCards.js",
"src/data/cctvFootprint.js",
"src/data/cctvGizmo.js",
+5 -3
View File
@@ -5,6 +5,7 @@
* with the locked NearFarScalar. It never edits that scalar, culls a contact,
* or allows alpha to reach zero.
*/
import { billboardState, setBillboard } from './billboardMirror.js';
export const DEFAULT_AIRCRAFT_RECESSION_PARAMS = Object.freeze({
startLimbRatio: 0.5,
@@ -220,11 +221,12 @@ export function applyAircraftBillboardTreatment({
const alpha = baseAlpha * composedTreatment;
let scaleWrites = 0;
let alphaWrites = 0;
const pushedScale = billboardState(billboard)?.scale;
if (
!Number.isFinite(billboard.scale) ||
Math.abs(billboard.scale - scale) > tuning.writeEpsilon
!Number.isFinite(pushedScale) ||
Math.abs(pushedScale - scale) > tuning.writeEpsilon
) {
billboard.scale = scale;
setBillboard(billboard, 'scale', scale);
scaleWrites = 1;
}
if (
+5 -4
View File
@@ -7,6 +7,7 @@ import {
applyAircraftModelTreatment,
cameraLimbDistanceM,
} from './aircraftRecession.js';
import { mirrorBillboard } from './billboardMirror.js';
const params = { ...DEFAULT_AIRCRAFT_RECESSION_PARAMS };
const cameraHeightM = 1_000_000;
@@ -68,7 +69,7 @@ test('globe-view transition eases to identity without a threshold pop', () => {
test('aircraft billboard wire writes scale and alpha only for the far treatment', () => {
const color = { withAlpha: (alpha) => ({ alpha }) };
const near = { scale: 1.2, color: { alpha: 0.8 } };
const near = mirrorBillboard({ scale: 1.2, color: { alpha: 0.8 } });
const nearResult = applyAircraftBillboardTreatment({
billboard: near,
baseScale: 1.2,
@@ -82,7 +83,7 @@ test('aircraft billboard wire writes scale and alpha only for the far treatment'
assert.equal(nearResult.scaleWrites, 0);
assert.equal(nearResult.alphaWrites, 0);
const far = { scale: 1.2, color: { alpha: 0.8 } };
const far = mirrorBillboard({ scale: 1.2, color: { alpha: 0.8 } });
const farResult = applyAircraftBillboardTreatment({
billboard: far,
baseScale: 1.2,
@@ -101,7 +102,7 @@ test('aircraft billboard wire writes scale and alpha only for the far treatment'
test('combined focus and haze product is clamped at the composed alpha floor', () => {
const color = { withAlpha: (alpha) => ({ alpha }) };
const billboard = { scale: 0.45, color: { alpha: 1 } };
const billboard = mirrorBillboard({ scale: 0.45, color: { alpha: 1 } });
const result = applyAircraftBillboardTreatment({
billboard,
baseScale: 1,
@@ -118,7 +119,7 @@ test('combined focus and haze product is clamped at the composed alpha floor', (
test('applyAircraftBillboardTreatment returns its documented module singleton', () => {
const color = { withAlpha: (alpha) => ({ alpha }) };
const billboard = { scale: 1, color: { alpha: 1 } };
const billboard = mirrorBillboard({ scale: 1, color: { alpha: 1 } });
const input = {
billboard,
baseAlpha: 1,
+106
View File
@@ -0,0 +1,106 @@
/**
* @module billboardMirror
* @description Core's copy of what a layer pushed to its billboards, so the layer
* reads its own state back from Core instead of from the renderer's objects.
*
* A layer adds billboards with {@link addBillboard}, writes the mirrored fields
* with {@link setBillboard}, and reads them with {@link billboardState}. Fields
* outside the mirror (image, colour, eye offset, …) are written to the billboard
* directly. Colour stays out because the cyber-sonar theme writes it too and
* tells the two writers apart by reading the live value.
*/
import * as Cesium from 'cesium';
import { createEntityStore } from './entityRecords.js';
const mirrors = createEntityStore();
/**
* Mirrored fields with Cesium's defaults for a billboard added without them.
* Width and height have none: unset, Cesium reports the loaded image's size,
* which Core cannot know, so {@link addBillboard} requires them.
*/
const DEFAULTS = Object.freeze({
show: true,
scale: 1,
rotation: 0,
width: undefined,
height: undefined,
scaleByDistance: undefined,
});
/** Cesium copies positions and scalers on write; the mirror keeps its own copy too. */
function copyInto(record, field, value) {
if (field === 'position') {
// Cesium keeps its old position when handed none; so does the mirror.
if (!value && record.position) return;
record.position = Cesium.Cartesian3.clone(
value ?? Cesium.Cartesian3.ZERO,
record.position,
);
} else if (field === 'scaleByDistance') {
record.scaleByDistance = value
? Cesium.NearFarScalar.clone(value, record.scaleByDistance)
: undefined;
} else {
record[field] = value;
}
}
function seed(billboard, source) {
const record = mirrors.write(billboard);
copyInto(record, 'position', source.position);
for (const [field, fallback] of Object.entries(DEFAULTS))
copyInto(record, field, source[field] ?? fallback);
return record;
}
/**
* Add a billboard and remember what was pushed.
* @param {Cesium.BillboardCollection} collection
* @param {Object} options Billboard options, as for `collection.add`, with an
* explicit `width` and `height`.
* @returns {Cesium.Billboard}
*/
export function addBillboard(collection, options) {
if (!Number.isFinite(options?.width) || !Number.isFinite(options?.height))
throw new TypeError(
'A mirrored billboard needs an explicit width and height',
);
const billboard = collection.add(options);
seed(billboard, options);
return billboard;
}
/**
* Start mirroring a billboard Core did not add itself (test seams), from its
* current fields.
* @param {Object} billboard
* @returns {Object} The billboard.
*/
export function mirrorBillboard(billboard) {
if (billboard && typeof billboard === 'object') seed(billboard, billboard);
return billboard;
}
/**
* Write one mirrored field to the billboard and to Core's copy.
* @param {Object} billboard
* @param {'show'|'position'|'scale'|'rotation'|'width'|'height'|'scaleByDistance'} field
* @param {*} value
*/
export function setBillboard(billboard, field, value) {
copyInto(mirrors.write(billboard), field, value);
billboard[field] = value;
}
/**
* Core's copy of a billboard's mirrored fields, or null when it has none (or
* there is no billboard).
* @param {Object|null|undefined} billboard
* @returns {{ show: boolean, position: Cesium.Cartesian3, scale: number,
* rotation: number, width?: number, height?: number,
* scaleByDistance?: Cesium.NearFarScalar }|null}
*/
export function billboardState(billboard) {
return mirrors.read(billboard);
}
+74
View File
@@ -0,0 +1,74 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import * as Cesium from 'cesium';
import {
addBillboard,
billboardState,
mirrorBillboard,
setBillboard,
} from './billboardMirror.js';
const SIZE = Object.freeze({ width: 20, height: 20 });
function fakeCollection() {
return { add: (options) => ({ ...options }) };
}
test('an added billboard is mirrored with Cesium defaults for unset fields', () => {
const position = Cesium.Cartesian3.fromDegrees(10, 20, 30);
const bb = addBillboard(fakeCollection(), { position, width: 20, height: 24 });
const state = billboardState(bb);
assert.equal(state.show, true);
assert.equal(state.scale, 1);
assert.equal(state.rotation, 0);
assert.equal(state.width, 20);
assert.equal(state.height, 24);
assert.ok(Cesium.Cartesian3.equals(state.position, position));
assert.notEqual(state.position, position, 'the mirror keeps its own copy');
});
test('writes reach both the billboard and the mirror', () => {
const bb = addBillboard(fakeCollection(), SIZE);
setBillboard(bb, 'show', false);
setBillboard(bb, 'scale', 0.5);
assert.equal(bb.show, false);
assert.equal(billboardState(bb).show, false);
assert.equal(billboardState(bb).scale, 0.5);
});
test('a reused scratch position does not move the mirror', () => {
const bb = addBillboard(fakeCollection(), SIZE);
const scratch = Cesium.Cartesian3.fromDegrees(1, 2, 3);
setBillboard(bb, 'position', scratch);
const written = Cesium.Cartesian3.clone(scratch);
Cesium.Cartesian3.fromDegrees(50, 50, 50, undefined, scratch);
assert.ok(Cesium.Cartesian3.equals(billboardState(bb).position, written));
});
test('writing no position keeps the last one, as Cesium does', () => {
const position = Cesium.Cartesian3.fromDegrees(1, 2, 3);
const bb = addBillboard(fakeCollection(), { ...SIZE, position });
setBillboard(bb, 'position', undefined);
assert.ok(Cesium.Cartesian3.equals(billboardState(bb).position, position));
});
test('the mirror answers from Core, not from the billboard', () => {
const bb = addBillboard(fakeCollection(), { ...SIZE, show: true });
bb.show = false; // a write that bypassed the mirror
assert.equal(billboardState(bb).show, true);
});
test('a billboard Core did not add can be mirrored from its fields', () => {
const bb = mirrorBillboard({ show: false, scale: 2 });
assert.equal(billboardState(bb).show, false);
assert.equal(billboardState(bb).scale, 2);
assert.ok(Cesium.Cartesian3.equals(billboardState(bb).position, Cesium.Cartesian3.ZERO));
assert.equal(billboardState(null), null);
assert.equal(mirrorBillboard(null), null);
});
test('a billboard without explicit dimensions is refused', () => {
// Unset, Cesium reports the image's size, which the mirror cannot know.
assert.throws(() => addBillboard(fakeCollection(), {}), TypeError);
assert.throws(() => addBillboard(fakeCollection(), { width: 20 }), TypeError);
});
+2 -2
View File
@@ -90,11 +90,11 @@ for (const layer of LAYERS) {
// operator explicitly converted into a TR-3B.
assert.match(source, /bb\.image\s*=\s*aircraftIcon\(\s*_iconKind\(\s*icao24,\s*meta\?\.klass,?\s*\)(,\s*aircraftVisuals\.read\(bb\)\?\.iconLarge\s*\?\s*TRACKED_ICON_PX\s*:\s*undefined)?,?\s*\)/,
'near contacts and model fallbacks retain the class-derived aircraft silhouette');
assert.match(source, /bb\.rotation\s*=\s*0;/,
assert.match(source, /setBillboard\(\s*bb,\s*'rotation',\s*0\s*\);/,
'far dots are reset to a rotation-free presentation');
assert.match(source, /\(\s*!(?:flightState\.)?_cockpitContactMode\s*\|\|\s*isCockpitNear,?\s*\)\s*&&\s*\(\s*doRotations\s*\|\|\s*revealed,?\s*\)/,
'near 2D silhouettes continue to receive projected course');
assert.match(source, /if\s*\(\s*bb\.show,?\s*\)\s*bb\.show\s*=\s*false;\s*\/\/\s*hand\s*off\s*ONLY\s*once\s*the\s*model\s*renders/,
assert.match(source, /if\s*\(\s*billboardState\(bb\)\.show,?\s*\)\s*setBillboard\(\s*bb,\s*'show',\s*false\s*\);\s*\/\/\s*hand\s*off\s*ONLY\s*once\s*the\s*model\s*renders/,
'the gap-proof billboard-to-model handoff remains intact');
});
+4 -1
View File
@@ -3,6 +3,7 @@ import test from 'node:test';
import * as Cesium from 'cesium';
import { routePlausible } from './routePlausible.js';
import { createTracking } from '../layers/flights/tracking.js';
import { addBillboard } from './billboardMirror.js';
const SFO = { lat: 37.6188, lon: -122.3754, code: 'SFO' };
const LAX = { lat: 33.9416, lon: -118.4085, code: 'LAX' };
@@ -156,8 +157,10 @@ test('the actual native tracked label hides the wrong leg and keeps a healthy ro
});
flightState._billboards.set(
id,
collection.add({
addBillboard(collection, {
id,
width: 20,
height: 20,
position: Cesium.Cartesian3.fromDegrees(
position.lon,
position.lat,
+1 -1
View File
@@ -245,7 +245,7 @@ test('both flight layers keep a converted contact 2D and visible (render invaria
// 2. The billboard is never hidden by that suppression — it must keep
// satisfying the getNearby/getDetectableObjects visibility guards, so a
// converted contact still works in Contacts and Cockpit.
assert.match(source, /if\s*\(\s*bb\s*&&\s*id\s*!==\s*(?:flightState\.)?_trackedIcao,?\s*\)\s*bb\.show\s*=\s*true;|if\s*\(\s*modelled\s*&&\s*id\s*!==\s*(?:flightState\.)?_trackedIcao,?\s*\)\s*modelled\.show\s*=\s*true;/,
assert.match(source, /if\s*\(\s*(bb|modelled)\s*&&\s*id\s*!==\s*(?:flightState\.)?_trackedIcao,?\s*\)\s*setBillboard\(\s*\1,\s*'show',\s*true,?\s*\);/,
`${name}: converting restores the billboard the model handoff had hidden`);
// 3. Every aircraftIcon() CALL SITE routes through the kind resolver, so no
+7 -6
View File
@@ -45,6 +45,7 @@ import militaryFlightsLayer, {
} from './militaryFlights.js';
import { clearTr3bRegistry, setTr3b } from './tr3bRegistry.js';
import { reportMeshFloorCell, _clearMeshFloorCellsForTest } from './groundFloor.js';
import { setBillboard } from './billboardMirror.js';
// Sample altitudes are expressed RELATIVE to the band rather than as absolute
// offsets. The thresholds have already been retuned once (1_000_000 → 150_000
@@ -351,7 +352,7 @@ for (const fixture of LAYERS) {
onGround: true,
models: [[icao24, model]],
});
billboard.show = false;
setBillboard(billboard, 'show', false);
let ownsVisual = fixture.driveFleetModel({ icao24, position, course: 92 });
assert.equal(ownsVisual, false, 'cold terrain cannot transfer ownership to 3D');
@@ -487,7 +488,7 @@ for (const fixture of LAYERS) {
onGround: true,
models: [[icao24, model]],
});
billboard.show = false;
setBillboard(billboard, 'show', false);
const atA = Cesium.Cartesian3.fromDegrees(A_LON, LAT, 0);
const atB = Cesium.Cartesian3.fromDegrees(B_LON, LAT, 0);
@@ -622,7 +623,7 @@ for (const fixture of LAYERS) {
0,
1e-6,
), 'a hidden tracked model cannot steal the detection anchor');
billboard.show = false; // tracking hides the fleet sprite; the tracked entity remains the 2D owner
setBillboard(billboard, 'show', false); // tracking hides the fleet sprite; the tracked entity remains the 2D owner
const loadingNearby = fixture.layer.getNearby(billboard.position, 1_000, 50)
.find((object) => object.icao24 === (fixture.name === 'flights' ? 'a0f001' : 'b0f001'));
assert.ok(loadingNearby, 'the tracked contact remains in proximity while its model loads');
@@ -661,7 +662,7 @@ for (const fixture of LAYERS) {
viewer.tileset.tilesLoaded = false;
sampledHeight = undefined;
billboard.position = Cesium.Cartesian3.fromDegrees(-97.71, 30.21, -500);
setBillboard(billboard, 'position', Cesium.Cartesian3.fromDegrees(-97.71, 30.21, -500));
fixture.driveTrackedModel();
assert.equal(model.show, true,
'a successful cached snap survives a poll-time vertical datum change');
@@ -671,7 +672,7 @@ for (const fixture of LAYERS) {
// Taxi past the resample threshold with the tiles gone: the tracked model
// holds its last measured floor rather than handing the operator's own
// selection back to a 2D glyph for a retry backoff.
billboard.position = Cesium.Cartesian3.fromDegrees(-97.709, 30.21, -500);
setBillboard(billboard, 'position', Cesium.Cartesian3.fromDegrees(-97.709, 30.21, -500));
fixture.driveTrackedModel();
assert.equal(model.show, true,
'a taxi-invalidated tracked snap is HELD through the resample outage');
@@ -680,7 +681,7 @@ for (const fixture of LAYERS) {
assert.equal(sampleCalls, 1, 'the hold costs no extra synchronous sample');
// Past the drift bound the memory is released and the gate takes over.
billboard.position = Cesium.Cartesian3.fromDegrees(-97.707, 30.21, -500);
setBillboard(billboard, 'position', Cesium.Cartesian3.fromDegrees(-97.707, 30.21, -500));
fixture.driveTrackedModel();
assert.equal(model.show, false,
'past HELD_SNAP_MAX_DRIFT_M the hold is released and the model is withheld');
+5 -6
View File
@@ -9,6 +9,7 @@ import {
ENRICH_AMBIENT_REFILL_WINDOW_MS,
ENRICH_AMBIENT_PER_SWEEP,
} from './policy.js';
import { billboardState } from '../../data/billboardMirror.js';
export function createEnrichment({
flightState,
@@ -199,11 +200,9 @@ export function createEnrichment({
if (flightState._enrichSeen.has(`t:${icao24}`)) continue; // answered / queued / negative this session
if (!/^[0-9a-f]{6}$/i.test(icao24)) continue; // adsbdb keys are 6-char hex only
if (flightState.records.data.get(icao24)?.onGround) continue; // ground traffic never spends ambient budget (click-to-enrich still works)
if (!bb.position || !occluder.isPointVisible(bb.position)) continue; // beyond the limb
Cesium.Cartesian3.clone(
bb.position,
flightState._scratchModelBS.center,
);
const { position } = billboardState(bb);
if (!position || !occluder.isPointVisible(position)) continue; // beyond the limb
Cesium.Cartesian3.clone(position, flightState._scratchModelBS.center);
if (
cull.computeVisibility(flightState._scratchModelBS) ===
Cesium.Intersect.OUTSIDE
@@ -211,7 +210,7 @@ export function createEnrichment({
continue; // off-screen
cand.push([
icao24,
Cesium.Cartesian3.distanceSquared(camPos, bb.position),
Cesium.Cartesian3.distanceSquared(camPos, position),
]);
}
cand.sort((a, b) => a[1] - b[1]); // nearest first — what the user is looking at resolves first
+13 -8
View File
@@ -1,6 +1,11 @@
import * as Cesium from 'cesium';
import { aircraftIcon } from '../../data/aircraftIcons.js';
import { FOCUS_EVIDENCE_DEV } from './policy.js';
import {
addBillboard,
billboardState,
setBillboard,
} from '../../data/billboardMirror.js';
export function createEvidence({
flightState,
@@ -83,7 +88,7 @@ export function createEvidence({
flightState.records.data.set(id, meta);
flightState._cullPositions.delete(id);
flightState._focusEvidenceIds.add(id);
const bb = flightState._billboardCollection.add({
const bb = addBillboard(flightState._billboardCollection, {
position,
image: aircraftIcon(parts.rendering._iconKind(id, klass)),
width: 20,
@@ -119,7 +124,7 @@ export function createEvidence({
const position = _focusEvidencePosition(record);
const bb = flightState._billboards.get(id);
if (!position || !bb) continue;
bb.position = position;
setBillboard(bb, 'position', position);
const meta = flightState.records.data.get(id);
if (meta) {
if (Number.isFinite(record.trackDeg)) meta.true_track = record.trackDeg;
@@ -139,23 +144,23 @@ export function createEvidence({
if (!FOCUS_EVIDENCE_DEV || !flightState._viewer) return [];
return [...flightState._focusEvidenceIds].map((id) => {
const bb = flightState._billboards.get(id);
const screen = bb?.position
const screen = billboardState(bb)?.position
? Cesium.SceneTransforms.worldToWindowCoordinates(
flightState._viewer.scene,
bb.position,
billboardState(bb).position,
)
: null;
return {
id,
show: bb?.show === true,
scale: bb?.scale ?? null,
show: billboardState(bb)?.show === true,
scale: billboardState(bb)?.scale ?? null,
alpha: bb?.color?.alpha ?? null,
x: screen?.x ?? null,
y: screen?.y ?? null,
cameraDistanceM: bb?.position
cameraDistanceM: billboardState(bb)?.position
? Cesium.Cartesian3.distance(
flightState._viewer.camera.positionWC,
bb.position,
billboardState(bb).position,
)
: null,
};
+5 -2
View File
@@ -35,6 +35,7 @@ import {
DISPLAY_CORRIDOR_CELL_BUDGET,
DISPLAY_CORRIDOR_FAIR_SHARE,
} from './policy.js';
import { billboardState, setBillboard } from '../../data/billboardMirror.js';
export function createMotion({
flightState,
@@ -109,7 +110,7 @@ export function createMotion({
if (newest)
newest.position = Cesium.Cartesian3.clone(position, newest.position);
const bb = flightState._billboards.get(icao24);
if (bb) bb.position = position;
if (bb) setBillboard(bb, 'position', position);
}
}
@@ -131,7 +132,9 @@ export function createMotion({
function _deadReckon(icao24, result, sampledRenderTime) {
const info = flightState.records.data.get(icao24);
if (FOCUS_EVIDENCE_DEV && flightState._focusEvidenceIds.has(icao24)) {
const position = flightState._billboards.get(icao24)?.position;
const position = billboardState(
flightState._billboards.get(icao24),
)?.position;
flightState._drCourseDeg = info?.true_track || 0;
flightState._drSpeedMps = info?.velocity || 0;
flightState._drCourseHold =
+12 -8
View File
@@ -16,6 +16,7 @@ import {
FLEET_DR_INTERVAL_MS,
} from './policy.js';
import { emergencyBroadcastPhrase } from '../../data/aircraftEmergency.js';
import { billboardState, setBillboard } from '../../data/billboardMirror.js';
export function createQueries({
flightState,
@@ -134,7 +135,8 @@ export function createQueries({
const info = flightState.records.data.get(icao24);
const bb = flightState._billboards.get(icao24);
const basePos =
parts.motion._deadReckon(icao24) || (bb ? bb.position : null);
parts.motion._deadReckon(icao24) ||
(bb ? billboardState(bb).position : null);
if (!basePos) return null;
const displayed = displayedKinematics({
derivedSpeedMps: flightState._drSpeedMps,
@@ -336,7 +338,8 @@ export function createQueries({
// here would double-image the tracked plane when 3D is turned off mid-track.
if (flightState._billboardCollection)
for (const [icao, bb] of flightState._billboards) {
if (icao !== flightState._trackedIcao) bb.show = true;
if (icao !== flightState._trackedIcao)
setBillboard(bb, 'show', true);
}
}
}
@@ -454,7 +457,8 @@ export function createQueries({
// doesn't vanish on the 2D→3D handoff; bb.position stays current while hidden.
const model = flightState._models.get(icao24) ?? null;
const modelOwnsVisual = parts.rendering._modelOwnsVisual(icao24, model);
if (!isTracked && !bb.show && !modelOwnsVisual) continue;
if (!isTracked && !billboardState(bb).show && !modelOwnsVisual)
continue;
const info = flightState.records.data.get(icao24);
let object = flightState._detectionObjects.get(icao24);
if (!object) {
@@ -476,7 +480,7 @@ export function createQueries({
? parts.rendering._modelSpec(info?.klass)
: null;
const pos = isTracked
? parts.motion._trackedVisualCached() || bb.position
? parts.motion._trackedVisualCached() || billboardState(bb).position
: modelOwnsVisual
? modelVisualAnchor(
model.modelMatrix,
@@ -486,7 +490,7 @@ export function createQueries({
: spec.scale,
object._weldPos || (object._weldPos = new Cesium.Cartesian3()),
)
: bb.position;
: billboardState(bb).position;
if (!pos) continue;
object.position = pos;
object.skipLabel = isTracked;
@@ -600,7 +604,7 @@ export function createQueries({
if (
!aircraftIncludedInNearby({
isTracked,
billboardShown: bb.show,
billboardShown: billboardState(bb).show,
modelRendering: parts.rendering._modelOwnsVisual(icao24),
includeHidden,
})
@@ -610,7 +614,7 @@ export function createQueries({
const trackedPos = isTracked
? parts.motion._trackedDisplayCached()
: null; // cached, no recompute (anti-jitter)
const pos = trackedPos || bb.position;
const pos = trackedPos || billboardState(bb).position;
if (!pos) continue;
const distance = Cesium.Cartesian3.distance(center, pos);
@@ -692,7 +696,7 @@ export function createQueries({
const result = [];
for (const [icao24, bb] of flightState._billboards) {
const pos = bb.position;
const pos = billboardState(bb).position;
if (!pos) continue;
const carto = Cesium.Cartographic.fromCartesian(
pos,
+18 -6
View File
@@ -3,6 +3,11 @@ import assert from 'node:assert/strict';
import * as Cesium from 'cesium';
import { createQueries } from './queries.js';
import { createRendering } from './rendering.js';
import {
billboardState,
mirrorBillboard,
setBillboard,
} from '../../data/billboardMirror.js';
function fixture() {
const info = {
@@ -10,7 +15,10 @@ function fixture() {
registration: ' N123 ',
altitude: 1000,
};
const bb = { show: true, position: new Cesium.Cartesian3(1, 2, 3) };
const bb = mirrorBillboard({
show: true,
position: new Cesium.Cartesian3(1, 2, 3),
});
const flightState = {
feed: {},
_billboardCollection: { show: true },
@@ -48,9 +56,10 @@ test('candidate text follows in-place enrichment without freezing identity or po
const [first] = f.queries.getDetectableObjects();
assert.equal(first.id, 'TEST123');
assert.equal(first.sourceId, 'abc123');
f.bb.position = new Cesium.Cartesian3(4, 5, 6);
setBillboard(f.bb, 'position', new Cesium.Cartesian3(4, 5, 6));
assert.equal(f.queries.getDetectableObjects()[0], first);
assert.equal(first.position, f.bb.position);
assert.equal(first.position, billboardState(f.bb).position);
assert.deepEqual(first.position, new Cesium.Cartesian3(4, 5, 6));
assert.equal(f.formats(), 1);
f.info.callsign = ' NEW456 ';
assert.equal(f.queries.getDetectableObjects()[0].id, 'NEW456');
@@ -97,7 +106,7 @@ test('candidate collection resolves models once and keeps handoffs live', (t) =>
scale: 1,
});
f.flightState._models.set('abc123', model);
f.bb.show = false;
setBillboard(f.bb, 'show', false);
assert.deepEqual(f.queries.getDetectableObjects(), []);
model.ready = true;
assert.equal(f.queries.getDetectableObjects()[0], candidate);
@@ -111,8 +120,11 @@ test('candidate collection resolves models once and keeps handoffs live', (t) =>
assert.deepEqual(candidate.position, new Cesium.Cartesian3(40, 50, 60));
model.show = false;
assert.deepEqual(f.queries.getDetectableObjects(), []);
f.bb.show = true;
assert.equal(f.queries.getDetectableObjects()[0].position, f.bb.position);
setBillboard(f.bb, 'show', true);
assert.equal(
f.queries.getDetectableObjects()[0].position,
billboardState(f.bb).position,
);
assert.equal(get.mock.callCount(), 6);
model.show = true;
assert.equal(f.parts.rendering._modelOwnsVisual('abc123'), true);
+47 -38
View File
@@ -39,6 +39,7 @@ import {
} from './policy.js';
import { AIRCRAFT_ENVIRONMENT_MAP_OPTIONS } from '../../data/aircraftModelLighting.js';
import { aircraftVisuals } from '../aircraft/visualState.js';
import { billboardState, setBillboard } from '../../data/billboardMirror.js';
export function createRendering({
flightState,
@@ -151,12 +152,12 @@ export function createRendering({
if (isCockpitContact && !isCockpitNear) {
const freshnessAlpha = cyberSonarBaseAlpha(bb);
bb.image = cockpitContactDotImage();
bb.width = COCKPIT_CONTACT_SIZE_PX;
bb.height = COCKPIT_CONTACT_SIZE_PX;
bb.scale = limbScale;
bb.scaleByDistance = _cockpitBillboardScaleByDistance();
setBillboard(bb, 'width', COCKPIT_CONTACT_SIZE_PX);
setBillboard(bb, 'height', COCKPIT_CONTACT_SIZE_PX);
setBillboard(bb, 'scale', limbScale);
setBillboard(bb, 'scaleByDistance', _cockpitBillboardScaleByDistance());
bb.color = _cockpitContactColor(icao24).withAlpha(freshnessAlpha);
bb.rotation = 0;
setBillboard(bb, 'rotation', 0);
return;
}
@@ -165,10 +166,14 @@ export function createRendering({
_iconKind(icao24, meta?.klass),
aircraftVisuals.read(bb)?.iconLarge ? TRACKED_ICON_PX : undefined,
);
bb.width = icao24 === flightState._trackedIcao ? 24 : 20;
bb.height = icao24 === flightState._trackedIcao ? 24 : 20;
bb.scale = _fleetBillboardScale(icao24, meta?.klass) * limbScale;
bb.scaleByDistance = _normalBillboardScaleByDistance();
setBillboard(bb, 'width', icao24 === flightState._trackedIcao ? 24 : 20);
setBillboard(bb, 'height', icao24 === flightState._trackedIcao ? 24 : 20);
setBillboard(
bb,
'scale',
_fleetBillboardScale(icao24, meta?.klass) * limbScale,
);
setBillboard(bb, 'scaleByDistance', _normalBillboardScaleByDistance());
bb.color = _fleetBillboardColor(icao24).withAlpha(cyberSonarBaseAlpha(bb));
}
@@ -324,7 +329,7 @@ export function createRendering({
function _driveFleetModelHandoff(icao24, model, bb, pos, course, beforeShow) {
if (!model) {
bb.show = true;
setBillboard(bb, 'show', true);
return false;
}
const displayPos = _modelDisplayPosition(
@@ -334,7 +339,7 @@ export function createRendering({
);
if (!displayPos) {
model.show = false; // no ground evidence → nothing safe to place a depth-tested model at
bb.show = true;
setBillboard(bb, 'show', true);
return false;
}
// The matrix is written BEFORE the readiness test on purpose: a model can flip
@@ -343,12 +348,12 @@ export function createRendering({
_modelMatrix(displayPos, course, model.modelMatrix);
if (!model.ready) {
model.show = false; // not loaded yet → keep the 2D icon, no half-model flash
bb.show = true;
setBillboard(bb, 'show', true);
return false;
}
beforeShow?.();
if (!model.show) model.show = true;
if (bb.show) bb.show = false; // hand off ONLY once the model renders
if (billboardState(bb).show) setBillboard(bb, 'show', false); // hand off ONLY once the model renders
return true;
}
@@ -437,7 +442,8 @@ export function createRendering({
(!current && flightState._modelPending.has(icao24))
) {
const bb = flightState._billboards.get(icao24);
if (bb && icao24 !== flightState._trackedIcao) bb.show = true;
if (bb && icao24 !== flightState._trackedIcao)
setBillboard(bb, 'show', true);
_releaseModel(icao24);
}
if (
@@ -472,7 +478,8 @@ export function createRendering({
)
continue; // already right state
const bb = flightState._billboards.get(icao);
if (bb && icao !== flightState._trackedIcao) bb.show = true;
if (bb && icao !== flightState._trackedIcao)
setBillboard(bb, 'show', true);
_releaseModel(icao);
}
if (flightState._irReloadQueue.length === 0)
@@ -699,7 +706,8 @@ export function createRendering({
// readout consumers then reuse that exact cached position.
const pos =
parts.motion._trackedDisplayPosition(flightState._trackedIcao) ||
flightState._billboards.get(flightState._trackedIcao)?.position;
billboardState(flightState._billboards.get(flightState._trackedIcao))
?.position;
if (!pos) {
if (flightState._trackedModel) flightState._trackedModel.show = false;
return;
@@ -777,7 +785,9 @@ export function createRendering({
// default here produces a one-frame jump to the Earth's center.
const currentPos =
parts.motion._trackedDisplayCached() ||
flightState._billboards.get(flightState._trackedIcao)?.position;
billboardState(
flightState._billboards.get(flightState._trackedIcao),
)?.position;
if (currentPos) {
const displayPos = _modelDisplayPosition(
flightState._trackedIcao,
@@ -938,12 +948,10 @@ export function createRendering({
// ground or in the air — no distinction"). The cap + nearest-first ordering
// below already bound airport clusters; grounded placement is handled by the
// one-shot ground snap in _modelDisplayPosition.
const d2 = Cesium.Cartesian3.distanceSquared(camPos, bb.position);
const { position } = billboardState(bb);
const d2 = Cesium.Cartesian3.distanceSquared(camPos, position);
if (d2 > keepDistSq) continue; // beyond the keep radius → never eligible
Cesium.Cartesian3.clone(
bb.position,
flightState._scratchModelBS.center,
);
Cesium.Cartesian3.clone(position, flightState._scratchModelBS.center);
cand.push([
icao,
d2,
@@ -973,6 +981,7 @@ export function createRendering({
for (const [icao24, bb] of flightState._billboards) {
if (icao24 === flightState._trackedIcao) continue; // tracked entity owns its own motion
const icon = billboardState(bb);
const info = flightState.records.data.get(icao24);
@@ -996,9 +1005,9 @@ export function createRendering({
// collection's vertex buffer, so skip sub-meter moves.
if (
display &&
Cesium.Cartesian3.distanceSquared(display, bb.position) > 1.0
Cesium.Cartesian3.distanceSquared(display, icon.position) > 1.0
) {
bb.position = display;
setBillboard(bb, 'position', display);
}
// Round 6: occlusion-test a LIFTED point for contacts rendering below
@@ -1007,15 +1016,15 @@ export function createRendering({
// pass would hide a plane that is really just low over high-N terrain
// waiting for its floor to warm (ATL grounded contacts at geoid −31 m).
const beyondHorizon = !occluder.isPointVisible(
flightState._cullPositions.get(icao24) || bb.position,
flightState._cullPositions.get(icao24) || icon.position,
);
// A billboard flipping INTO view (horizon reveal while the camera idles)
// gets its rotation refreshed THIS tick even without a pose change —
// otherwise it reappears wearing its stale (often creation-north) nose for
// up to ROTATION_REFRESH_MS. (Model-handed-off planes also read show=false
// here; harmless — the model branch below `continue`s past the rotation.)
const revealed = !beyondHorizon && !bb.show;
if (bb.show === beyondHorizon) bb.show = !beyondHorizon;
const revealed = !beyondHorizon && !icon.show;
if (icon.show === beyondHorizon) setBillboard(bb, 'show', !beyondHorizon);
if (beyondHorizon) {
// Also hide any 3D model — otherwise a model that crossed the limb would keep
// rendering through the hidden globe at its last matrix.
@@ -1031,22 +1040,22 @@ export function createRendering({
// alpha; nearer focus behavior remains tunable rather than universal.
const cameraDistanceM = Cesium.Cartesian3.distance(
camera.positionWC,
bb.position,
icon.position,
);
const distanceScale = nearFarScalarValueAtDistance(
bb.scaleByDistance,
icon.scaleByDistance,
cameraDistanceM,
);
const focus = advanceProjectedSpriteFocus(
bb,
bb.position,
icon.position,
scene,
camera,
nowMs,
focusTarget,
undefined,
(bb.width || 20) * (bb.scale || 1) * distanceScale * 0.5,
(bb.height || 20) * (bb.scale || 1) * distanceScale * 0.5,
(icon.width || 20) * (icon.scale || 1) * distanceScale * 0.5,
(icon.height || 20) * (icon.scale || 1) * distanceScale * 0.5,
);
const isCockpitNear =
flightState._cockpitContactMode &&
@@ -1077,8 +1086,8 @@ export function createRendering({
// hysteresis so zoom oscillation never thrashes the atlas.
if (!flightState._cockpitContactMode || isCockpitNear) {
const glyphDevPx =
(bb.width || 20) *
(bb.scale || 1) *
(icon.width || 20) *
(icon.scale || 1) *
distanceScale *
(globalThis.devicePixelRatio || 1);
const visual = aircraftVisuals.write(bb);
@@ -1160,12 +1169,12 @@ export function createRendering({
) {
const rot = screenProjectedRotation(
scene,
bb.position,
icon.position,
course,
bb.rotation,
icon.rotation,
);
if (rot !== null && Math.abs(rot - bb.rotation) > 0.002) {
bb.rotation = rot;
if (rot !== null && Math.abs(rot - icon.rotation) > 0.002) {
setBillboard(bb, 'rotation', rot);
}
}
}
+2 -1
View File
@@ -7,6 +7,7 @@ import {
} from '../../data/motionModel.js';
import { aircraftIcon } from '../../data/aircraftIcons.js';
import { POSITION_HISTORY_LIMIT } from './recordPolicy.js';
import { addBillboard } from '../../data/billboardMirror.js';
/** Apply reconciled aircraft records to Cesium resources and follow state. */
export function createFlightSnapshotRenderer({
@@ -195,7 +196,7 @@ export function createFlightSnapshotRenderer({
rendering._modelOwnsVisual(icao24),
)) ||
position;
const bb = flightState._billboardCollection.add({
const bb = addBillboard(flightState._billboardCollection, {
position: displayed,
image: aircraftIcon(rendering._iconKind(icao24, meta.klass)),
width: isTracked ? 24 : 20,
+3 -2
View File
@@ -1,5 +1,6 @@
import * as Cesium from 'cesium';
import { CYAN_TRANSPARENT } from './policy.js';
import { mirrorBillboard } from '../../data/billboardMirror.js';
export function createTesting({
flightState,
@@ -68,7 +69,7 @@ export function createTesting({
flightState._viewer = viewer;
flightState._modelCollection = modelCollection;
flightState._billboardCollection = billboardCollection;
flightState._billboards = new Map([[icao24, billboard]]);
flightState._billboards = new Map([[icao24, mirrorBillboard(billboard)]]);
flightState._cullPositions.clear();
flightState._models.clear();
for (const [key, model] of models) flightState._models.set(key, model);
@@ -117,7 +118,7 @@ export function createTesting({
billboard,
history = [],
}) {
flightState._billboards.set(icao24, billboard);
flightState._billboards.set(icao24, mirrorBillboard(billboard));
flightState.records.data.set(icao24, meta);
flightState._positionHistory.set(icao24, history);
}
+26 -18
View File
@@ -26,6 +26,7 @@ import {
} from './policy.js';
import { roundHeadingDegrees } from '../../data/headingDegrees.js';
import { entityRecord, readEntityRecord } from '../../data/entityRecords.js';
import { billboardState, setBillboard } from '../../data/billboardMirror.js';
export function createTracking({
flightState,
@@ -65,7 +66,7 @@ export function createTracking({
function _publishTrackedSelection(icao24, origin = 'programmatic') {
const bb = flightState._billboards.get(icao24);
const info = flightState.records.data.get(icao24);
if (!bb?.position || !info) return false;
if (!billboardState(bb)?.position || !info) return false;
if (flightState._trackedEntity)
entityRecord(flightState._trackedEntity).selectionOrigin = origin;
_emitAwarenessEvent('gev:awareness-subject-selected', {
@@ -76,7 +77,7 @@ export function createTracking({
// a callsign-less contact reached Context as its raw hex even once adsbdb
// had supplied a registration. Identity below stays `icao24`.
label: parts.queries._contactLabel(icao24, info),
position: Cesium.Cartesian3.clone(bb.position),
position: Cesium.Cartesian3.clone(billboardState(bb).position),
origin,
});
selectTrackedSubjectContext(_contextSubjectMetadata(icao24));
@@ -162,12 +163,13 @@ export function createTracking({
const previous = flightState._cockpitNearContacts;
const distancesSquared = [];
for (const [icao24, bb] of flightState._billboards) {
if (icao24 === flightState._trackedIcao || !bb?.position) continue;
const position = billboardState(bb)?.position;
if (icao24 === flightState._trackedIcao || !position) continue;
distancesSquared.push([
icao24,
Cesium.Cartesian3.distanceSquared(
flightState._viewer.camera.positionWC,
bb.position,
position,
),
]);
}
@@ -316,7 +318,8 @@ export function createTracking({
if (!flightState._trackedIcao || !flightState._viewer) return;
const pos =
parts.motion._trackedDisplayCached() ||
flightState._billboards.get(flightState._trackedIcao)?.position;
billboardState(flightState._billboards.get(flightState._trackedIcao))
?.position;
if (!pos) return;
const projected = screenProjectedRotation(
flightState._viewer.scene,
@@ -642,17 +645,21 @@ export function createTracking({
// ROTATION_REFRESH_MS of a wrong (possibly reversed) nose on release.
if (flightState._billboards.has(flightState._trackedIcao)) {
const bb = flightState._billboards.get(flightState._trackedIcao);
bb.show = true;
bb.width = 20;
bb.height = 20;
setBillboard(bb, 'show', true);
setBillboard(bb, 'width', 20);
setBillboard(bb, 'height', 20);
// Ground/military-aware restore (a plane untracked while taxiing must come
// back muted-gray at ground scale, not white at full scale).
bb.color = parts.rendering._fleetBillboardColor(flightState._trackedIcao);
bb.scale = parts.rendering._fleetBillboardScale(
flightState._trackedIcao,
flightState.records.data.get(flightState._trackedIcao)?.klass,
setBillboard(
bb,
'scale',
parts.rendering._fleetBillboardScale(
flightState._trackedIcao,
flightState.records.data.get(flightState._trackedIcao)?.klass,
),
);
bb.rotation = flightState._lastTrackedRotation;
setBillboard(bb, 'rotation', flightState._lastTrackedRotation);
}
flightState._lastCamPoseSig = ''; // force a fleet rotation pass on the next tick
@@ -830,7 +837,7 @@ export function createTracking({
if (flightState._models.has(id) || flightState._modelPending.has(id))
parts.rendering._releaseModel(id);
const bb = flightState._billboards.get(id);
if (bb && id !== flightState._trackedIcao) bb.show = true; // horizon cull re-asserts next tick
if (bb && id !== flightState._trackedIcao) setBillboard(bb, 'show', true); // horizon cull re-asserts next tick
if (id === flightState._trackedIcao) {
parts.rendering._releaseTrackedModel();
_syncTracked2dRotation();
@@ -862,9 +869,9 @@ export function createTracking({
function _routeIsPlausible(icao24, route) {
const info = flightState.records.data.get(icao24);
const bb = flightState._billboards.get(icao24);
if (!info || !bb || !bb.position) return true;
if (!info || !bb || !billboardState(bb).position) return true;
const carto = Cesium.Cartographic.fromCartesian(
bb.position,
billboardState(bb).position,
Cesium.Ellipsoid.WGS84,
flightState._scratchCarto,
);
@@ -906,21 +913,22 @@ export function createTracking({
flightState._trackedIcao = icao24;
_resetTrackedSelectionState(); // fresh selection: enter at the ENTER ceiling, full load-retry budget
flightState._cachedDRFrame = -1;
flightState._lastTrackedRotation = bb.rotation || 0;
flightState._lastTrackedRotation = billboardState(bb).rotation || 0;
// Drop any fleet 3D model for this aircraft — the tracked entity now owns its visual (its
// own billboard + model graphic), and the fleet tick skips the tracked icao, so a leftover
// fleet model would be orphaned + double-rendered.
parts.rendering._releaseModel(icao24);
// Hide the billboard — the tracked entity replaces it visually
bb.show = false;
setBillboard(bb, 'show', false);
// Helper: smoothed, per-frame-cached tracked position (see _trackedDisplayPosition —
// one computation shared by the position/alignedAxis/rotation/trail-head callbacks,
// with discontinuity reconciliation). Falls back to the last billboard position when
// the aircraft has no fix.
const getTrackedPosition = () =>
parts.motion._trackedDisplayPosition(icao24) || bb.position;
parts.motion._trackedDisplayPosition(icao24) ||
billboardState(bb).position;
// Dead-reckoning position property — smooth continuous motion between API updates.
const positionProperty = new Cesium.CallbackProperty(() => {
+12 -8
View File
@@ -11,6 +11,7 @@ import {
} from '../../data/contactMatch.js';
import { LANDED_ALT_MAX_FT, LANDED_SPEED_MAX_MPS } from './policy.js';
import { emergencyBroadcastPhrase } from '../../data/aircraftEmergency.js';
import { billboardState, setBillboard } from '../../data/billboardMirror.js';
export function createQueries({
flightState,
@@ -96,7 +97,8 @@ export function createQueries({
const info = flightState.records.data.get(icao24);
const bb = flightState._billboards.get(icao24);
const basePos =
parts.motion._deadReckon(icao24) || (bb ? bb.position : null);
parts.motion._deadReckon(icao24) ||
(bb ? billboardState(bb).position : null);
if (!basePos) return null;
const displayed = displayedKinematics({
derivedSpeedMps: flightState._drSpeedMps,
@@ -272,7 +274,8 @@ export function createQueries({
// plane's own fleet billboard — its tracked entity is the visual (avoids a double-image).
if (flightState._billboardCollection)
for (const [icao, bb] of flightState._billboards) {
if (icao !== flightState._trackedIcao) bb.show = true;
if (icao !== flightState._trackedIcao)
setBillboard(bb, 'show', true);
}
}
}
@@ -375,7 +378,7 @@ export function createQueries({
if (
!aircraftIncludedInNearby({
isTracked,
billboardShown: bb.show,
billboardShown: billboardState(bb).show,
modelRendering: parts.rendering._modelOwnsVisual(icao24),
includeHidden,
})
@@ -385,7 +388,7 @@ export function createQueries({
const trackedPos = isTracked
? parts.motion._trackedDisplayCached()
: null; // cached, no recompute (anti-jitter)
const pos = trackedPos || bb.position;
const pos = trackedPos || billboardState(bb).position;
if (!pos) continue;
const distance = Cesium.Cartesian3.distance(center, pos);
@@ -462,7 +465,8 @@ export function createQueries({
// doesn't vanish on the 2D→3D handoff; bb.position stays current while hidden.
const model = flightState._models.get(icao24);
const modelOwnsVisual = parts.rendering._modelOwnsVisual(icao24);
if (!isTracked && !bb.show && !modelOwnsVisual) continue;
if (!isTracked && !billboardState(bb).show && !modelOwnsVisual)
continue;
const info = flightState.records.data.get(icao24);
const callsign = info?.callsign?.trim();
const registration = info?.registration?.trim();
@@ -484,7 +488,7 @@ export function createQueries({
? parts.rendering._modelSpec(info?.klass)
: null;
const pos = isTracked
? parts.motion._trackedVisualCached() || bb.position
? parts.motion._trackedVisualCached() || billboardState(bb).position
: modelOwnsVisual
? modelVisualAnchor(
model.modelMatrix,
@@ -494,7 +498,7 @@ export function createQueries({
: spec.scale,
object._weldPos || (object._weldPos = new Cesium.Cartesian3()),
)
: bb.position;
: billboardState(bb).position;
if (!pos) continue;
object.position = pos;
object.skipLabel = isTracked;
@@ -616,7 +620,7 @@ export function createQueries({
const result = [];
for (const [icao24, bb] of flightState._billboards) {
const pos = bb.position;
const pos = billboardState(bb).position;
if (!pos) continue;
const carto = Cesium.Cartographic.fromCartesian(
pos,
+43 -38
View File
@@ -55,6 +55,7 @@ import {
} from './policy.js';
import { AIRCRAFT_ENVIRONMENT_MAP_OPTIONS } from '../../data/aircraftModelLighting.js';
import { aircraftVisuals } from '../aircraft/visualState.js';
import { billboardState, setBillboard } from '../../data/billboardMirror.js';
export function createRendering({
flightState,
@@ -159,12 +160,12 @@ export function createRendering({
if (isCockpitContact && !isCockpitNear) {
const freshnessAlpha = cyberSonarBaseAlpha(bb);
bb.image = cockpitContactDotImage();
bb.width = COCKPIT_CONTACT_SIZE_PX;
bb.height = COCKPIT_CONTACT_SIZE_PX;
bb.scale = limbScale;
bb.scaleByDistance = _cockpitBillboardScaleByDistance();
setBillboard(bb, 'width', COCKPIT_CONTACT_SIZE_PX);
setBillboard(bb, 'height', COCKPIT_CONTACT_SIZE_PX);
setBillboard(bb, 'scale', limbScale);
setBillboard(bb, 'scaleByDistance', _cockpitBillboardScaleByDistance());
bb.color = _fleetBillboardColor(icao24).withAlpha(freshnessAlpha);
bb.rotation = 0;
setBillboard(bb, 'rotation', 0);
return;
}
@@ -173,10 +174,10 @@ export function createRendering({
_iconKind(icao24, meta?.klass),
aircraftVisuals.read(bb)?.iconLarge ? TRACKED_ICON_PX : undefined,
);
bb.width = icao24 === flightState._trackedIcao ? 24 : 20;
bb.height = icao24 === flightState._trackedIcao ? 24 : 20;
bb.scale = _militaryBillboardScale(icao24) * limbScale;
bb.scaleByDistance = _normalBillboardScaleByDistance();
setBillboard(bb, 'width', icao24 === flightState._trackedIcao ? 24 : 20);
setBillboard(bb, 'height', icao24 === flightState._trackedIcao ? 24 : 20);
setBillboard(bb, 'scale', _militaryBillboardScale(icao24) * limbScale);
setBillboard(bb, 'scaleByDistance', _normalBillboardScaleByDistance());
bb.color = _fleetBillboardColor(icao24, meta).withAlpha(
cyberSonarBaseAlpha(bb),
);
@@ -330,7 +331,7 @@ export function createRendering({
function _driveFleetModelHandoff(icao24, model, bb, pos, course, beforeShow) {
if (!model) {
bb.show = true;
setBillboard(bb, 'show', true);
return false;
}
const displayPos = _modelDisplayPosition(
@@ -340,7 +341,7 @@ export function createRendering({
);
if (!displayPos) {
model.show = false; // no ground evidence → nothing safe to place a depth-tested model at
bb.show = true;
setBillboard(bb, 'show', true);
return false;
}
// The matrix is written BEFORE the readiness test on purpose: a model can flip
@@ -354,12 +355,12 @@ export function createRendering({
);
if (!model.ready) {
model.show = false; // not loaded yet → keep the 2D icon, no half-model flash
bb.show = true;
setBillboard(bb, 'show', true);
return false;
}
beforeShow?.();
if (!model.show) model.show = true;
if (bb.show) bb.show = false; // hand off ONLY once the model renders
if (billboardState(bb).show) setBillboard(bb, 'show', false); // hand off ONLY once the model renders
return true;
}
@@ -405,7 +406,8 @@ export function createRendering({
(!current && flightState._modelPending.has(icao24))
) {
const bb = flightState._billboards.get(icao24);
if (bb && icao24 !== flightState._trackedIcao) bb.show = true;
if (bb && icao24 !== flightState._trackedIcao)
setBillboard(bb, 'show', true);
_releaseModel(icao24);
}
if (
@@ -440,7 +442,8 @@ export function createRendering({
)
continue; // already right state
const bb = flightState._billboards.get(icao);
if (bb && icao !== flightState._trackedIcao) bb.show = true;
if (bb && icao !== flightState._trackedIcao)
setBillboard(bb, 'show', true);
_releaseModel(icao);
}
if (flightState._irReloadQueue.length === 0)
@@ -662,7 +665,8 @@ export function createRendering({
// readout consumers then reuse that exact cached position.
const pos =
parts.motion._trackedDisplayPosition(flightState._trackedIcao) ||
flightState._billboards.get(flightState._trackedIcao)?.position;
billboardState(flightState._billboards.get(flightState._trackedIcao))
?.position;
if (!pos) {
if (flightState._trackedModel) flightState._trackedModel.show = false;
return;
@@ -738,7 +742,9 @@ export function createRendering({
// default here produces a one-frame jump to the Earth's center.
const currentPos =
parts.motion._trackedDisplayCached() ||
flightState._billboards.get(flightState._trackedIcao)?.position;
billboardState(
flightState._billboards.get(flightState._trackedIcao),
)?.position;
if (currentPos) {
const displayPos = _modelDisplayPosition(
flightState._trackedIcao,
@@ -897,12 +903,10 @@ export function createRendering({
// 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);
const { position } = billboardState(bb);
const d2 = Cesium.Cartesian3.distanceSquared(camPos, position);
if (d2 > keepDistSq) continue; // beyond keep radius → never eligible
Cesium.Cartesian3.clone(
bb.position,
flightState._scratchModelBS.center,
);
Cesium.Cartesian3.clone(position, flightState._scratchModelBS.center);
cand.push([
icao,
d2,
@@ -932,27 +936,28 @@ export function createRendering({
for (const [icao24, bb] of flightState._billboards) {
if (icao24 === flightState._trackedIcao) continue; // tracked entity owns its own motion
const icon = billboardState(bb);
const dr = parts.motion._deadReckon(icao24, flightState._scratchFleetPos);
// Gate the write — assigning Billboard.position dirties the whole
// collection's vertex buffer, so skip sub-meter moves.
if (dr && Cesium.Cartesian3.distanceSquared(dr, bb.position) > 1.0) {
bb.position = dr;
if (dr && Cesium.Cartesian3.distanceSquared(dr, icon.position) > 1.0) {
setBillboard(bb, 'position', dr);
}
// Round 6: occlusion-test a LIFTED point for contacts at/below the
// ellipsoid (mirror of flights.js — sub-ellipsoid points near the limb
// read "beyond the horizon" and would hide low contacts awaiting floors).
const beyondHorizon = !occluder.isPointVisible(
flightState.records.data.get(icao24)?.cullPosition || bb.position,
flightState.records.data.get(icao24)?.cullPosition || icon.position,
);
// A billboard flipping INTO view (horizon reveal while the camera idles)
// gets its rotation refreshed THIS tick even without a pose change —
// otherwise it reappears wearing its stale (often creation-north) nose for
// up to ROTATION_REFRESH_MS. (Model-handed-off planes also read show=false
// here; harmless — the model branch below `continue`s past the rotation.)
const revealed = !beyondHorizon && !bb.show;
if (bb.show === beyondHorizon) bb.show = !beyondHorizon;
const revealed = !beyondHorizon && !icon.show;
if (icon.show === beyondHorizon) setBillboard(bb, 'show', !beyondHorizon);
if (beyondHorizon) {
// Also hide any 3D model — otherwise a model that crossed the limb would keep
// rendering through the hidden globe at its last matrix.
@@ -968,22 +973,22 @@ export function createRendering({
// untouched and multiplicative; there is no cull or zero-alpha path.
const cameraDistanceM = Cesium.Cartesian3.distance(
camera.positionWC,
bb.position,
icon.position,
);
const distanceScale = nearFarScalarValueAtDistance(
bb.scaleByDistance,
icon.scaleByDistance,
cameraDistanceM,
);
const focus = advanceProjectedSpriteFocus(
bb,
bb.position,
icon.position,
scene,
camera,
nowMs,
focusTarget,
undefined,
(bb.width || 20) * (bb.scale || 1) * distanceScale * 0.5,
(bb.height || 20) * (bb.scale || 1) * distanceScale * 0.5,
(icon.width || 20) * (icon.scale || 1) * distanceScale * 0.5,
(icon.height || 20) * (icon.scale || 1) * distanceScale * 0.5,
);
const isCockpitNear =
flightState._cockpitContactMode &&
@@ -1007,8 +1012,8 @@ export function createRendering({
// focus/limb recession counts) with hysteresis (atlas has no mips).
if (!flightState._cockpitContactMode || isCockpitNear) {
const glyphDevPx =
(bb.width || 20) *
(bb.scale || 1) *
(icon.width || 20) *
(icon.scale || 1) *
distanceScale *
(globalThis.devicePixelRatio || 1);
const visual = aircraftVisuals.write(bb);
@@ -1087,12 +1092,12 @@ export function createRendering({
) {
const rot = screenProjectedRotation(
scene,
bb.position,
icon.position,
course,
bb.rotation,
icon.rotation,
);
if (rot !== null && Math.abs(rot - bb.rotation) > 0.002) {
bb.rotation = rot;
if (rot !== null && Math.abs(rot - icon.rotation) > 0.002) {
setBillboard(bb, 'rotation', rot);
}
}
}
+3 -2
View File
@@ -9,6 +9,7 @@ import {
import { aircraftIcon } from '../../data/aircraftIcons.js';
import { POSITION_HISTORY_LIMIT } from './recordPolicy.js';
import { BILLBOARD_SCALE, GROUND_SCALE, TRACKED_ICON_COLOR } from './policy.js';
import { addBillboard, setBillboard } from '../../data/billboardMirror.js';
/** Apply military record changes to Cesium resources and existing follow operations. */
export function createMilitarySnapshotRenderer({
@@ -150,7 +151,7 @@ export function createMilitarySnapshotRenderer({
// it ahead until the next fleet tick snapped it back.
const displayed =
motion._deadReckon(icao24, new Cesium.Cartesian3()) || position;
const bb = flightState._billboardCollection.add({
const bb = addBillboard(flightState._billboardCollection, {
position: displayed,
image: aircraftIcon(rendering._iconKind(icao24, meta.klass)),
width: isTracked ? 24 : 20,
@@ -226,7 +227,7 @@ export function createMilitarySnapshotRenderer({
if (newest)
newest.position = Cesium.Cartesian3.clone(liftedPos, newest.position);
const bbStale = flightState._billboards.get(icao24);
if (bbStale) bbStale.position = liftedPos;
if (bbStale) setBillboard(bbStale, 'position', liftedPos);
}
// Round 4: sample the RENDERED mesh for those cells (one-shot per cell,
// budget-capped, viewer-proximate, google-3d regime only), excluding
+3 -2
View File
@@ -1,4 +1,5 @@
import { AMBER_TRANSPARENT, TRACKED_ICON_COLOR } from './policy.js';
import { mirrorBillboard } from '../../data/billboardMirror.js';
export function createTesting({
flightState,
@@ -39,7 +40,7 @@ export function createTesting({
flightState._viewer = viewer;
flightState._modelCollection = modelCollection;
flightState._billboardCollection = billboardCollection;
flightState._billboards = new Map([[icao24, billboard]]);
flightState._billboards = new Map([[icao24, mirrorBillboard(billboard)]]);
flightState._cullPositions.clear();
flightState._models.clear();
for (const [key, model] of models) flightState._models.set(key, model);
@@ -86,7 +87,7 @@ export function createTesting({
billboard,
history = [],
}) {
flightState._billboards.set(icao24, billboard);
flightState._billboards.set(icao24, mirrorBillboard(billboard));
flightState.records.data.set(icao24, meta);
flightState._positionHistory.set(icao24, history);
}
+24 -16
View File
@@ -27,6 +27,7 @@ import {
} from './policy.js';
import { roundHeadingDegrees } from '../../data/headingDegrees.js';
import { entityRecord, readEntityRecord } from '../../data/entityRecords.js';
import { billboardState, setBillboard } from '../../data/billboardMirror.js';
export function createTracking({
flightState,
@@ -64,7 +65,7 @@ export function createTracking({
function _publishTrackedSelection(icao24, origin = 'programmatic') {
const bb = flightState._billboards.get(icao24);
const info = flightState.records.data.get(icao24);
if (!bb?.position || !info) return false;
if (!billboardState(bb)?.position || !info) return false;
if (flightState._trackedEntity)
entityRecord(flightState._trackedEntity).selectionOrigin = origin;
_emitAwarenessEvent('gev:awareness-subject-selected', {
@@ -74,7 +75,7 @@ export function createTracking({
parts.queries._toCleanText(info.callsign) ||
parts.queries._toCleanText(info.registration) ||
icao24,
position: Cesium.Cartesian3.clone(bb.position),
position: Cesium.Cartesian3.clone(billboardState(bb).position),
origin,
});
selectTrackedSubjectContext(_contextSubjectMetadata(icao24));
@@ -151,12 +152,13 @@ export function createTracking({
const previous = flightState._cockpitNearContacts;
const distancesSquared = [];
for (const [icao24, bb] of flightState._billboards) {
if (icao24 === flightState._trackedIcao || !bb?.position) continue;
const position = billboardState(bb)?.position;
if (icao24 === flightState._trackedIcao || !position) continue;
distancesSquared.push([
icao24,
Cesium.Cartesian3.distanceSquared(
flightState._viewer.camera.positionWC,
bb.position,
position,
),
]);
}
@@ -295,7 +297,8 @@ export function createTracking({
if (flightState._models.has(id) || flightState._modelPending.has(id))
parts.rendering._releaseModel(id);
const modelled = flightState._billboards.get(id);
if (modelled && id !== flightState._trackedIcao) modelled.show = true; // horizon cull re-asserts next tick
if (modelled && id !== flightState._trackedIcao)
setBillboard(modelled, 'show', true); // horizon cull re-asserts next tick
if (id === flightState._trackedIcao) {
parts.rendering._releaseTrackedModel();
_syncTracked2dRotation();
@@ -411,7 +414,8 @@ export function createTracking({
if (!flightState._trackedIcao || !flightState._viewer) return;
const pos =
parts.motion._trackedDisplayCached() ||
flightState._billboards.get(flightState._trackedIcao)?.position;
billboardState(flightState._billboards.get(flightState._trackedIcao))
?.position;
if (!pos) return;
const projected = screenProjectedRotation(
flightState._viewer.scene,
@@ -780,21 +784,24 @@ export function createTracking({
if (flightState._billboards.has(flightState._trackedIcao)) {
const bb = flightState._billboards.get(flightState._trackedIcao);
const meta = flightState.records.data.get(flightState._trackedIcao);
bb.show = true;
bb.width = 20;
bb.height = 20;
setBillboard(bb, 'show', true);
setBillboard(bb, 'width', 20);
setBillboard(bb, 'height', 20);
// Ground-aware restore (a plane untracked while taxiing comes back at
// ground scale; the fleet tint is amber, or red while broadcasting an
// emergency, on the ground and in the air).
bb.scale =
setBillboard(
bb,
'scale',
BILLBOARD_SCALE *
(CLASS_SCALE_2D[meta?.klass] || 1) *
(meta?.onGround ? GROUND_SCALE : 1);
(CLASS_SCALE_2D[meta?.klass] || 1) *
(meta?.onGround ? GROUND_SCALE : 1),
);
bb.color = parts.rendering._fleetBillboardColor(
flightState._trackedIcao,
meta,
);
bb.rotation = flightState._lastTrackedRotation;
setBillboard(bb, 'rotation', flightState._lastTrackedRotation);
}
flightState._lastCamPoseSig = ''; // force a fleet rotation pass on the next tick
@@ -869,21 +876,22 @@ export function createTracking({
flightState._trackedIcao = icao24;
_resetTrackedSelectionState(); // fresh selection: enter at the ENTER ceiling, full load-retry budget
flightState._cachedDRFrame = -1;
flightState._lastTrackedRotation = bb.rotation || 0;
flightState._lastTrackedRotation = billboardState(bb).rotation || 0;
// Drop any fleet 3D model for this aircraft — the tracked entity now owns its visual (its
// own billboard + model graphic), and the fleet tick skips the tracked icao, so a leftover
// fleet model would be orphaned + double-rendered.
parts.rendering._releaseModel(icao24);
// Hide the billboard -- the tracked entity replaces it visually
bb.show = false;
setBillboard(bb, 'show', false);
// Helper: smoothed, per-frame-cached tracked position (see _trackedDisplayPosition —
// one computation shared by the position/rotation/trail-head callbacks, with
// discontinuity reconciliation). Falls back to the last billboard position when the
// aircraft has no fix.
const getTrackedPosition = () =>
parts.motion._trackedDisplayPosition(icao24) || bb.position;
parts.motion._trackedDisplayPosition(icao24) ||
billboardState(bb).position;
// Dead-reckoning position property evaluated every render frame.
// isConstant = false so CesiumJS re-evaluates each frame.