Merge current main into Nepal flood contribution

This commit is contained in:
Sameh Khamis committed 2026-09-15 08:24:37 -07:00
commit 1a936ebac1
136 files changed
+11006 -4580

No files matched your search

File diff suppressed because it is too large. Load diff
+388 -199
View File
@@ -103,7 +103,8 @@ const WARMUP_FRAMES = Number(process.env.GEV_ALLOC_WARMUP) || 3000;
// tightest sound estimate of the steady rate.
const CHUNK_FRAMES = Number(process.env.GEV_ALLOC_CHUNK) || 48;
const CHUNK_COUNT = Number(process.env.GEV_ALLOC_CHUNKS) || 10;
const STABILIZATION_CHUNKS = Number(process.env.GEV_ALLOC_STABILIZATION_CHUNKS) || 6;
const STABILIZATION_CHUNKS =
Number(process.env.GEV_ALLOC_STABILIZATION_CHUNKS) || 6;
const FRAME_MS = 16;
const VIEWPORT_WIDTH = 1600;
const VIEWPORT_HEIGHT = 900;
@@ -124,7 +125,9 @@ function silentContext() {
filter: 'none',
globalAlpha: 1,
globalCompositeOperation: 'source-over',
measureText(text) { return { width: String(text).length * 6 }; },
measureText(text) {
return { width: String(text).length * 6 };
},
setTransform() {},
clearRect() {},
save() {},
@@ -188,7 +191,9 @@ function installMockEnvironment({ width, height, dpr }) {
return child;
}
insertBefore(child) { return this.appendChild(child); }
insertBefore(child) {
return this.appendChild(child);
}
setAttribute(name, value) {
if (name === 'id') this.id = String(value);
@@ -196,9 +201,13 @@ function installMockEnvironment({ width, height, dpr }) {
if (this.id) byId.set(this.id, this);
}
getBoundingClientRect() { return this._rect; }
getBoundingClientRect() {
return this._rect;
}
getContext() { return this.tagName === 'CANVAS' ? ctx : null; }
getContext() {
return this.tagName === 'CANVAS' ? ctx : null;
}
querySelector(selector) {
const wanted = selector.slice(1);
@@ -208,24 +217,40 @@ function installMockEnvironment({ width, height, dpr }) {
remove() {
if (this.parentElement) {
this.parentElement.children = this.parentElement.children.filter((child) => child !== this);
this.parentElement.children = this.parentElement.children.filter(
(child) => child !== this,
);
}
if (this.id) byId.delete(this.id);
this.parentElement = null;
}
get nextSibling() { return null; }
get nextSibling() {
return null;
}
}
const body = new MockElement('body');
body.classList = { contains() { return false; } };
body.classList = {
contains() {
return false;
},
};
const document = {
body,
createElement(tagName) { return new MockElement(tagName); },
getElementById(id) { return byId.get(id) || null; },
querySelector(selector) { return byId.get(selector.slice(1)) || null; },
createElement(tagName) {
return new MockElement(tagName);
},
getElementById(id) {
return byId.get(id) || null;
},
querySelector(selector) {
return byId.get(selector.slice(1)) || null;
},
querySelectorAll(selector) {
const element = selector.startsWith('#') ? byId.get(selector.slice(1)) : null;
const element = selector.startsWith('#')
? byId.get(selector.slice(1))
: null;
return element ? [element] : [];
},
};
@@ -234,11 +259,19 @@ function installMockEnvironment({ width, height, dpr }) {
devicePixelRatio: dpr,
addEventListener() {},
removeEventListener() {},
getComputedStyle() { return { display: 'block', visibility: 'visible', opacity: '1' }; },
getComputedStyle() {
return { display: 'block', visibility: 'visible', opacity: '1' };
},
};
globalThis.ResizeObserver = class { observe() {} disconnect() {} };
globalThis.MutationObserver = class { observe() {} disconnect() {} };
globalThis.ResizeObserver = class {
observe() {}
disconnect() {}
};
globalThis.MutationObserver = class {
observe() {}
disconnect() {}
};
globalThis.document = document;
globalThis.window = window;
@@ -249,11 +282,15 @@ function installMockEnvironment({ width, height, dpr }) {
// Array-backed so raising a frame allocates nothing: the harness floor has
// to stay far below the budget the gate asserts.
class MockEvent {
constructor() { this.listeners = []; }
constructor() {
this.listeners = [];
}
addEventListener(listener) {
this.listeners.push(listener);
return () => { this.listeners.length = 0; };
return () => {
this.listeners.length = 0;
};
}
raise() {
@@ -269,7 +306,9 @@ function installMockEnvironment({ width, height, dpr }) {
positionWC: new Cesium.Cartesian3(0, 0, 10_000_000),
positionCartographic: { height: 1000 },
viewMatrix: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY),
frustum: { projectionMatrix: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY) },
frustum: {
projectionMatrix: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY),
},
moveEnd: new MockEvent(),
},
scene: { postRender, requestRender() {} },
@@ -278,7 +317,9 @@ function installMockEnvironment({ width, height, dpr }) {
return {
viewer,
postRender,
advanceTime(ms) { currentTime += ms; },
advanceTime(ms) {
currentTime += ms;
},
};
}
@@ -334,16 +375,18 @@ function buildLocalInfrastructureWorkload(count) {
id: `${isDatacenter ? 'dc' : 'dam'}-${index}`,
layerId: sourceId,
position: workload.entries[index].position,
properties: isDatacenter ? {
tags: {
name: `DC ${1000 + index}`,
operator: `Operator ${index % 17}`,
'capacity:it_load': `${20 + index % 40} MW`,
},
} : {
name: `Dam ${1000 + index}`,
tags: { associated_river: `River ${index % 23}` },
},
properties: isDatacenter
? {
tags: {
name: `DC ${1000 + index}`,
operator: `Operator ${index % 17}`,
'capacity:it_load': `${20 + (index % 40)} MW`,
},
}
: {
name: `Dam ${1000 + index}`,
tags: { associated_river: `River ${index % 23}` },
},
priority: index % 7,
accent: isDatacenter ? '#00ffff' : '#0088ff',
});
@@ -360,12 +403,18 @@ function buildLocalInfrastructureWorkload(count) {
{
sourceId: 'local-datacenters',
entries: datacenters,
options: { collisionCapacity: 96, cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT },
options: {
collisionCapacity: 96,
cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT,
},
},
{
sourceId: 'local-dams',
entries: dams,
options: { collisionCapacity: 96, cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT },
options: {
collisionCapacity: 96,
cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT,
},
},
],
};
@@ -374,7 +423,8 @@ function buildLocalInfrastructureWorkload(count) {
function buildPhase3FirmsWorkload(count) {
const localCount = LOCAL_OVERLAY_COHORT_LIMIT * 2;
const expectedCount = localCount + FIRMS_AMBIENT_COHORT_LIMIT;
if (count !== expectedCount) throw new Error(`phase3-firms requires ${expectedCount} entries`);
if (count !== expectedCount)
throw new Error(`phase3-firms requires ${expectedCount} entries`);
const workload = buildWorkload(count);
const datacenters = [];
const dams = [];
@@ -389,8 +439,13 @@ function buildPhase3FirmsWorkload(count) {
layerId: sourceId,
position,
properties: isDatacenter
? { tags: { name: `DC ${index}`, operator: `Operator ${index % 17}` } }
: { name: `Dam ${index}`, tags: { associated_river: `River ${index % 23}` } },
? {
tags: { name: `DC ${index}`, operator: `Operator ${index % 17}` },
}
: {
name: `Dam ${index}`,
tags: { associated_river: `River ${index % 23}` },
},
priority: index % 7,
accent: isDatacenter ? '#00ffff' : '#0088ff',
});
@@ -399,16 +454,21 @@ function buildPhase3FirmsWorkload(count) {
continue;
}
const fireIndex = index - localCount;
firms.push(applyFirmsOverlayPolicy({
id: `fire:${fireIndex}`,
position,
gapPx: 10,
accent: '224, 82, 82',
title: `▲ ${50 + fireIndex} MW`,
details: ['high · 2h · N20'],
selected: false,
priority: 100 - fireIndex,
}, 12_000_000));
firms.push(
applyFirmsOverlayPolicy(
{
id: `fire:${fireIndex}`,
position,
gapPx: 10,
accent: '224, 82, 82',
title: `▲ ${50 + fireIndex} MW`,
details: ['high · 2h · N20'],
selected: false,
priority: 100 - fireIndex,
},
12_000_000,
),
);
firms.at(-1).horizonCull = false;
}
return {
@@ -417,12 +477,18 @@ function buildPhase3FirmsWorkload(count) {
{
sourceId: 'local-datacenters',
entries: datacenters,
options: { collisionCapacity: 96, cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT },
options: {
collisionCapacity: 96,
cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT,
},
},
{
sourceId: 'local-dams',
entries: dams,
options: { collisionCapacity: 96, cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT },
options: {
collisionCapacity: 96,
cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT,
},
},
{
sourceId: FIRMS_OVERLAY_SOURCE_ID,
@@ -439,9 +505,14 @@ function buildPhase3FirmsWorkload(count) {
function buildPhase3VesselsWorkload(count) {
const localCount = LOCAL_OVERLAY_COHORT_LIMIT * 2;
const vesselAmbientCount = vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
const expectedCount = localCount + FIRMS_AMBIENT_COHORT_LIMIT + vesselAmbientCount + 1;
if (count !== expectedCount) throw new Error(`phase3-vessels requires ${expectedCount} entries`);
const vesselAmbientCount = vesselOverlayCohortLimit(
VIEWPORT_WIDTH,
VIEWPORT_HEIGHT,
);
const expectedCount =
localCount + FIRMS_AMBIENT_COHORT_LIMIT + vesselAmbientCount + 1;
if (count !== expectedCount)
throw new Error(`phase3-vessels requires ${expectedCount} entries`);
const workload = buildWorkload(count);
const datacenters = [];
const dams = [];
@@ -457,8 +528,13 @@ function buildPhase3VesselsWorkload(count) {
layerId: sourceId,
position,
properties: isDatacenter
? { tags: { name: `DC ${index}`, operator: `Operator ${index % 17}` } }
: { name: `Dam ${index}`, tags: { associated_river: `River ${index % 23}` } },
? {
tags: { name: `DC ${index}`, operator: `Operator ${index % 17}` },
}
: {
name: `Dam ${index}`,
tags: { associated_river: `River ${index % 23}` },
},
priority: index % 7,
accent: isDatacenter ? '#00ffff' : '#0088ff',
});
@@ -469,34 +545,40 @@ function buildPhase3VesselsWorkload(count) {
const firmsEnd = localCount + FIRMS_AMBIENT_COHORT_LIMIT;
if (index < firmsEnd) {
const fireIndex = index - localCount;
const entry = applyFirmsOverlayPolicy({
id: `fire:${fireIndex}`,
position,
gapPx: 10,
accent: '224, 82, 82',
title: `▲ ${50 + fireIndex} MW`,
details: ['high · 2h · N20'],
selected: false,
priority: 100 - fireIndex,
}, 12_000_000);
const entry = applyFirmsOverlayPolicy(
{
id: `fire:${fireIndex}`,
position,
gapPx: 10,
accent: '224, 82, 82',
title: `▲ ${50 + fireIndex} MW`,
details: ['high · 2h · N20'],
selected: false,
priority: 100 - fireIndex,
},
12_000_000,
);
entry.horizonCull = false;
firms.push(entry);
continue;
}
const vesselIndex = index - firmsEnd;
const selected = vesselIndex === vesselAmbientCount;
const entry = applyVesselOverlayPolicy({
id: selected ? 'vessel:selected' : `vessel:${vesselIndex}`,
position,
gapPx: selected ? 12 : 10,
accent: '57, 213, 255',
title: selected ? 'SELECTED VESSEL' : `VESSEL ${vesselIndex}`,
details: selected
? ['CARGO · 14.5KT · 231°', 'MMSI 353136000 · POS: LIVE']
: ['CARGO · 14.5KT · 231°'],
selected,
priority: selected ? 100000 : vesselAmbientCount - vesselIndex,
}, 12_000_000);
const entry = applyVesselOverlayPolicy(
{
id: selected ? 'vessel:selected' : `vessel:${vesselIndex}`,
position,
gapPx: selected ? 12 : 10,
accent: '57, 213, 255',
title: selected ? 'SELECTED VESSEL' : `VESSEL ${vesselIndex}`,
details: selected
? ['CARGO · 14.5KT · 231°', 'MMSI 353136000 · POS: LIVE']
: ['CARGO · 14.5KT · 231°'],
selected,
priority: selected ? 100000 : vesselAmbientCount - vesselIndex,
},
12_000_000,
);
entry.horizonCull = false;
vessels.push(entry);
}
@@ -506,12 +588,18 @@ function buildPhase3VesselsWorkload(count) {
{
sourceId: 'local-datacenters',
entries: datacenters,
options: { collisionCapacity: 96, cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT },
options: {
collisionCapacity: 96,
cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT,
},
},
{
sourceId: 'local-dams',
entries: dams,
options: { collisionCapacity: 96, cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT },
options: {
collisionCapacity: 96,
cohortLimit: LOCAL_OVERLAY_COHORT_LIMIT,
},
},
{
sourceId: FIRMS_OVERLAY_SOURCE_ID,
@@ -524,7 +612,10 @@ function buildPhase3VesselsWorkload(count) {
{
sourceId: VESSEL_OVERLAY_SOURCE_ID,
entries: vessels,
options: { collisionCapacity: vesselAmbientCount, cohortLimit: vesselAmbientCount },
options: {
collisionCapacity: vesselAmbientCount,
cohortLimit: vesselAmbientCount,
},
},
],
ambientCardCapacity: AMBIENT_CARD_COLLISION_CAPACITY,
@@ -533,14 +624,21 @@ function buildPhase3VesselsWorkload(count) {
function buildPhase3TrackedWorkload(count) {
const localCount = LOCAL_OVERLAY_COHORT_LIMIT * 2;
const vesselAmbientCount = vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
const expectedCount = localCount + FIRMS_AMBIENT_COHORT_LIMIT + vesselAmbientCount + 1;
if (count !== expectedCount) throw new Error(`phase3-tracked requires ${expectedCount} entries`);
const vesselAmbientCount = vesselOverlayCohortLimit(
VIEWPORT_WIDTH,
VIEWPORT_HEIGHT,
);
const expectedCount =
localCount + FIRMS_AMBIENT_COHORT_LIMIT + vesselAmbientCount + 1;
if (count !== expectedCount)
throw new Error(`phase3-tracked requires ${expectedCount} entries`);
const workload = buildPhase3VesselsWorkload(count);
const vesselRegistration = workload.registrations.find(
({ sourceId }) => sourceId === VESSEL_OVERLAY_SOURCE_ID,
);
const selectedIndex = vesselRegistration.entries.findIndex(({ selected }) => selected);
const selectedIndex = vesselRegistration.entries.findIndex(
({ selected }) => selected,
);
const selected = vesselRegistration.entries.splice(selectedIndex, 1)[0];
const trackedEntity = {
gevTrackedId: 'flights:allocation-probe',
@@ -566,10 +664,14 @@ function buildPhase3TrackedWorkload(count) {
}
function buildPhase4CctvWorkload(count) {
const phase3Count = LOCAL_OVERLAY_COHORT_LIMIT * 2 + FIRMS_AMBIENT_COHORT_LIMIT
+ vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) + 1;
const phase3Count =
LOCAL_OVERLAY_COHORT_LIMIT * 2 +
FIRMS_AMBIENT_COHORT_LIMIT +
vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) +
1;
const expectedCount = phase3Count + CCTV_AMBIENT_CARD_MAX + 1;
if (count !== expectedCount) throw new Error(`phase4-cctv requires ${expectedCount} entries`);
if (count !== expectedCount)
throw new Error(`phase4-cctv requires ${expectedCount} entries`);
const workload = buildPhase3TrackedWorkload(phase3Count);
const cctv = [];
for (let index = 0; index <= CCTV_AMBIENT_CARD_MAX; index++) {
@@ -616,11 +718,16 @@ function buildPhase4CctvWorkload(count) {
}
function buildPhase5EarthquakesWorkload(count) {
const phase4Count = LOCAL_OVERLAY_COHORT_LIMIT * 2 + FIRMS_AMBIENT_COHORT_LIMIT
+ vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) + 1
+ CCTV_AMBIENT_CARD_MAX + 1;
const phase4Count =
LOCAL_OVERLAY_COHORT_LIMIT * 2 +
FIRMS_AMBIENT_COHORT_LIMIT +
vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) +
1 +
CCTV_AMBIENT_CARD_MAX +
1;
const expectedCount = phase4Count + EARTHQUAKE_OVERLAY_COHORT_LIMIT;
if (count !== expectedCount) throw new Error(`phase5-earthquakes requires ${expectedCount} entries`);
if (count !== expectedCount)
throw new Error(`phase5-earthquakes requires ${expectedCount} entries`);
const workload = buildPhase4CctvWorkload(phase4Count);
const earthquakes = [];
for (let index = 0; index < EARTHQUAKE_OVERLAY_COHORT_LIMIT; index++) {
@@ -640,7 +747,8 @@ function buildPhase5EarthquakesWorkload(count) {
id: `quake-${index}`,
position,
magnitude: 2.5 + (index % 45) / 10,
accent: index % 3 === 0 ? '#ff0000' : index % 3 === 1 ? '#ffa500' : '#ffff00',
accent:
index % 3 === 0 ? '#ff0000' : index % 3 === 1 ? '#ffa500' : '#ffff00',
});
entry.horizonCull = false;
earthquakes.push(entry);
@@ -657,11 +765,17 @@ function buildPhase5EarthquakesWorkload(count) {
}
function buildPhase5BikeshareWorkload(count) {
const earthquakeCount = LOCAL_OVERLAY_COHORT_LIMIT * 2 + FIRMS_AMBIENT_COHORT_LIMIT
+ vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) + 1
+ CCTV_AMBIENT_CARD_MAX + 1 + EARTHQUAKE_OVERLAY_COHORT_LIMIT;
const earthquakeCount =
LOCAL_OVERLAY_COHORT_LIMIT * 2 +
FIRMS_AMBIENT_COHORT_LIMIT +
vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) +
1 +
CCTV_AMBIENT_CARD_MAX +
1 +
EARTHQUAKE_OVERLAY_COHORT_LIMIT;
const expectedCount = earthquakeCount + 1;
if (count !== expectedCount) throw new Error(`phase5-bikeshare requires ${expectedCount} entries`);
if (count !== expectedCount)
throw new Error(`phase5-bikeshare requires ${expectedCount} entries`);
const workload = buildPhase5EarthquakesWorkload(earthquakeCount);
const position = new Cesium.Cartesian3(0.12, -0.08, 0);
workload.positions.push(position);
@@ -687,11 +801,18 @@ function buildPhase5BikeshareWorkload(count) {
}
function buildPhase5SatellitesWorkload(count) {
const bikeshareCount = LOCAL_OVERLAY_COHORT_LIMIT * 2 + FIRMS_AMBIENT_COHORT_LIMIT
+ vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) + 1
+ CCTV_AMBIENT_CARD_MAX + 1 + EARTHQUAKE_OVERLAY_COHORT_LIMIT + 1;
const bikeshareCount =
LOCAL_OVERLAY_COHORT_LIMIT * 2 +
FIRMS_AMBIENT_COHORT_LIMIT +
vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) +
1 +
CCTV_AMBIENT_CARD_MAX +
1 +
EARTHQUAKE_OVERLAY_COHORT_LIMIT +
1;
const expectedCount = bikeshareCount + 1;
if (count !== expectedCount) throw new Error(`phase5-satellites requires ${expectedCount} entries`);
if (count !== expectedCount)
throw new Error(`phase5-satellites requires ${expectedCount} entries`);
const workload = buildPhase5BikeshareWorkload(bikeshareCount);
const position = new Cesium.Cartesian3(-0.42, 0.54, 0);
workload.positions.push(position);
@@ -707,9 +828,15 @@ function buildPhase5SatellitesWorkload(count) {
}
function buildPhase5CctvProjectionWorkload(count) {
const satelliteCount = LOCAL_OVERLAY_COHORT_LIMIT * 2 + FIRMS_AMBIENT_COHORT_LIMIT
+ vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) + 1
+ CCTV_AMBIENT_CARD_MAX + 1 + EARTHQUAKE_OVERLAY_COHORT_LIMIT + 2;
const satelliteCount =
LOCAL_OVERLAY_COHORT_LIMIT * 2 +
FIRMS_AMBIENT_COHORT_LIMIT +
vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) +
1 +
CCTV_AMBIENT_CARD_MAX +
1 +
EARTHQUAKE_OVERLAY_COHORT_LIMIT +
2;
const expectedCount = satelliteCount + 1;
if (count !== expectedCount) {
throw new Error(`phase5-cctv-projection requires ${expectedCount} entries`);
@@ -738,7 +865,8 @@ function buildPhase5CivilWorkload(count) {
({ sourceId }) => sourceId === TRACKED_OVERLAY_SOURCE_ID,
);
const entry = registration?.entries[0];
if (!entry) throw new Error('phase5-civil requires the protected tracked entry');
if (!entry)
throw new Error('phase5-civil requires the protected tracked entry');
entry.title = 'ALLOC01 · FL350 · 451 kts';
entry.details = ['TEST AIR · A320', 'AUS → LAX'];
return workload;
@@ -750,7 +878,8 @@ function buildPhase5MilitaryWorkload(count) {
({ sourceId }) => sourceId === TRACKED_OVERLAY_SOURCE_ID,
);
const entry = registration?.entries[0];
if (!entry) throw new Error('phase5-military requires the protected tracked entry');
if (!entry)
throw new Error('phase5-military requires the protected tracked entry');
entry.id = 'military:allocation-probe';
entry.title = 'RCH451';
entry.details = ['C17 · 05-8152', 'USAF · 28000 ft · 400 kt'];
@@ -773,12 +902,17 @@ function appendRocketMissionAmbientWorkload(workload, count) {
phase: index * 0.33,
rate: 0.3 + (index % 6) * 0.06,
});
const entry = createRocketMissionMarkerOverlayEntry({
id: `allocation-${index}`,
name: `MISSION ${1000 + index} | PAYLOAD`,
launchSite: `Launch Complex ${index}`,
launchTime: new Date(Date.UTC(2026, 6, 31) - index * 60_000).toISOString(),
}, () => position);
const entry = createRocketMissionMarkerOverlayEntry(
{
id: `allocation-${index}`,
name: `MISSION ${1000 + index} | PAYLOAD`,
launchSite: `Launch Complex ${index}`,
launchTime: new Date(
Date.UTC(2026, 6, 31) - index * 60_000,
).toISOString(),
},
() => position,
);
entry.horizonCull = false;
entries.push(entry);
}
@@ -799,19 +933,29 @@ function buildRocketMissionAmbientWorkload(count) {
`rocket-missions requires ${ROCKET_MISSION_AMBIENT_OVERLAY_COHORT_LIMIT} entries`,
);
}
return appendRocketMissionAmbientWorkload({
entries: [],
positions: [],
drifts: [],
registrations: [],
}, count);
return appendRocketMissionAmbientWorkload(
{
entries: [],
positions: [],
drifts: [],
registrations: [],
},
count,
);
}
function buildPhase5RocketMissionWorkload(count) {
const phase5Count = LOCAL_OVERLAY_COHORT_LIMIT * 2 + FIRMS_AMBIENT_COHORT_LIMIT
+ vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) + 1
+ CCTV_AMBIENT_CARD_MAX + 1 + EARTHQUAKE_OVERLAY_COHORT_LIMIT + 3;
const expectedCount = phase5Count + ROCKET_MISSION_AMBIENT_OVERLAY_COHORT_LIMIT;
const phase5Count =
LOCAL_OVERLAY_COHORT_LIMIT * 2 +
FIRMS_AMBIENT_COHORT_LIMIT +
vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) +
1 +
CCTV_AMBIENT_CARD_MAX +
1 +
EARTHQUAKE_OVERLAY_COHORT_LIMIT +
3;
const expectedCount =
phase5Count + ROCKET_MISSION_AMBIENT_OVERLAY_COHORT_LIMIT;
if (count !== expectedCount) {
throw new Error(`phase5-rockets requires ${expectedCount} entries`);
}
@@ -822,23 +966,42 @@ function buildPhase5RocketMissionWorkload(count) {
}
function buildAllLiveRadioWorkload(count) {
const phase5Count = LOCAL_OVERLAY_COHORT_LIMIT * 2 + FIRMS_AMBIENT_COHORT_LIMIT
+ vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) + 1
+ CCTV_AMBIENT_CARD_MAX + 1 + EARTHQUAKE_OVERLAY_COHORT_LIMIT + 3
+ ROCKET_MISSION_AMBIENT_OVERLAY_COHORT_LIMIT;
const phase5Count =
LOCAL_OVERLAY_COHORT_LIMIT * 2 +
FIRMS_AMBIENT_COHORT_LIMIT +
vesselOverlayCohortLimit(VIEWPORT_WIDTH, VIEWPORT_HEIGHT) +
1 +
CCTV_AMBIENT_CARD_MAX +
1 +
EARTHQUAKE_OVERLAY_COHORT_LIMIT +
3 +
ROCKET_MISSION_AMBIENT_OVERLAY_COHORT_LIMIT;
// 2026-08-18: "every shared-host source" includes the migrated
// submarine-cable cohort, so the aggregate exercises cable labels
// interacting with Radio/earthquake/mission quotas and allocation.
const expectedCount = phase5Count + RADIO_OVERLAY_COHORT_LIMIT + 1
+ CABLE_REFERENCE_LABEL_WINNER_CAP;
if (count !== expectedCount) throw new Error(`all-live-radio requires ${expectedCount} entries`);
const expectedCount =
phase5Count +
RADIO_OVERLAY_COHORT_LIMIT +
1 +
CABLE_REFERENCE_LABEL_WINNER_CAP;
if (count !== expectedCount)
throw new Error(`all-live-radio requires ${expectedCount} entries`);
const workload = buildPhase5RocketMissionWorkload(phase5Count);
appendSubmarineCableWorkload(workload, CABLE_REFERENCE_LABEL_WINNER_CAP);
const entries = [];
for (let index = 0; index < RADIO_OVERLAY_COHORT_LIMIT; index += 1) {
const position = new Cesium.Cartesian3(-0.72 + (index % 8) * 0.18, -0.64 + Math.floor(index / 8) * 0.16, 0);
const position = new Cesium.Cartesian3(
-0.72 + (index % 8) * 0.18,
-0.64 + Math.floor(index / 8) * 0.16,
0,
);
workload.positions.push(position);
workload.drifts.push({ baseX: position.x, baseY: position.y, phase: index * 0.21, rate: 0.35 });
workload.drifts.push({
baseX: position.x,
baseY: position.y,
phase: index * 0.21,
rate: 0.35,
});
const entry = createRadioClusterOverlayEntry({
id: `cluster-${index}`,
position: () => position,
@@ -851,12 +1014,20 @@ function buildAllLiveRadioWorkload(count) {
}
const selectedPosition = new Cesium.Cartesian3(0.08, 0.12, 0);
workload.positions.push(selectedPosition);
workload.drifts.push({ baseX: selectedPosition.x, baseY: selectedPosition.y, phase: 0.5, rate: 0.4 });
const selectedEntry = createRadioSelectedOverlayEntry({
id: 'selected',
name: 'ALLOCATION RADIO',
tags: ['news'],
}, () => selectedPosition);
workload.drifts.push({
baseX: selectedPosition.x,
baseY: selectedPosition.y,
phase: 0.5,
rate: 0.4,
});
const selectedEntry = createRadioSelectedOverlayEntry(
{
id: 'selected',
name: 'ALLOCATION RADIO',
tags: ['news'],
},
() => selectedPosition,
);
selectedEntry.horizonCull = false;
entries.push(selectedEntry);
workload.registrations.push({
@@ -895,7 +1066,8 @@ function appendSubmarineCableWorkload(workload, count) {
const entry = createCableOverlayEntry({
id: `${kind}-reference-${index}-alloc`,
kind,
label: kind === 'cable' ? `Cable System ${index}` : `Landing Point ${index}`,
label:
kind === 'cable' ? `Cable System ${index}` : `Landing Point ${index}`,
tip: position,
distanceM: 200_000 + index * 40_000,
});
@@ -919,7 +1091,9 @@ function appendSubmarineCableWorkload(workload, count) {
function buildSubmarineCablesWorkload(count) {
if (count !== CABLE_REFERENCE_LABEL_WINNER_CAP) {
throw new Error(`submarine-cables requires ${CABLE_REFERENCE_LABEL_WINNER_CAP} entries`);
throw new Error(
`submarine-cables requires ${CABLE_REFERENCE_LABEL_WINNER_CAP} entries`,
);
}
return appendSubmarineCableWorkload(
{ entries: [], positions: [], drifts: [], registrations: [] },
@@ -943,8 +1117,8 @@ function buildPhase6DetectionWorkload(count) {
const row = Math.floor(index / columns);
const baseX = -700_000 + column * spanX;
const baseY = -525_000 + row * spanY;
const normalizedHorizontal = (baseX * baseX + baseY * baseY)
/ (equatorialRadiusM * equatorialRadiusM);
const normalizedHorizontal =
(baseX * baseX + baseY * baseY) / (equatorialRadiusM * equatorialRadiusM);
const z = polarRadiusM * Math.sqrt(Math.max(0, 1 - normalizedHorizontal));
const position = new Cesium.Cartesian3(baseX, baseY, z);
positions.push(position);
@@ -959,7 +1133,10 @@ function buildPhase6DetectionWorkload(count) {
position,
sourceId: `detect-${index}`,
id: `D${String(index).padStart(4, '0')}`,
metric: index % 5 === 0 ? '' : `FL${String(180 + (index % 220)).padStart(3, '0')}`,
metric:
index % 5 === 0
? ''
: `FL${String(180 + (index % 220)).padStart(3, '0')}`,
type: ['AIR', 'SAT', 'SEA', 'VEH'][index % 4],
tier: index % 17 === 0 ? 'military' : undefined,
});
@@ -970,7 +1147,9 @@ function buildPhase6DetectionWorkload(count) {
drifts,
detectionLayer: {
id: 'flights',
getDetectableObjects() { return observations; },
getDetectableObjects() {
return observations;
},
},
};
}
@@ -996,39 +1175,40 @@ function main() {
dpr: 2,
});
initWorldOverlay(env.viewer);
const workload = PROFILE === 'phase6-detection'
? buildPhase6DetectionWorkload(ENTRY_COUNT)
: PROFILE === 'local-infrastructure'
? buildLocalInfrastructureWorkload(ENTRY_COUNT)
: PROFILE === 'phase3-firms'
? buildPhase3FirmsWorkload(ENTRY_COUNT)
: PROFILE === 'phase3-vessels'
? buildPhase3VesselsWorkload(ENTRY_COUNT)
: PROFILE === 'phase3-tracked'
? buildPhase3TrackedWorkload(ENTRY_COUNT)
: PROFILE === 'phase4-cctv'
? buildPhase4CctvWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-earthquakes'
? buildPhase5EarthquakesWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-bikeshare'
? buildPhase5BikeshareWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-satellites'
? buildPhase5SatellitesWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-cctv-projection'
? buildPhase5CctvProjectionWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-civil'
? buildPhase5CivilWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-military'
? buildPhase5MilitaryWorkload(ENTRY_COUNT)
: PROFILE === 'rocket-missions'
? buildRocketMissionAmbientWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-rockets'
? buildPhase5RocketMissionWorkload(ENTRY_COUNT)
: PROFILE === 'all-live-radio'
? buildAllLiveRadioWorkload(ENTRY_COUNT)
: PROFILE === 'submarine-cables'
? buildSubmarineCablesWorkload(ENTRY_COUNT)
: buildWorkload(ENTRY_COUNT);
const workload =
PROFILE === 'phase6-detection'
? buildPhase6DetectionWorkload(ENTRY_COUNT)
: PROFILE === 'local-infrastructure'
? buildLocalInfrastructureWorkload(ENTRY_COUNT)
: PROFILE === 'phase3-firms'
? buildPhase3FirmsWorkload(ENTRY_COUNT)
: PROFILE === 'phase3-vessels'
? buildPhase3VesselsWorkload(ENTRY_COUNT)
: PROFILE === 'phase3-tracked'
? buildPhase3TrackedWorkload(ENTRY_COUNT)
: PROFILE === 'phase4-cctv'
? buildPhase4CctvWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-earthquakes'
? buildPhase5EarthquakesWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-bikeshare'
? buildPhase5BikeshareWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-satellites'
? buildPhase5SatellitesWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-cctv-projection'
? buildPhase5CctvProjectionWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-civil'
? buildPhase5CivilWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-military'
? buildPhase5MilitaryWorkload(ENTRY_COUNT)
: PROFILE === 'rocket-missions'
? buildRocketMissionAmbientWorkload(ENTRY_COUNT)
: PROFILE === 'phase5-rockets'
? buildPhase5RocketMissionWorkload(ENTRY_COUNT)
: PROFILE === 'all-live-radio'
? buildAllLiveRadioWorkload(ENTRY_COUNT)
: PROFILE === 'submarine-cables'
? buildSubmarineCablesWorkload(ENTRY_COUNT)
: buildWorkload(ENTRY_COUNT);
const { entries, positions, drifts } = workload;
const solveIntervalMs = Number(process.env.GEV_ALLOC_SOLVE_MS) || 125;
const detectionActive = !!workload.detectionLayer;
@@ -1080,9 +1260,15 @@ function main() {
}
const warm = getWorldOverlayDiagnostics();
const detectionWarm = detectionActive ? getDetectionDiagnostics() : null;
const painted = detectionActive ? detectionWarm.visibleCount : warm.paintedCount;
const candidates = detectionActive ? detectionWarm.observationCount : warm.candidateCount;
const solveRevisionBefore = detectionActive ? detectionWarm.solveRevision : warm.solveRevision;
const painted = detectionActive
? detectionWarm.visibleCount
: warm.paintedCount;
const candidates = detectionActive
? detectionWarm.observationCount
: warm.candidateCount;
const solveRevisionBefore = detectionActive
? detectionWarm.solveRevision
: warm.solveRevision;
const chunkRates = [];
for (let chunk = 0; chunk < CHUNK_COUNT; chunk++) {
@@ -1101,31 +1287,34 @@ function main() {
const medianBytesPerFrame = sorted[Math.floor(sorted.length / 2)];
const perCandidate = (value) => value / Math.max(1, candidates);
process.stdout.write(`${JSON.stringify({
ok: true,
profile: PROFILE,
ambientCardCapacity: workload.ambientCardCapacity ?? null,
entryCount: ENTRY_COUNT,
warmupFrames: WARMUP_FRAMES,
stabilizationChunks: STABILIZATION_CHUNKS,
chunkFrames: CHUNK_FRAMES,
chunkCount: CHUNK_COUNT,
measuredFrames: CHUNK_FRAMES * CHUNK_COUNT,
paintedCount: painted,
paintedBySource: warm.paintedBySource,
candidateCount: candidates,
detectionSelectedCount: detectionWarm?.selectedCount ?? null,
detectionCollectiveLabelBudget: detectionWarm?.collectiveLabelBudget ?? null,
solveCount: solveRevisionAfter - solveRevisionBefore,
// Frame cost is the primary signal; the per-candidate rate is the
// scale-invariant one. Neither is normalized by paintedCount, which
// saturates at the domain collision capacity.
maxBytesPerFrame,
medianBytesPerFrame,
maxBytesPerCandidatePerFrame: perCandidate(maxBytesPerFrame),
medianBytesPerCandidatePerFrame: perCandidate(medianBytesPerFrame),
chunkBytesPerFrame: chunkRates,
})}\n`);
process.stdout.write(
`${JSON.stringify({
ok: true,
profile: PROFILE,
ambientCardCapacity: workload.ambientCardCapacity ?? null,
entryCount: ENTRY_COUNT,
warmupFrames: WARMUP_FRAMES,
stabilizationChunks: STABILIZATION_CHUNKS,
chunkFrames: CHUNK_FRAMES,
chunkCount: CHUNK_COUNT,
measuredFrames: CHUNK_FRAMES * CHUNK_COUNT,
paintedCount: painted,
paintedBySource: warm.paintedBySource,
candidateCount: candidates,
detectionSelectedCount: detectionWarm?.selectedCount ?? null,
detectionCollectiveLabelBudget:
detectionWarm?.collectiveLabelBudget ?? null,
solveCount: solveRevisionAfter - solveRevisionBefore,
// Frame cost is the primary signal; the per-candidate rate is the
// scale-invariant one. Neither is normalized by paintedCount, which
// saturates at the domain collision capacity.
maxBytesPerFrame,
medianBytesPerFrame,
maxBytesPerCandidatePerFrame: perCandidate(maxBytesPerFrame),
medianBytesPerCandidatePerFrame: perCandidate(medianBytesPerFrame),
chunkBytesPerFrame: chunkRates,
})}\n`,
);
if (detectionActive) destroyDetection();
destroyWorldOverlay();
+241 -84
View File
@@ -49,22 +49,34 @@ export function clearWorldOverlayTextMeasureCache() {
/** Install the font-loading invalidation hooks once, when the API exists. */
export function installWorldOverlayFontInvalidation() {
if (_fontInvalidationInstalled || typeof document === 'undefined' || !document.fonts) return;
if (
_fontInvalidationInstalled ||
typeof document === 'undefined' ||
!document.fonts
)
return;
_fontInvalidationInstalled = true;
_observedFontSet = document.fonts;
const generation = ++_fontInvalidationGeneration;
Promise.resolve(document.fonts.ready)
.then(() => {
if (generation === _fontInvalidationGeneration) clearWorldOverlayTextMeasureCache();
if (generation === _fontInvalidationGeneration)
clearWorldOverlayTextMeasureCache();
})
.catch(() => {});
_observedFontSet.addEventListener?.('loadingdone', clearWorldOverlayTextMeasureCache);
_observedFontSet.addEventListener?.(
'loadingdone',
clearWorldOverlayTextMeasureCache,
);
}
/** Remove font hooks and cached measurements when the overlay host is destroyed. */
export function destroyWorldOverlayDraw() {
_fontInvalidationGeneration++;
_observedFontSet?.removeEventListener?.('loadingdone', clearWorldOverlayTextMeasureCache);
_observedFontSet?.removeEventListener?.(
'loadingdone',
clearWorldOverlayTextMeasureCache,
);
_observedFontSet = null;
_fontInvalidationInstalled = false;
clearWorldOverlayTextMeasureCache();
@@ -95,7 +107,8 @@ export function measureWorldOverlayText(ctx, text, font) {
}
fontCache.set(normalizedText, { width, usedAt: ++_textMeasureClock });
_textMeasureCacheSize++;
if (_textMeasureCacheSize > TEXT_MEASURE_CACHE_LIMIT) evictOldestTextMeasureEntry();
if (_textMeasureCacheSize > TEXT_MEASURE_CACHE_LIMIT)
evictOldestTextMeasureEntry();
return width;
}
@@ -113,7 +126,14 @@ export function getWorldOverlayTextMeasureCacheSize() {
* @param {number} h
* @param {number} radius
*/
export function roundedRectPath(path, x, y, w, h, radius = WORLD_OVERLAY_STYLE.radius) {
export function roundedRectPath(
path,
x,
y,
w,
h,
radius = WORLD_OVERLAY_STYLE.radius,
) {
if (typeof path.roundRect === 'function') {
path.roundRect(x, y, w, h, radius);
return;
@@ -137,11 +157,14 @@ export function roundedRectPath(path, x, y, w, h, radius = WORLD_OVERLAY_STYLE.r
* @param {number} [options.fadeStartRatio]
* @returns {number}
*/
export function distanceFade(distanceM, {
minDistance = 0,
maxDistance = Number.POSITIVE_INFINITY,
fadeStartRatio = 0.7,
} = {}) {
export function distanceFade(
distanceM,
{
minDistance = 0,
maxDistance = Number.POSITIVE_INFINITY,
fadeStartRatio = 0.7,
} = {},
) {
if (!Number.isFinite(distanceM)) return 0;
const min = Number.isFinite(minDistance) ? Math.max(0, minDistance) : 0;
if (distanceM < min) return 0;
@@ -192,17 +215,27 @@ export function altitudeScale(altitudeM, curve = null) {
const end = Number(curve.end);
const midValue = Number(curve.midValue);
const endValue = Number(curve.endValue);
if (!Number.isFinite(fullEnd) || !Number.isFinite(midEnd) || !Number.isFinite(end)
|| !Number.isFinite(midValue) || !Number.isFinite(endValue)) return 1;
if (
!Number.isFinite(fullEnd) ||
!Number.isFinite(midEnd) ||
!Number.isFinite(end) ||
!Number.isFinite(midValue) ||
!Number.isFinite(endValue)
)
return 1;
if (altitudeM <= fullEnd) return 1;
if (altitudeM <= midEnd) {
const span = Math.max(1, midEnd - fullEnd);
let progress = Math.max(0, Math.min(1, (altitudeM - fullEnd) / span));
if (curve.smoothToMid === true) progress = progress * progress * (3 - 2 * progress);
if (curve.smoothToMid === true)
progress = progress * progress * (3 - 2 * progress);
return 1 + (midValue - 1) * progress;
}
if (altitudeM >= end) return endValue;
const progress = Math.max(0, Math.min(1, (altitudeM - midEnd) / Math.max(1, end - midEnd)));
const progress = Math.max(
0,
Math.min(1, (altitudeM - midEnd) / Math.max(1, end - midEnd)),
);
return midValue + (endValue - midValue) * progress;
}
@@ -215,11 +248,14 @@ export function altitudeScale(altitudeM, curve = null) {
* @param {number} [options.fadeEnd]
* @returns {number}
*/
export function altitudeFade(altitudeM, {
minAltitude = Number.NEGATIVE_INFINITY,
fadeStart = Number.POSITIVE_INFINITY,
fadeEnd = Number.POSITIVE_INFINITY,
} = {}) {
export function altitudeFade(
altitudeM,
{
minAltitude = Number.NEGATIVE_INFINITY,
fadeStart = Number.POSITIVE_INFINITY,
fadeEnd = Number.POSITIVE_INFINITY,
} = {},
) {
if (!Number.isFinite(altitudeM)) return 1;
if (Number.isFinite(minAltitude) && altitudeM < minAltitude) return 0;
if (!Number.isFinite(fadeEnd)) return 1;
@@ -238,24 +274,32 @@ export function combinedOverlayAlpha({
altitudeFade: altitudeAlpha = 1,
keyholeEdgeFade = 1,
} = {}) {
return clampUnit(sourceAlpha)
* clampUnit(temporalFade)
* clampUnit(distanceAlpha)
* clampUnit(altitudeAlpha)
* clampUnit(keyholeEdgeFade);
return (
clampUnit(sourceAlpha) *
clampUnit(temporalFade) *
clampUnit(distanceAlpha) *
clampUnit(altitudeAlpha) *
clampUnit(keyholeEdgeFade)
);
}
function clampUnit(value) {
return Number.isFinite(Number(value)) ? Math.max(0, Math.min(1, Number(value))) : 1;
return Number.isFinite(Number(value))
? Math.max(0, Math.min(1, Number(value)))
: 1;
}
function trackDisplayText(entry) {
const title = entry?.title || '';
const detail = Array.isArray(entry?.details) ? entry.details[0] || '' : '';
if (entry._overlayTrackDisplayTitle !== title || entry._overlayTrackDisplayDetail !== detail) {
if (
entry._overlayTrackDisplayTitle !== title ||
entry._overlayTrackDisplayDetail !== detail
) {
entry._overlayTrackDisplayTitle = title;
entry._overlayTrackDisplayDetail = detail;
entry._overlayTrackDisplayText = title && detail ? `${title} · ${detail}` : title || detail;
entry._overlayTrackDisplayText =
title && detail ? `${title} · ${detail}` : title || detail;
}
return entry._overlayTrackDisplayText || '';
}
@@ -268,16 +312,23 @@ function trackDisplayText(entry) {
* @returns {{w:number,h:number,padX:number,padY:number,titleH:number,lineH:number,thumbW:number,thumbH:number}}
*/
export function measureOverlayEntry(ctx, entry, out = {}) {
const variant = entry?.selected ? 'selected' : String(entry?.variant || 'label');
const variant = entry?.selected
? 'selected'
: String(entry?.variant || 'label');
const details = Array.isArray(entry?.details) ? entry.details : [];
const selected = variant === 'selected';
const tracked = variant === 'tracked';
const tactical = entry?.cardStyle === 'tactical';
const titleFont = tracked
? WORLD_OVERLAY_STYLE.fontTrackedTitle
: selected ? WORLD_OVERLAY_STYLE.fontSelected : WORLD_OVERLAY_STYLE.fontTitle;
let titleWidth = measureWorldOverlayText(ctx, entry?.title || '',
variant === 'label' ? WORLD_OVERLAY_STYLE.fontLabel : titleFont);
: selected
? WORLD_OVERLAY_STYLE.fontSelected
: WORLD_OVERLAY_STYLE.fontTitle;
let titleWidth = measureWorldOverlayText(
ctx,
entry?.title || '',
variant === 'label' ? WORLD_OVERLAY_STYLE.fontLabel : titleFont,
);
if (variant === 'track' && details[0]) {
titleWidth = measureWorldOverlayText(
ctx,
@@ -292,16 +343,38 @@ export function measureOverlayEntry(ctx, entry, out = {}) {
measureWorldOverlayText(
ctx,
details[i],
tracked ? WORLD_OVERLAY_STYLE.fontTrackedDetail : WORLD_OVERLAY_STYLE.fontDetail,
tracked
? WORLD_OVERLAY_STYLE.fontTrackedDetail
: WORLD_OVERLAY_STYLE.fontDetail,
),
);
}
out.padX = tracked ? 13 : selected ? 12 : variant === 'label' || variant === 'track' ? 6 : 9;
out.padY = tracked ? 9 : selected ? 8 : variant === 'label' || variant === 'track' ? 4 : 6;
out.padX = tracked
? 13
: selected
? 12
: variant === 'label' || variant === 'track'
? 6
: 9;
out.padY = tracked
? 9
: selected
? 8
: variant === 'label' || variant === 'track'
? 4
: 6;
out.titleH = tactical
? (selected ? 14 : 12)
: tracked ? 13 : selected ? 15 : variant === 'label' || variant === 'track' ? 11 : 13;
? selected
? 14
: 12
: tracked
? 13
: selected
? 15
: variant === 'label' || variant === 'track'
? 11
: 13;
out.lineH = tracked ? 17 : selected ? 15 : 13;
out.thumbW = 0;
out.thumbH = 0;
@@ -328,7 +401,17 @@ export function measureOverlayEntry(ctx, entry, out = {}) {
return out;
}
function writePlacement(out, corner, x, y, w, h, anchorX, anchorY, signedLeaderOffset = 0) {
function writePlacement(
out,
corner,
x,
y,
w,
h,
anchorX,
anchorY,
signedLeaderOffset = 0,
) {
const placement = out || {};
placement.corner = corner;
placement.rect ||= {};
@@ -353,12 +436,17 @@ function writePlacement(out, corner, x, y, w, h, anchorX, anchorY, signedLeaderO
// pushed the card sideways off its anchor, which reads as "the card isn't
// attached to that camera".
placement.leadToX = anchorX;
placement.leadToY = corner === 'above' ? placement.rect.y + h : placement.rect.y;
placement.leadToY =
corner === 'above' ? placement.rect.y + h : placement.rect.y;
} else {
placement.leadFromX = anchorX;
placement.leadFromY = anchorY;
placement.leadToX = corner === 'left' ? placement.rect.x + w : placement.rect.x;
placement.leadToY = Math.max(placement.rect.y, Math.min(anchorY, placement.rect.y + h));
placement.leadToX =
corner === 'left' ? placement.rect.x + w : placement.rect.x;
placement.leadToY = Math.max(
placement.rect.y,
Math.min(anchorY, placement.rect.y + h),
);
}
return placement;
}
@@ -376,7 +464,10 @@ const VERTICAL_PLACEMENT_ORDERS = Object.freeze({
});
function clampPlacementCoordinate(value, size, viewportSize, margin = 4) {
return Math.max(margin, Math.min(value, Math.max(margin, viewportSize - size - margin)));
return Math.max(
margin,
Math.min(value, Math.max(margin, viewportSize - size - margin)),
);
}
/**
@@ -397,19 +488,22 @@ function clampPlacementCoordinate(value, size, viewportSize, margin = 4) {
* @param {Array<object>} [out]
* @returns {Array<object>}
*/
export function placementVariants({
anchorX,
anchorY,
width,
height,
viewportWidth,
viewportHeight,
gap = 12,
preferred = 'auto',
leaderOffset = 0,
verticalOnly = false,
viewportMargin = 4,
}, out = []) {
export function placementVariants(
{
anchorX,
anchorY,
width,
height,
viewportWidth,
viewportHeight,
gap = 12,
preferred = 'auto',
leaderOffset = 0,
verticalOnly = false,
viewportMargin = 4,
},
out = [],
) {
const automatic = anchorY - gap - height >= 4 ? 'above' : 'below';
const orders = verticalOnly ? VERTICAL_PLACEMENT_ORDERS : PLACEMENT_ORDERS;
const order = orders[preferred] || orders[automatic];
@@ -440,7 +534,9 @@ export function placementVariants({
height,
anchorX,
anchorY,
corner === 'above' || corner === 'left' ? negativeLeaderOffset : positiveLeaderOffset,
corner === 'above' || corner === 'left'
? negativeLeaderOffset
: positiveLeaderOffset,
);
}
out.length = order.length;
@@ -525,9 +621,13 @@ function drawCardChrome(ctx, entry, placement, selected = false, drawLeaderLine
if (drawLeaderLine) drawLeader(ctx, placement, accent, 1, 1, entry.leaderStyle);
ctx.beginPath();
roundedRectPath(ctx, x, y, w, h);
ctx.fillStyle = selected ? WORLD_OVERLAY_STYLE.selectedBackground : WORLD_OVERLAY_STYLE.background;
ctx.fillStyle = selected
? WORLD_OVERLAY_STYLE.selectedBackground
: WORLD_OVERLAY_STYLE.background;
ctx.fill();
ctx.strokeStyle = selected ? WORLD_OVERLAY_STYLE.selectedBorder : WORLD_OVERLAY_STYLE.border;
ctx.strokeStyle = selected
? WORLD_OVERLAY_STYLE.selectedBorder
: WORLD_OVERLAY_STYLE.border;
ctx.lineWidth = selected ? 1.25 : 1;
ctx.stroke();
ctx.fillStyle = accent;
@@ -539,7 +639,9 @@ function drawCardText(ctx, entry, placement, selected = false, topOffset = 0) {
const x = placement.rect.x + (selected ? 12 : 9);
let y = placement.rect.y + (selected ? 8 : 6) + topOffset;
ctx.fillStyle = WORLD_OVERLAY_STYLE.title;
ctx.font = selected ? WORLD_OVERLAY_STYLE.fontSelected : WORLD_OVERLAY_STYLE.fontTitle;
ctx.font = selected
? WORLD_OVERLAY_STYLE.fontSelected
: WORLD_OVERLAY_STYLE.fontTitle;
ctx.textBaseline = 'top';
ctx.fillText(String(entry.title || ''), x, y);
y += selected ? 15 : 13;
@@ -554,7 +656,8 @@ function drawCardText(ctx, entry, placement, selected = false, topOffset = 0) {
function colorWithAlpha(color, alpha) {
const text = String(color || WORLD_OVERLAY_STYLE.accent).trim();
const triplet = text.match(/^(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})$/);
if (triplet) return `rgba(${triplet[1]}, ${triplet[2]}, ${triplet[3]}, ${alpha})`;
if (triplet)
return `rgba(${triplet[1]}, ${triplet[2]}, ${triplet[3]}, ${alpha})`;
const hex = text.match(/^#([0-9a-f]{6})$/i);
if (hex) {
const value = Number.parseInt(hex[1], 16);
@@ -591,33 +694,57 @@ function leaderDrawRatio(entry) {
return Number.isFinite(ratio) ? Math.max(0.1, Math.min(0.9, ratio)) : 0.68;
}
export function leaderRevealProgress(entry, timestamp = globalThis.performance?.now?.() ?? Date.now()) {
const linear = Math.min(1, animationLinearProgress(entry, timestamp) / leaderDrawRatio(entry));
export function leaderRevealProgress(
entry,
timestamp = globalThis.performance?.now?.() ?? Date.now(),
) {
const linear = Math.min(
1,
animationLinearProgress(entry, timestamp) / leaderDrawRatio(entry),
);
return 1 - Math.pow(1 - linear, 3);
}
export function tacticalCardRevealAlpha(entry, timestamp = globalThis.performance?.now?.() ?? Date.now()) {
export function tacticalCardRevealAlpha(
entry,
timestamp = globalThis.performance?.now?.() ?? Date.now(),
) {
if (!(Math.max(0, Number(entry?.leaderAnimationMs) || 0) > 0)) return 1;
const start = leaderDrawRatio(entry);
const linear = Math.max(0, Math.min(1,
(animationLinearProgress(entry, timestamp) - start) / Math.max(0.1, 1 - start)));
const linear = Math.max(
0,
Math.min(
1,
(animationLinearProgress(entry, timestamp) - start) /
Math.max(0.1, 1 - start),
),
);
return linear * linear * (3 - 2 * linear);
}
function drawTacticalLeader(ctx, entry, placement, accent, timestamp) {
ctx.strokeStyle = accent;
ctx.lineWidth = (entry.leaderStyle === 'elbow' ? 1.5 : 1) / (placement.paintScale || 1);
ctx.lineWidth =
(entry.leaderStyle === 'elbow' ? 1.5 : 1) / (placement.paintScale || 1);
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.beginPath();
if (entry.leaderStyle !== 'elbow'
|| (placement.corner !== 'above' && placement.corner !== 'below')) {
if (
entry.leaderStyle !== 'elbow' ||
(placement.corner !== 'above' && placement.corner !== 'below')
) {
if (placement.leaderOffset === 0) {
ctx.moveTo(placement.leadFromX, placement.leadFromY);
} else if (placement.corner === 'above' || placement.corner === 'below') {
ctx.moveTo(placement.leadFromX, placement.leadFromY + placement.leaderOffset);
ctx.moveTo(
placement.leadFromX,
placement.leadFromY + placement.leaderOffset,
);
} else {
ctx.moveTo(placement.leadFromX + placement.leaderOffset, placement.leadFromY);
ctx.moveTo(
placement.leadFromX + placement.leaderOffset,
placement.leadFromY,
);
}
ctx.lineTo(placement.leadToX, placement.leadToY);
ctx.stroke();
@@ -632,7 +759,9 @@ function drawTacticalLeader(ctx, entry, placement, accent, timestamp) {
const anchorY = placement.leadFromY;
const horizontalLength = Math.abs(anchorX - cardX);
const verticalLength = Math.abs(anchorY - cardY);
let remaining = leaderRevealProgress(entry, timestamp) * (horizontalLength + verticalLength);
let remaining =
leaderRevealProgress(entry, timestamp) *
(horizontalLength + verticalLength);
ctx.moveTo(anchorX, anchorY);
if (remaining <= horizontalLength) {
const direction = Math.sign(cardX - anchorX) || -1;
@@ -641,7 +770,10 @@ function drawTacticalLeader(ctx, entry, placement, accent, timestamp) {
ctx.lineTo(cardX, anchorY);
remaining -= horizontalLength;
const direction = Math.sign(cardY - anchorY) || -1;
ctx.lineTo(cardX, anchorY + direction * Math.min(verticalLength, remaining));
ctx.lineTo(
cardX,
anchorY + direction * Math.min(verticalLength, remaining),
);
}
ctx.stroke();
}
@@ -657,9 +789,15 @@ export function paintTacticalCard(ctx, entry, placement, alpha = 1) {
ctx.save();
ctx.globalAlpha = alpha;
drawTacticalLeader(ctx, entry, placement, entry.leaderStyle === 'elbow'
? colorWithAlpha(entry.accent, 0.95)
: accentColors.leader, animationTimestamp);
drawTacticalLeader(
ctx,
entry,
placement,
entry.leaderStyle === 'elbow'
? colorWithAlpha(entry.accent, 0.95)
: accentColors.leader,
animationTimestamp,
);
ctx.globalAlpha = alpha * tacticalCardRevealAlpha(entry, animationTimestamp);
@@ -682,13 +820,19 @@ export function paintTacticalCard(ctx, entry, placement, alpha = 1) {
ctx.textAlign = 'left';
ctx.textBaseline = 'alphabetic';
ctx.fillStyle = WORLD_OVERLAY_STYLE.title;
ctx.font = selected ? WORLD_OVERLAY_STYLE.fontSelected : WORLD_OVERLAY_STYLE.fontTitle;
ctx.font = selected
? WORLD_OVERLAY_STYLE.fontSelected
: WORLD_OVERLAY_STYLE.fontTitle;
const titleBaseline = y + layout.padY + layout.titleH - 2;
ctx.fillText(String(entry.title || ''), x + layout.padX, titleBaseline);
ctx.fillStyle = WORLD_OVERLAY_STYLE.detail;
ctx.font = WORLD_OVERLAY_STYLE.fontDetail;
for (let i = 0; i < details.length; i++) {
ctx.fillText(String(details[i]), x + layout.padX, titleBaseline + (i + 1) * layout.lineH);
ctx.fillText(
String(details[i]),
x + layout.padX,
titleBaseline + (i + 1) * layout.lineH,
);
}
ctx.restore();
return placement.rect;
@@ -702,7 +846,11 @@ export function paintLabel(ctx, entry, placement, alpha = 1) {
ctx.fillStyle = WORLD_OVERLAY_STYLE.title;
ctx.font = WORLD_OVERLAY_STYLE.fontLabel;
ctx.textBaseline = 'top';
ctx.fillText(String(entry.title || ''), placement.rect.x + 6, placement.rect.y + 4);
ctx.fillText(
String(entry.title || ''),
placement.rect.x + 6,
placement.rect.y + 4,
);
ctx.restore();
return placement.rect;
}
@@ -805,13 +953,17 @@ export function paintThumbnail(
if (ruleHeight > 0) {
ctx.beginPath();
roundedRectPath(ctx, x, y, w, ruleHeight, Math.min(1, ruleHeight));
ctx.fillStyle = entry.thumbnailRuleColor || entry.accent || WORLD_OVERLAY_STYLE.accent;
ctx.fillStyle =
entry.thumbnailRuleColor || entry.accent || WORLD_OVERLAY_STYLE.accent;
ctx.fill();
}
const titleChars = Math.max(0, Math.floor(Number(entry.thumbnailTitleChars) || 0));
const title = entry._overlayThumbnailTitle
|| String(entry.title || '').toUpperCase();
const titleChars = Math.max(
0,
Math.floor(Number(entry.thumbnailTitleChars) || 0),
);
const title =
entry._overlayThumbnailTitle || String(entry.title || '').toUpperCase();
ctx.textAlign = 'left';
ctx.textBaseline = 'alphabetic';
ctx.fillStyle = entry.thumbnailTitleColor || WORLD_OVERLAY_STYLE.title;
@@ -865,7 +1017,11 @@ export function paintTracked(ctx, entry, placement, alpha = 1) {
ctx.fillStyle = WORLD_OVERLAY_STYLE.detail;
ctx.font = WORLD_OVERLAY_STYLE.fontTrackedDetail;
for (let i = 0; i < details.length; i++) {
ctx.fillText(String(details[i]), centerX, titleBaseline + (i + 1) * layout.lineH);
ctx.fillText(
String(details[i]),
centerX,
titleBaseline + (i + 1) * layout.lineH,
);
}
ctx.restore();
return placement.rect;
@@ -938,7 +1094,8 @@ export function paintDetectionCallout(ctx, callout, alpha = 1) {
ctx.globalAlpha = alpha;
ctx.fillStyle = callout.label;
ctx.font = callout.font;
if (callout.primary) ctx.fillText(callout.primary, callout.primaryX, callout.baseline);
if (callout.primary)
ctx.fillText(callout.primary, callout.primaryX, callout.baseline);
if (callout.micro) {
ctx.globalAlpha = alpha * 0.8;
ctx.font = callout.microFont;