Files
gods-eye-view/src/layers/transit/lifecycle.test.mjs
T
Cliff Flood 001043f74e fix(bikeshare): let one dead city feed fail without failing the layer
The layer recorded failure in a single `_error` string. Every city feed is
fetched independently, so one retired operator set that flag for the whole
layer — and the only code that cleared it ran on a successful *activation*,
which `runProximityCheck` skips for a city that already has rendered points.
A dead feed that re-entered camera range therefore set the flag on every
camera move and nothing ever cleared it: a permanent LOAD FAILED over a map
drawing every other city's stations.

Failure is now tracked per city in `_failedCityIds`, and `getStats()` reports
it only when the failures left nothing to draw. A city that recovers removes
its own entry rather than waiting for a neighbour to succeed.

This was reachable only because Buffalo sits 95 km from Toronto, inside the
100 km activation radius — the one dead city in the registry with a healthy
neighbour in range. The other four failed alone, where LOAD FAILED with an
empty map reads as correct, which is why the latch went unnoticed.

Probing all 33 registry systems found five dead. Three had moved and are
repointed: Reddy Bikeshare (Buffalo) to Social Bicycles, Tugo (Tucson) and
Trinity Metro (Fort Worth) to PBSC, all verified serving stations. Two are
dropped because the systems wound down rather than moved — Broward B-cycle
404s at every documented endpoint, and CoGo's replacement Lyft feed publishes
zero stations with status last reported in March 2025. `gbfs.cogobikeshare.com`
no longer resolves, so its allowlist entry goes too.

All 31 remaining systems were verified live, and every registry URL was
checked against the proxy host and path allowlists.
2026-10-13 18:55:20 -04:00

3987 lines
127 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import * as Cesium from 'cesium';
import { createTransitLayer } from './index.js';
import {
FEED_EVICT_AFTER_MS,
FEED_STALE_AFTER_MS,
MISSED_POLLS_TO_DROP,
SELECTED_CARD_REFRESH_MS,
TRANSIT_POLL_MS,
VEHICLE_MAX_FIX_AGE_MS,
} from './policy.js';
import { FLOOR_WARM_PER_POLL } from './policy.js';
import {
FUTURE_STAMP_TOLERANCE_MS,
ORDER_REJECTS_BEFORE_RESET,
} from './ingestion.js';
/**
* Fixes in these tests are AGE_MS old when they arrive, which makes the lag
* the feed earns equal to the lag a fifteen-second reporter earns on its own
* — one number, `LAG_MS`, and no slew — so the timelines below are exact.
*/
const AGE_MS = 15_000;
const LAG_MS = AGE_MS + TRANSIT_POLL_MS + 5_000;
/** A fix stamped AGE_MS before now, the way a live feed delivers them. */
const reported = () => (Date.now() - AGE_MS) / 1000;
const lat = (entry) =>
Cesium.Math.toDegrees(
Cesium.Cartographic.fromCartesian(entry.marker.position).latitude,
);
import {
cameraSensitivityClaims,
claimCameraSensitivity,
releaseCameraSensitivity,
} from '../../data/cameraSensitivity.js';
import {
_resetRenderGovernorForTest,
getRenderGovernorDiagnostics,
governorRequestRender,
holdContinuousRender,
installRenderGovernor,
releaseContinuousRender,
uninstallRenderGovernor,
} from '../../renderGovernor.js';
const BOSTON = { lat: 42.3601, lon: -71.0589 };
/** One decoded snapshot in the shape `/api/transit/vehicles/<id>` returns. */
function snapshot(
feedId,
name,
vehicles,
{ fetchedAt, feedTimestamp = null } = {},
) {
return {
feedId,
name,
fetchedAt,
feedTimestamp,
version: '2.0',
entityCount: vehicles.length,
truncated: false,
count: vehicles.length,
vehicles,
};
}
/** A vehicle record as the proxy emits it, with its timestamp source resolved. */
function vehicle(id, lat, lon, atSeconds, extra = {}) {
return {
id,
lat,
lon,
bearing: null,
speedMps: null,
timestamp: atSeconds,
timestampSource: 'vehicle',
routeId: '1',
tripId: null,
directionId: null,
label: null,
stopId: null,
status: null,
occupancy: null,
...extra,
};
}
/**
* A Transit layer wired to a stub scene and a scripted proxy.
*
* The render governor is the real one, so "does this layer keep the render loop
* awake?" is answered by the same code the app runs.
*/
/**
* A stand-in for the app's shared ground-floor system: warm cells, the coarse
* grid, and a lowest-neighbour read. `floorAt` decides what a cell resolves to
* when it is warmed; returning undefined models terrain that will not answer.
* @param {(lat: number, lon: number) => number|undefined} floorAt
*/
function groundStub(floorAt) {
const warm = new Map();
const warmed = [];
const coarseFloorCoord = (lat, lon) => ({
lat: Number(lat.toFixed(3)),
lon: Number(lon.toFixed(3)),
});
const key = (c) => `${c.lat},${c.lon}`;
const cachedGroundFloor = (lat, lon) => {
const value = warm.get(key(coarseFloorCoord(lat, lon)));
return value === undefined ? null : value;
};
return {
GROUND_FLOOR_LIFT_M: 1.5,
coarseFloorCoord,
cachedGroundFloor,
neighborFloorM(cell) {
let lowest = null;
let resolved = 0;
for (let dLat = -1; dLat <= 1; dLat += 1) {
for (let dLon = -1; dLon <= 1; dLon += 1) {
if (dLat === 0 && dLon === 0) continue;
const h = cachedGroundFloor(
cell.lat + dLat * 0.001,
cell.lon + dLon * 0.001,
);
if (h === null) continue;
resolved += 1;
if (lowest === null || h < lowest) lowest = h;
}
}
return resolved >= 3 ? lowest : null;
},
warmGroundFloor(points) {
warmed.push(...points);
for (const point of points) {
const value = floorAt(point.lat, point.lon);
if (Number.isFinite(value)) {
warm.set(key(coarseFloorCoord(point.lat, point.lon)), value);
}
}
},
_warm: warm,
_warmed: warmed,
_key: (lat, lon) => key(coarseFloorCoord(lat, lon)),
};
}
function harness(
t,
{
source,
altitude = 4_000,
floorAt = () => 12,
at = BOSTON,
view = {
west: at.lon - 0.1,
south: at.lat - 0.1,
east: at.lon + 0.1,
north: at.lat + 0.1,
},
} = {},
) {
const viewRect = { ...view };
t.mock.timers.enable({
apis: ['Date', 'setTimeout'],
now: 1_789_000_000_000,
});
for (const name of [
'HTMLCanvasElement',
'HTMLImageElement',
'ImageBitmap',
'OffscreenCanvas',
]) {
const prior = globalThis[name];
globalThis[name] = class {};
t.after(() => {
if (prior === undefined) delete globalThis[name];
else globalThis[name] = prior;
});
}
t.mock.method(performance, 'now', () => Date.now() - 1_789_000_000_000);
const originalFetch = globalThis.fetch;
const originalDocument = globalThis.document;
const primitives = [];
const sprites = new Map();
const pickOwners = new Map();
const overlaySources = new Map();
const credits = [];
/** @type {Map<string, (url: string) => {status:number, body?:object, headers?:object}>} */
const responders = new Map();
const requested = [];
globalThis.document = Object.assign(new EventTarget(), {
visibilityState: 'visible',
documentElement: { dataset: {} },
});
// The style and vision events arrive on `window`, which Node does not have.
const originalWindow = globalThis.window;
globalThis.window = new EventTarget();
const preRender = new Cesium.Event();
const cameraChanged = new Cesium.Event();
const viewer = {
camera: {
changed: cameraChanged,
percentageChanged: 0.5,
positionCartographic: {
height: altitude,
latitude: Cesium.Math.toRadians(at.lat),
longitude: Cesium.Math.toRadians(at.lon),
},
computeViewRectangle() {
return Cesium.Rectangle.fromDegrees(
viewRect.west,
viewRect.south,
viewRect.east,
viewRect.north,
);
},
},
scene: {
canvas: { addEventListener() {}, removeEventListener() {} },
globe: { ellipsoid: Cesium.Ellipsoid.WGS84 },
pick: () => undefined,
requestRenderMode: false,
maximumRenderTimeChange: 0,
requestRender() {},
preRender,
primitives: {
add(primitive) {
primitives.push(primitive);
return primitive;
},
remove(primitive) {
const index = primitives.indexOf(primitive);
if (index >= 0) primitives.splice(index, 1);
return index >= 0;
},
contains: (primitive) => primitives.includes(primitive),
raiseToTop() {},
},
},
};
globalThis.fetch = async (url) => {
requested.push(String(url));
const feedId = String(url).split('/').pop();
const responder = responders.get(feedId);
const answer = responder
? responder(String(url))
: { status: 504, body: { error: 'Transit feed unavailable' } };
return {
ok: answer.status >= 200 && answer.status < 300,
status: answer.status,
headers: { get: (name) => answer.headers?.[name.toLowerCase()] ?? null },
json: async () => answer.body,
};
};
const ground = groundStub(floorAt);
/** Every call the floor cycle makes to the rendered-mesh sampler. */
const meshCalls = [];
const layer = createTransitLayer({
source,
services: {
ground,
mesh: {
sampleMeshFloorCells(scene, points, options) {
meshCalls.push({ scene, points: [...points], options });
},
},
render: {
governorRequestRender,
holdContinuousRender,
releaseContinuousRender,
},
sprites: {
registerSpriteCollection: (id, collection) =>
sprites.set(id, collection),
unregisterSpriteCollection: (id) => sprites.delete(id),
restoreSpriteOrder() {},
},
picking: {
registerPickOwner: (id, predicate) => pickOwners.set(id, predicate),
unregisterPickOwner: (id) => pickOwners.delete(id),
},
overlays: {
setOverlayEntries: (id, entries) => overlaySources.set(id, entries),
setOverlaySourceVisible() {},
clearOverlaySource: (id) => overlaySources.delete(id),
},
credits: {
registerDynamicCredit: (_viewer, credit) => credits.push(credit?.key),
transitFeedCredit: (feed) => ({
key: `transit-${feed.id}`,
html: feed.name,
}),
},
},
});
installRenderGovernor(viewer);
layer.init(viewer);
t.after(() => {
try {
layer.destroy(viewer);
} catch {
/* a test may already have destroyed it */
}
uninstallRenderGovernor(viewer);
_resetRenderGovernorForTest();
globalThis.fetch = originalFetch;
if (originalDocument === undefined) delete globalThis.document;
else globalThis.document = originalDocument;
if (originalWindow === undefined) delete globalThis.window;
else globalThis.window = originalWindow;
});
return {
layer,
viewer,
primitives,
sprites,
pickOwners,
overlaySources,
credits,
requested,
serve(feedId, responder) {
responders.set(feedId, responder);
},
silence(feedId) {
responders.delete(feedId);
},
state: () => layer._transitStateForTest(),
vehicles: () => [...layer._transitStateForTest()._vehicles.values()],
frame() {
preRender.raiseEvent();
},
advance(ms) {
t.mock.timers.tick(ms);
},
/**
* Let wall time pass the way it does in the app: with frames along the
* way. The display clock only advances on frames, so a test that moves
* the clock without them models a tab in the background, not a viewer.
* @param {number} ms
* @param {number} [step]
*/
run(ms, step = 1_000) {
const until = Date.now() + ms;
while (Date.now() + step <= until) {
t.mock.timers.tick(step);
preRender.raiseEvent();
}
if (Date.now() < until) {
t.mock.timers.tick(until - Date.now());
preRender.raiseEvent();
}
},
holds: () => getRenderGovernorDiagnostics().holds,
ground,
meshCalls,
/**
* Run the floor cycle the way its timer would, then a frame. Ground
* resolution is a network round trip in the real app and deliberately
* lives off the render path, so a vehicle with no floor yet is not drawn
* until a re-read picks one up.
* @param {number} [rounds]
*/
settle(rounds = 4) {
for (let i = 0; i < rounds; i += 1) {
const parts = layer._transitPartsForTest();
parts.height.rereadFloors();
// The shown/hidden sweep is throttled in the app; running it here
// keeps the clock out of it, so a test about glide timing is not also
// a test about how long a sweep waits.
parts.rendering.refreshVisibility();
t.mock.timers.tick(16);
preRender.raiseEvent();
}
},
/** What the style manager dispatches when the user picks a preset. */
style(name) {
globalThis.window.dispatchEvent(
new CustomEvent('gev:style-change', { detail: { style: name } }),
);
},
/** What the style manager dispatches after every sync, cockpit included. */
vision(style, cockpit) {
globalThis.window.dispatchEvent(
new CustomEvent('gev:vision-change', { detail: { style, cockpit } }),
);
},
/** Move the camera's view rectangle, the way a real camera move would. */
lookAt(next) {
Object.assign(viewRect, next);
cameraChanged.raiseEvent();
},
};
}
test('a vehicle is played back a fixed lag behind its fixes, at 1x', async (t) => {
const app = harness(t);
const start = Date.now();
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, reported())],
{
fetchedAt: Date.now(),
},
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
const [entry] = app.vehicles();
assert.equal(entry.to.lat, 42.36);
assert.equal(
entry.sample.fromSeq,
entry.sample.toSeq,
'a brand-new vehicle holds at its only fix',
);
assert.equal(app.state()._moving.has(entry), false);
assert.ok(
Math.abs(Date.now() - entry.playT - LAG_MS) < 1,
'its display clock starts a full lag behind',
);
// A later poll gives it a new fix. Nothing is drawn yet: the display clock
// has not reached the first fix.
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.362, -71.06, reported())],
{
fetchedAt: Date.now(),
},
),
}));
await app.layer.update();
assert.ok(
entry.wakeTimer != null,
'one wake is scheduled at the first motion boundary',
);
app.frame();
assert.ok(Math.abs(lat(entry) - 42.36) < 1e-6, 'still at the first fix');
// The clock reaches the first fix LAG_MS after the wall time it was
// reported at; halfway along the fifteen seconds to the second fix the
// vehicle is drawn halfway between them.
const firstFixAt = start - AGE_MS;
app.run(firstFixAt + LAG_MS + TRANSIT_POLL_MS / 2 - Date.now());
assert.ok(Math.abs(lat(entry) - 42.361) < 1e-4, `halfway, got ${lat(entry)}`);
// Past the second fix it holds exactly there and stops being animated.
app.run(TRANSIT_POLL_MS);
assert.ok(Math.abs(lat(entry) - 42.362) < 1e-6);
assert.equal(app.state()._moving.has(entry), false, 'held, not animated');
});
test('a vehicle the feed stops reporting is removed, and its primitive with it', async (t) => {
const app = harness(t);
const now = Date.now();
const both = [
vehicle('bus-1', 42.36, -71.06, now / 1000),
vehicle('bus-2', 42.37, -71.07, now / 1000),
];
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', both, { fetchedAt: Date.now() }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
assert.equal(app.vehicles().length, 2);
const collection = app.sprites.get('transit');
assert.equal(collection.length, 2);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.361, -71.06, Date.now() / 1000)],
{
fetchedAt: Date.now(),
},
),
}));
// One missed poll is tolerated (feeds drop a vehicle for a beat), two is not.
app.advance(TRANSIT_POLL_MS);
await app.layer.update();
assert.equal(app.vehicles().length, 2);
app.advance(TRANSIT_POLL_MS);
await app.layer.update();
assert.deepEqual(
app.vehicles().map((entry) => entry.record.id),
['bus-1'],
);
assert.equal(
collection.length + app.sprites.get('transit-motion').length,
1,
'the removed vehicle released its point primitive',
);
});
test('one feed stale beside one fresh reads DEGRADED, not nominal', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, Date.now() / 1000)],
{
fetchedAt: Date.now(),
},
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
const state = app.state();
// Pretend a second feed is also in range and is being served from the proxy's
// stale cache — exactly the mixed case the old `every(stale)` call missed.
state._activeFeeds.set('ovapi-nl', {
id: 'ovapi-nl',
name: 'OVapi',
region: 'Netherlands',
});
state._feedStatus.set('ovapi-nl', {
count: 4,
lastUpdate: Date.now(),
error: null,
stale: true,
pollSeq: 1,
loading: false,
});
const mixed = app.layer.getStats();
assert.equal(mixed.stale, false, 'not everything is stale');
assert.equal(mixed.degraded, true, 'but the view is part live and part not');
// When every feed is stale the row says STALE outright.
state._feedStatus.get('mbta').stale = true;
const allStale = app.layer.getStats();
assert.equal(allStale.stale, true);
});
test('a feed that goes silent has its vehicles evicted instead of frozen on the globe', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, Date.now() / 1000)],
{
fetchedAt: Date.now(),
},
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
assert.equal(app.vehicles().length, 1);
// The proxy's stale window expires and the feed stops answering at all.
app.silence('mbta');
app.advance(FEED_EVICT_AFTER_MS + 1_000);
await app.layer.update();
assert.equal(app.vehicles().length, 0, 'the last fleet is not left standing');
assert.equal(app.sprites.get('transit').length, 0);
const stats = app.layer.getStats();
assert.equal(stats.count, 0);
assert.equal(stats.stale, true);
});
test('inside a fresh snapshot, only the vehicle fixes that are too old are dropped', async (t) => {
const app = harness(t);
const now = Date.now();
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
vehicle('fresh', 42.36, -71.06, Date.now() / 1000),
// A live feed really does carry these: a vehicle parked since this
// morning, still listed, still reporting its last known position.
vehicle(
'ancient',
42.37,
-71.07,
(Date.now() - 4 * 60 * 60 * 1000) / 1000,
),
// A clock ahead of ours is treated as current, never as negative age.
vehicle('future', 42.38, -71.08, (Date.now() + 60_000) / 1000),
],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
assert.deepEqual(
app
.vehicles()
.map((entry) => entry.record.id)
.sort(),
['fresh', 'future'],
);
// The same vehicles, unrefreshed, age out of the layer on a later sweep.
app.silence('mbta');
app.advance(VEHICLE_MAX_FIX_AGE_MS + 1_000);
await app.layer.update();
assert.equal(app.vehicles().length, 0);
assert.equal(now <= Date.now(), true);
});
test('a snapshot the proxy replayed from its own cache for six minutes is refused, not drawn', async (t) => {
const app = harness(t);
const fetchedAt = Date.now();
const vehicles = [
vehicle('bus-1', 42.36, -71.06, fetchedAt / 1000),
vehicle('bus-2', 42.37, -71.07, fetchedAt / 1000),
];
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', vehicles, { fetchedAt }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
assert.equal(app.vehicles().length, 2);
// The operator stops answering. The proxy keeps handing back the SAME
// snapshot every 15 s with STALE-ERROR; receiving bytes on a fresh cadence is
// not the same as the buses being where they say.
app.serve('mbta', () => ({
status: 200,
headers: { 'x-gev-cache': 'STALE-ERROR' },
body: snapshot('mbta', 'MBTA', vehicles, { fetchedAt }),
}));
for (let tick = 0; tick < 24; tick += 1) {
app.advance(TRANSIT_POLL_MS);
await app.layer.update();
}
assert.ok(
Date.now() - fetchedAt > FEED_EVICT_AFTER_MS,
'six minutes have passed',
);
assert.equal(
app.vehicles().length,
0,
'the replayed fleet is gone, not frozen',
);
assert.equal(app.sprites.get('transit').length, 0);
const stats = app.layer.getStats();
assert.equal(stats.count, 0);
assert.equal(stats.stale, true);
assert.match(stats.coverage, /\(\d+s old\)/);
assert.equal(
app.holds().includes('transit'),
false,
'and it holds no frames',
);
});
test('disable aborts in-flight work and a response that lands late is ignored', async (t) => {
const app = harness(t);
let release;
const pending = new Promise((resolve) => {
release = resolve;
});
for (const name of [
'HTMLCanvasElement',
'HTMLImageElement',
'ImageBitmap',
'OffscreenCanvas',
]) {
const prior = globalThis[name];
globalThis[name] = class {};
t.after(() => {
if (prior === undefined) delete globalThis[name];
else globalThis[name] = prior;
});
}
t.mock.method(performance, 'now', () => Date.now() - 1_789_000_000_000);
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url) => {
await pending;
return originalFetch(url);
};
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, Date.now() / 1000)],
{
fetchedAt: Date.now(),
},
),
}));
app.layer.enable(app.viewer);
const inFlight = app.layer.update();
app.layer.disable(app.viewer);
release();
await inFlight;
assert.equal(
app.vehicles().length,
0,
'a poll from the disabled generation never lands',
);
assert.equal(app.state()._inFlight.size, 0);
assert.equal(app.holds().includes('transit'), false);
// Re-enabling starts a clean generation and does take data.
globalThis.fetch = originalFetch;
app.layer.enable(app.viewer);
await app.layer.update();
assert.equal(app.vehicles().length, 1);
});
test('a vehicle waits for its floor rather than standing on the ellipsoid', async (t) => {
// The shared ground-floor system answers from the rendered mesh cell or the
// real DEM cell; until one of them is warm there is no honest height to draw
// at. Ellipsoid zero is not street level, and drawing there and dropping
// afterwards is exactly the floating the owner reported.
let terrainReady = false;
const app = harness(t, { floorAt: () => (terrainReady ? 47 : undefined) });
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, Date.now() / 1000)],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
const entry = app.vehicles()[0];
assert.equal(entry.heightResolved, false);
app.settle();
assert.equal(entry.heightPending, true, 'it has nothing to stand on');
assert.equal(entry.marker.show, false, 'so it is not drawn');
// Terrain arrives; the next re-read picks the cell up and the vehicle
// appears, standing on it.
terrainReady = true;
app.settle();
assert.equal(entry.heightResolved, true);
assert.equal(entry.heightM, 47 + 1.5, 'the floor plus the shared lift');
assert.equal(entry.marker.show, true);
// Driving into a new cell asks that cell rather than keeping the one it was
// born in.
app.advance(TRANSIT_POLL_MS);
const cellBefore = entry.heightCell;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.362, -71.06, Date.now() / 1000)],
{ fetchedAt: Date.now() },
),
}));
await app.layer.update();
assert.notEqual(app.vehicles()[0].heightCell, cellBefore);
});
test('the render hold follows real motion, and is released when everything settles', async (t) => {
const app = harness(t);
const start = Date.now();
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, reported())],
{
fetchedAt: Date.now(),
},
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
// The first poll draws each vehicle at its fix; there is nothing to play,
// and ground resolution runs off the render path, so the loop is never held.
assert.equal(
app.holds().includes('transit'),
false,
'a fleet that is not moving does not hold the loop',
);
app.frame();
assert.equal(app.holds().includes('transit'), false);
assert.equal(app.viewer.scene.requestRenderMode, true);
// A second fix IS motion to come: the vehicle is armed and the loop held.
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.362, -71.06, reported())],
{
fetchedAt: Date.now(),
},
),
}));
await app.layer.update();
const firstFixAt = start - AGE_MS;
app.run(firstFixAt + LAG_MS + TRANSIT_POLL_MS / 2 - Date.now());
assert.ok(
app.holds().includes('transit'),
'a moving vehicle keeps the loop awake',
);
assert.equal(app.viewer.scene.requestRenderMode, false);
// Past the second fix it holds and the loop is released again.
app.run(TRANSIT_POLL_MS);
assert.equal(
app.holds().includes('transit'),
false,
'a settled fleet does not hold the GPU',
);
assert.equal(app.viewer.scene.requestRenderMode, true);
});
test('disable hands back the camera sensitivity it borrowed', async (t) => {
const app = harness(t);
assert.equal(app.viewer.camera.percentageChanged, 0.5);
app.layer.enable(app.viewer);
assert.equal(
app.viewer.camera.percentageChanged,
0.05,
'the layer needs a finer camera trigger',
);
app.layer.disable(app.viewer);
assert.equal(app.viewer.camera.percentageChanged, 0.5, 'and gives it back');
});
test('disable leaves the fine camera trigger another layer asked for', async (t) => {
// The sequence that broke it: two layers ask for the SAME sensitivity, so
// the value on the camera cannot say who owns it. Transit used to compare
// the number, find its own 0.05 still there, and hand the coarse default
// back while Bikeshare was still driving off the fine one.
const app = harness(t);
assert.equal(app.viewer.camera.percentageChanged, 0.5);
app.layer.enable(app.viewer);
assert.equal(app.viewer.camera.percentageChanged, 0.05);
claimCameraSensitivity(app.viewer.camera, 'bikeshare', 0.05);
app.layer.disable(app.viewer);
assert.equal(
app.viewer.camera.percentageChanged,
0.05,
'bikeshare is still enabled, so the fine trigger stays',
);
assert.deepEqual(cameraSensitivityClaims(app.viewer.camera), ['bikeshare']);
releaseCameraSensitivity(app.viewer.camera, 'bikeshare');
assert.equal(
app.viewer.camera.percentageChanged,
0.5,
'and the original returns once the last claim is gone',
);
assert.deepEqual(cameraSensitivityClaims(app.viewer.camera), []);
});
test('a finer claim wins while it stands, and the coarser one survives it', async (t) => {
const app = harness(t);
app.layer.enable(app.viewer);
claimCameraSensitivity(app.viewer.camera, 'traffic', 0.01);
assert.equal(app.viewer.camera.percentageChanged, 0.01);
releaseCameraSensitivity(app.viewer.camera, 'traffic');
assert.equal(
app.viewer.camera.percentageChanged,
0.05,
'transit still needs its own trigger',
);
app.layer.disable(app.viewer);
assert.equal(app.viewer.camera.percentageChanged, 0.5);
});
test('destroy releases every shared registration this layer took', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, Date.now() / 1000)],
{
fetchedAt: Date.now(),
},
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
assert.ok(app.sprites.has('transit'));
assert.ok(app.pickOwners.has('transit'));
assert.equal(app.primitives.length, 2);
app.layer.destroy(app.viewer);
assert.equal(
app.sprites.has('transit'),
false,
'the sprite collection is unregistered',
);
assert.equal(
app.pickOwners.has('transit'),
false,
'the pick owner is released',
);
assert.equal(
app.primitives.length,
0,
'the primitive collection leaves the scene',
);
assert.equal(app.holds().includes('transit'), false);
assert.equal(app.state()._viewer, null);
assert.equal(app.state()._dataManager, null);
});
test('two layers built from the factory share no state', async (t) => {
const app = harness(t);
const second = createTransitLayer({
services: {
ground: groundStub(() => 12),
render: {
governorRequestRender,
holdContinuousRender,
releaseContinuousRender,
},
sprites: {
registerSpriteCollection() {},
unregisterSpriteCollection() {},
restoreSpriteOrder() {},
},
picking: { registerPickOwner() {}, unregisterPickOwner() {} },
overlays: {
setOverlayEntries() {},
setOverlaySourceVisible() {},
clearOverlaySource() {},
},
credits: {
registerDynamicCredit() {},
transitFeedCredit: (feed) => ({ key: feed.id, html: feed.name }),
},
},
});
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, Date.now() / 1000)],
{
fetchedAt: Date.now(),
},
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
assert.equal(app.vehicles().length, 1);
assert.equal(second._transitStateForTest()._vehicles.size, 0);
assert.notEqual(second._transitStateForTest(), app.state());
assert.equal(second.getStats().count, 0);
});
test('a proxy cooldown reaches the row as a time, not just a shrug', async (t) => {
const app = harness(t);
// The proxy answers 503 with how long it intends to leave the operator alone.
app.serve('mbta', () => ({
status: 503,
body: { error: 'Transit feed unavailable', feedId: 'mbta', retryInSec: 60 },
}));
app.layer.enable(app.viewer);
await app.layer.update();
const stats = app.layer.getStats();
assert.equal(stats.count, 0);
assert.match(String(stats.error), /MBTA/);
assert.equal(stats.retryInSec, 60);
// A successful poll clears both the error and the countdown.
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, Date.now() / 1000)],
{
fetchedAt: Date.now(),
},
),
}));
app.advance(TRANSIT_POLL_MS);
await app.layer.update();
const recovered = app.layer.getStats();
assert.equal(recovered.error, null);
assert.equal(recovered.retryInSec, undefined);
});
test('the coverage text ages a quiet feed by the same rule the row state uses', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, Date.now() / 1000)],
{
fetchedAt: Date.now(),
},
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
assert.equal(app.layer.getStats().coverage, 'MBTA 1');
// Past the stale threshold the text names the age, and the row agrees.
app.silence('mbta');
app.advance(FEED_STALE_AFTER_MS + 10_000);
const stale = app.layer.getStats();
assert.match(stale.coverage, /^MBTA 1 \(\d+s old\)$/);
assert.equal(stale.stale, true);
});
test('a parked fleet that keeps reporting the same position stops asking for frames', async (t) => {
const app = harness(t);
// A depot at night: the feed answers on time, every 15 s, with coordinates
// that do not change. Each of those is a real observation — the bus is
// still there — but there is nothing to draw, and drawing it anyway kept
// the render loop awake all night.
const parked = () => [
vehicle('bus-1', 42.36, -71.06, reported()),
vehicle('bus-2', 42.37, -71.07, reported()),
];
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', parked(), { fetchedAt: Date.now() }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.frame();
assert.equal(
app.holds().includes('transit'),
false,
'nothing moved on the first poll',
);
for (let poll = 0; poll < 4; poll += 1) {
app.advance(TRANSIT_POLL_MS);
await app.layer.update();
app.frame();
assert.equal(
app.holds().includes('transit'),
false,
`identical poll ${poll + 1} must not count as motion`,
);
assert.equal(app.state()._moving.size, 0);
}
assert.equal(app.viewer.scene.requestRenderMode, true);
assert.equal(app.vehicles().length, 2, 'and the fleet is still on screen');
// One vehicle finally moves: that IS work, and the hold comes back.
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
vehicle('bus-1', 42.3605, -71.06, reported()),
vehicle('bus-2', 42.37, -71.07, reported()),
],
{ fetchedAt: Date.now() },
),
}));
await app.layer.update();
assert.equal(
app.holds().includes('transit'),
false,
'departure remains buffered until its boundary',
);
app.advance(6000);
app.frame();
assert.ok(
app.holds().includes('transit'),
'departure wakes the loop at its boundary',
);
app.advance(TRANSIT_POLL_MS / 2);
app.frame();
assert.ok(app.holds().includes('transit'));
assert.equal(
app.state()._moving.size,
1,
'only the vehicle that actually moved is animating',
);
});
test('the last vehicle leaving mid-segment takes the render hold with it', async (t) => {
const app = harness(t);
const fetchedAt = Date.now();
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.36, -71.06, reported())],
{
fetchedAt: Date.now(),
},
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
// Second poll: the vehicle has somewhere to go, so the hold is taken.
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus-1', 42.362, -71.06, reported())],
{
fetchedAt: Date.now(),
},
),
}));
await app.layer.update();
app.advance(TRANSIT_POLL_MS / 3);
app.frame();
assert.ok(app.holds().includes('transit'), 'in motion, so the loop is held');
assert.equal(app.state()._moving.has(app.vehicles()[0]), true);
// The feed dies. The sweep removes the last vehicle while it is still
// in motion, and the poll that discovers this fails — so no frame follows to
// notice the fleet is gone. The hold has to be released by the eviction.
app.silence('mbta');
app.advance(FEED_EVICT_AFTER_MS + 1_000);
await app.layer.update();
assert.equal(app.vehicles().length, 0);
assert.equal(
app.holds().includes('transit'),
false,
'no hold may outlive the fleet that justified it',
);
assert.equal(app.viewer.scene.requestRenderMode, true);
assert.ok(Date.now() - fetchedAt > FEED_EVICT_AFTER_MS);
});
test('only the vehicles near the view are animated frame by frame', async (t) => {
const app = harness(t);
const near = vehicle('near', 42.36, -71.06, reported());
// Far enough away to be outside any plausible inflation of the harness view
// rectangle, but well inside the feed's coverage circle.
const far = vehicle('far', 42.62, -71.44, reported());
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [near, far], { fetchedAt: Date.now() }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.frame();
// Both move on the next poll; only the one in view is armed at all.
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
vehicle('near', 42.3625, -71.06, reported()),
vehicle('far', 42.6225, -71.44, reported()),
],
{ fetchedAt: Date.now() },
),
}));
await app.layer.update();
const byId = new Map(app.vehicles().map((entry) => [entry.record.id, entry]));
app.advance(6000);
app.frame();
assert.equal(
app.state()._moving.size,
1,
'a poll arms only the vehicle in view',
);
assert.ok(app.state()._moving.has(byId.get('near')));
assert.equal(
byId.get('far').marker.show,
false,
'the off-screen vehicle is hidden',
);
app.advance(TRANSIT_POLL_MS / 4);
app.frame();
assert.equal(
app.state()._moving.has(byId.get('far')),
false,
'and is never animated',
);
assert.ok(
app.state()._moving.has(byId.get('near')),
'the one in view keeps playing',
);
// When the view reaches it, its clock is re-synced and the frame pass places
// it: nothing is owed for motion nobody watched.
app.lookAt({ west: -71.54, south: 42.52, east: -71.34, north: 42.72 });
// Two floor passes: it was never warmed while hidden, and a warmed cell
// answers on the pass after the one that asked.
app.settle(2);
app.advance(250);
app.frame();
assert.notEqual(byId.get('far').marker.show, false, 'now shown');
assert.ok(
Math.abs(lat(byId.get('far')) - 42.62) < 1e-3,
'placed on its history',
);
});
test('a wide oblique view does not hide the fleet that is plainly in it', async (t) => {
// Codex's reproduction: a high oblique camera over Europe sees roughly
// -78°..88° of longitude. Padding each endpoint on its own wrapped that span
// into a five-degree slice of the Pacific, so every Dutch vehicle failed the
// view gate — hidden, unanimated, and invisible to DETECT — while filling
// the screen.
const app = harness(t, {
at: { lat: 52.09, lon: 5.12 },
altitude: 1_500_000,
view: { west: -78.084, south: 20, east: 88.084, north: 84 },
});
const now = Date.now();
const utrecht = vehicle('nl-1', 52.09, 5.12, now / 1000);
const rotterdam = vehicle('nl-2', 51.92, 4.48, now / 1000);
app.serve('ovapi-nl', () => ({
status: 200,
body: snapshot('ovapi-nl', 'OVapi', [utrecht, rotterdam], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.advance(250);
app.frame();
const shown = app.vehicles().filter((entry) => entry.marker?.show !== false);
assert.equal(shown.length, 2, 'both vehicles are in the view the camera has');
assert.equal(
app.layer.getDetectableObjects({ maxCount: 50, seed: 0 }).length,
2,
'and DETECT can see them',
);
});
test('a lone visible vehicle is offered to DETECT out of a large hidden fleet', async (t) => {
// The stride used to run over the WHOLE fleet and discard the hidden ones
// afterwards, so one visible bus among thousands could fall outside the
// sample and go unlabelled while the overlay had capacity to spare.
const app = harness(t);
const now = Date.now();
const vehicles = [];
for (let i = 0; i < 400; i += 1) {
// Far outside the harness view rectangle, inside MBTA coverage.
vehicles.push(vehicle(`away-${i}`, 42.6 + i * 0.001, -71.45, now / 1000));
// Deliberately NOT first in the map: an ordinal stride happens to pick
// index zero, which would hide the fault rather than show it.
if (i === 0) vehicles.push(vehicle('visible', 42.361, -71.058, now / 1000));
}
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', vehicles, { fetchedAt: Date.now() }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const contacts = app.layer.getDetectableObjects({ maxCount: 8, seed: 0 });
assert.equal(contacts.length, 1, 'only the visible one is a candidate');
assert.equal(contacts[0].sourceId, 'mbta:visible');
});
test('removing one vehicle does not hand every label to its neighbour', async (t) => {
// Striding by position meant that one bus leaving at the front renumbered
// everyone behind it: the sampled set flipped from even indices to odd, and
// not one of the previous identities survived.
const app = harness(t, {
view: { west: -71.3, south: 42.2, east: -70.8, north: 42.5 },
});
const now = Date.now();
const fleet = [];
for (let i = 0; i < 40; i += 1) {
fleet.push(vehicle(`bus-${i}`, 42.35 + i * 0.001, -71.06, now / 1000));
}
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', fleet, { fetchedAt: Date.now() }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const before = app.layer
.getDetectableObjects({ maxCount: 10, seed: 0 })
.map((contact) => contact.sourceId);
assert.equal(before.length, 10);
// The first vehicle stops being reported; everyone else is unchanged. It
// takes MISSED_POLLS_TO_DROP polls of silence for it to actually leave.
for (let poll = 0; poll < MISSED_POLLS_TO_DROP + 1; poll += 1) {
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
fleet.slice(1).map((v) => ({ ...v, timestamp: Date.now() / 1000 })),
{ fetchedAt: Date.now() },
),
}));
await app.layer.update();
app.frame();
}
assert.equal(
app.vehicles().some((entry) => entry.record.id === 'bus-0'),
false,
'the vehicle really did leave',
);
const after = new Set(
app.layer
.getDetectableObjects({ maxCount: 10, seed: 0 })
.map((contact) => contact.sourceId),
);
const survived = before.filter(
(id) => id !== 'mbta:bus-0' && after.has(id),
).length;
const expected = before.filter((id) => id !== 'mbta:bus-0').length;
assert.equal(
survived,
expected,
'every still-present labelled vehicle keeps its label',
);
});
test('a vehicle inside the view drives out of it instead of vanishing', async (t) => {
// A fix placing the DESTINATION just past the padded edge used to hide the
// marker on the spot — while the vehicle was still drawn well inside the
// view — and throw its glide away in the same frame.
const app = harness(t, {
view: { west: -71.062, south: 42.3585, east: -71.058, north: 42.3605 },
});
const now = Date.now();
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('b', 42.36, -71.06, now / 1000)], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
assert.notEqual(entry.marker.show, false, 'it starts on screen');
// Next fix is beyond the inflated rectangle; the vehicle is still drawn
// inside it.
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('b', 42.3625, -71.06, Date.now() / 1000)],
{ fetchedAt: Date.now() },
),
}));
await app.layer.update();
app.frame();
assert.notEqual(
entry.marker.show,
false,
'it is still on screen, because it still is',
);
assert.ok(
entry.wakeTimer != null || app.state()._moving.has(entry),
'and it has a departure wake rather than teleporting',
);
});
test('the selected card lands where the vehicle stopped, not short of it', async (t) => {
// The card is refreshed on a 250 ms throttle. The last tick before a
// segment ends left it a few metres behind, and then the render hold was
// released with no frame scheduled to close the gap.
const app = harness(t);
const start = Date.now();
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('b', 42.36, -71.06, reported())], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
// Its floor resolves, so the marker is shown: a hidden vehicle has no card.
app.settle();
const entry = app.vehicles()[0];
app.layer._transitPartsForTest().selection.selectVehicle(entry.key);
app.advance(TRANSIT_POLL_MS);
// Stamped exactly one poll after the first fix, so the vehicle's own lag
// equals the feed's and the clock runs at exactly 1x through the segment.
const secondFixS = (entry.fixes[0].t + TRANSIT_POLL_MS) / 1000;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('b', 42.3625, -71.0625, secondFixS)],
{
fetchedAt: Date.now(),
},
),
}));
await app.layer.update();
// Codex's sequence: a card refresh 200 ms before the segment completes, then
// the settling frame — which falls INSIDE the 250 ms card throttle, so
// nothing but a forced refresh can close the gap.
// The segment ends when the clock reaches the second fix, a lag after it.
const segmentEndsAt = entry.fixes[1].t + LAG_MS;
app.run(segmentEndsAt - 200 - Date.now());
// The settling frame — whichever frame that turns out to be — falls INSIDE
// the card throttle, so only the forced refresh can close the gap.
let frames = 0;
while (app.state()._moving.has(entry) && frames < 60) {
app.advance(16);
app.frame();
frames += 1;
}
assert.equal(app.state()._moving.has(entry), false, 'settled');
assert.equal(
app.state()._selectedCardAt,
Date.now(),
'the settling frame forced a card refresh, throttle or no throttle',
);
const card = app.overlaySources.get('transit-selected')?.[0];
assert.ok(card, 'the card is still on screen');
const anchor =
typeof card.position === 'function' ? card.position() : card.position;
const drift = Cesium.Cartesian3.distance(anchor, entry.marker.position);
assert.ok(
drift < 0.5,
`the card sits on the vehicle it follows (was ${drift.toFixed(2)} m behind)`,
);
});
test('a parked fleet over unsampled ground off screen stops asking for frames', async (t) => {
// 200 stationary vehicles in distinct off-screen cells whose terrain never
// resolves used to keep the height queue non-empty for ever, and a non-empty
// queue is a held render loop over a view with nothing moving in it.
const app = harness(t, { floorAt: () => undefined });
const now = Date.now();
const parked = [];
for (let i = 0; i < 200; i += 1) {
parked.push(vehicle(`p-${i}`, 42.5 + i * 0.003, -71.4, now / 1000));
}
const serve = () => ({
status: 200,
body: snapshot('mbta', 'MBTA', parked, { fetchedAt: Date.now() }),
});
app.serve('mbta', serve);
app.layer.enable(app.viewer);
await app.layer.update();
app.frame();
// Eight polls — two minutes — with nothing moving and nothing in view.
for (let poll = 0; poll < 8; poll += 1) {
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', serve);
await app.layer.update();
for (let f = 0; f < 4; f += 1) {
app.advance(100);
app.frame();
}
}
assert.equal(
app.ground._warmed.length,
0,
'no ground was asked for on behalf of vehicles nobody can see',
);
assert.equal(
app.holds().includes('transit'),
false,
'and the render loop is not held over an empty view',
);
});
test('a height that arrives from a neighbour lifts a stationary vehicle too', async (t) => {
// A cache hit updated heightM but the billboard endpoints only refreshed on
// a lat/lon change, so a bus that had not moved kept drawing at ellipsoid
// height beside a neighbour standing on the street.
const app = harness(t, { floorAt: () => 125 });
const now = Date.now();
const parked = vehicle('still', 42.36, -71.06, now / 1000);
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [parked], { fetchedAt: Date.now() }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
const entry = app.vehicles()[0];
// Pretend the floor landed for this cell while the vehicle was absent: the
// shared cache knows the height, the entry does not.
entry.heightM = 0;
entry.heightResolved = false;
entry.heightPending = false;
app.ground._warm.set(app.ground._key(42.36, -71.06), 125);
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('still', 42.36, -71.06, Date.now() / 1000)],
{ fetchedAt: Date.now() },
),
}));
await app.layer.update();
app.frame();
const carto = Cesium.Cartographic.fromCartesian(entry.marker.position);
assert.ok(
Math.abs(carto.height - 126.5) < 1,
`the vehicle stands on the ground its cell reported (drew at ${carto.height.toFixed(1)} m)`,
);
});
test('a feed answering 304 every poll stays healthy instead of ageing out', async (t) => {
// The proxy keeps serving the body it holds, whose fetch time stops
// advancing. Reading that as "when did we last hear from them" marked a feed
// whose every request succeeded as DEGRADED at 90 s and emptied it at five
// minutes.
const app = harness(t);
const firstFetch = Date.now();
const body = snapshot(
'mbta',
'MBTA',
[vehicle('b', 42.36, -71.06, firstFetch / 1000)],
{ fetchedAt: firstFetch },
);
app.serve('mbta', () => ({
status: 200,
body,
headers: { 'x-transit-contact': String(Date.now()) },
}));
app.layer.enable(app.viewer);
await app.layer.update();
assert.equal(app.vehicles().length, 1);
// Twenty-four polls — six minutes — of successful revalidation.
for (let poll = 0; poll < 24; poll += 1) {
app.advance(16_000);
app.serve('mbta', () => ({
status: 200,
body,
headers: { 'x-transit-contact': String(Date.now()) },
}));
await app.layer.update();
}
const stats = app.layer.getStats();
assert.equal(app.vehicles().length, 1, 'the vehicle is still on the globe');
assert.equal(stats.stale, false, 'and the feed does not read as gone quiet');
});
test('a feed that has genuinely stopped answering still ages out', async (t) => {
// The other half of the same rule: a contact time that does NOT advance is
// still silence, and silence must empty the map.
const app = harness(t);
const firstFetch = Date.now();
const body = snapshot(
'mbta',
'MBTA',
[vehicle('b', 42.36, -71.06, firstFetch / 1000)],
{ fetchedAt: firstFetch },
);
app.serve('mbta', () => ({
status: 200,
body,
headers: { 'x-transit-contact': String(firstFetch) },
}));
app.layer.enable(app.viewer);
await app.layer.update();
assert.equal(app.vehicles().length, 1);
app.advance(FEED_EVICT_AFTER_MS + 30_000);
app.serve('mbta', () => ({
status: 200,
body,
headers: { 'x-transit-contact': String(firstFetch) },
}));
await app.layer.update();
assert.equal(app.vehicles().length, 0, 'a silent feed empties the globe');
});
test('a vehicle with no surface yet waits instead of floating', async (t) => {
// Ellipsoid zero is not street level — in Boston it is about thirty metres
// underground — so a marker drawn at +3 m while its sample is pending sits
// above its final height and then drops. The owner saw that as floating.
let allowSamples = false;
const app = harness(t, {
floorAt: () => (allowSamples ? 30 : undefined),
});
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('b', 42.36, -71.06, Date.now() / 1000)],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.frame();
const entry = app.vehicles()[0];
assert.equal(
entry.heightPending,
true,
'it knows it has nothing to stand on',
);
assert.equal(entry.marker.show, false, 'so it is not drawn');
allowSamples = true;
app.settle();
assert.equal(entry.heightPending, false);
assert.equal(entry.marker.show, true, 'and appears once the ground answers');
const carto = Cesium.Cartographic.fromCartesian(entry.marker.position);
assert.ok(
Math.abs(carto.height - 31.5) < 1,
`standing on its floor plus the shared lift, got ${carto.height.toFixed(1)} m`,
);
});
test('a new vehicle borrows the lowest surface resolved nearby', async (t) => {
// The first visible frame uses the best prior available rather than a flat
// +3 m, and the prior is biased LOW so a marker can only rise onto its own
// surface, never appear above it and fall.
const app = harness(t, { floorAt: () => 40 });
const early = [
vehicle('a', 42.36, -71.06, Date.now() / 1000),
vehicle('b', 42.361, -71.06, Date.now() / 1000),
vehicle('c', 42.36, -71.061, Date.now() / 1000),
vehicle('d', 42.361, -71.061, Date.now() / 1000),
];
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', early, { fetchedAt: Date.now() }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
// A second vehicle appears a block away, in a cell of its own whose
// neighbours are warm. The shared floor wants at least three resolved
// neighbours before it will speak for a cold cell.
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
...early.map((v) => ({ ...v, timestamp: Date.now() / 1000 })),
// Its own cell is cold; the three around it are warm.
vehicle('second', 42.3605, -71.0605, Date.now() / 1000),
],
{ fetchedAt: Date.now() },
),
}));
await app.layer.update();
const second = app.vehicles().find((e) => e.record.id === 'second');
assert.equal(
second.heightPending,
false,
'it has ground to stand on at once',
);
assert.equal(second.heightM, 41.5, 'borrowed from the neighbourhood');
app.advance(250);
app.frame();
assert.equal(second.marker.show, true);
});
test('only the vehicles in view have their ground warmed', async (t) => {
// Warming is a network round trip through the shared resolver. A parked
// metropolitan fleet off screen used to keep a queue — and with it the
// render loop — alive indefinitely over an empty view.
const app = harness(t, { floorAt: () => undefined });
const warmed = app.ground._warmed;
const fleet = [vehicle('near', 42.36, -71.06, Date.now() / 1000)];
for (let i = 0; i < 50; i += 1) {
fleet.push(
vehicle(`far-${i}`, 42.6 + i * 0.002, -71.45, Date.now() / 1000),
);
}
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', fleet, { fetchedAt: Date.now() }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
assert.ok(warmed.length > 0, 'the one in view is warmed');
const offScreen = warmed.filter((point) => point.lat > 42.5);
assert.equal(offScreen.length, 0, 'and nothing off screen is');
assert.equal(
app.holds().includes('transit'),
false,
'a fleet waiting for ground is not a fleet worth burning frames on',
);
});
test('a segment climbs from the ground it left to the ground it reaches', async (t) => {
// Rebuilding both ends of a segment at the destination's altitude lifted the
// vehicle onto the new height on the FIRST frame: it flew up, then
// travelled. With the display running a lag behind, the trap is subtler —
// the floor is asked for the NEWEST fix while an older one is still being
// drawn — so each fix carries its own floor.
const app = harness(t, { floorAt: (lat) => (lat > 42.3615 ? 100 : 10) });
const start = Date.now();
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('b', 42.36, -71.06, reported())], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
assert.ok(Math.abs(entry.heightM - 11.5) < 1, 'it starts on the low ground');
// The destination's surface is already known — a hundred metres uphill — so
// the poll resolves it immediately, on the fix that stands there.
app.ground._warm.set(app.ground._key(42.362, -71.06), 100);
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('b', 42.362, -71.06, reported())], {
fetchedAt: Date.now(),
}),
}));
await app.layer.update();
assert.ok(Math.abs(entry.heightM - 101.5) < 1, 'the destination is uphill');
const height = () =>
Cesium.Cartographic.fromCartesian(entry.marker.position).height;
app.run(400);
assert.ok(
height() < 30,
`still on the ground it is drawn on, got ${height().toFixed(1)} m`,
);
// Halfway along the segment it is halfway up; at the end, on the high ground.
const firstFixAt = start - AGE_MS;
app.run(firstFixAt + LAG_MS + TRANSIT_POLL_MS / 2 - Date.now());
assert.ok(
Math.abs(height() - 11.5) < 3,
`at the corridor floor, got ${height().toFixed(1)} m`,
);
app.run(TRANSIT_POLL_MS);
assert.ok(
Math.abs(height() - 101.5) < 1.5,
`and arrives on the high ground, got ${height().toFixed(1)} m`,
);
});
test('a lone impossible fix leaves the displayed vehicle and health untouched', async (t) => {
const app = harness(t, { floorAt: () => 5 });
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('b', 42.36, -71.06, reported())], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
// Eleven kilometres in one poll: 2,600 km/h.
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('b', 42.46, -71.06, reported())], {
fetchedAt: Date.now(),
}),
}));
await app.layer.update();
assert.equal(
entry.sample.fromSeq,
entry.sample.toSeq,
'no segment was started',
);
assert.equal(app.state()._moving.has(entry), false);
assert.equal(entry.fixes.length, 1, 'the accepted history is preserved');
assert.equal(entry.resets, 0, 'one bad fix cannot reset playback');
assert.deepEqual(
[entry.to.lat, entry.to.lon],
[42.36, -71.06],
'it remains at its accepted position',
);
assert.ok(Math.abs(lat(entry) - 42.36) < 1e-6);
});
test('the detection metric says what the display is doing, not what the feed claims', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
vehicle('m', 42.36, -71.06, Date.now() / 1000, {
routeId: 'Green-C',
status: 'STOPPED_AT',
stopId: '49001',
}),
],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
// It moves; the feed still says STOPPED_AT.
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
vehicle('m', 42.362, -71.06, Date.now() / 1000, {
routeId: 'Green-C',
status: 'STOPPED_AT',
stopId: '49001',
}),
],
{ fetchedAt: Date.now() },
),
}));
await app.layer.update();
app.advance(2_000);
app.frame();
const [contact] = app.layer.getDetectableObjects({ maxCount: 5, seed: 0 });
assert.ok(contact, 'the vehicle is a contact');
assert.equal(
contact.metric.includes('STOPPED'),
false,
`a moving vehicle is not stopped, got "${contact.metric}"`,
);
assert.equal(contact.id, 'GREEN-C');
assert.ok(
contact.klass.includes('METRO') || contact.klass.includes('TRAM'),
`the class carries the mode, got "${contact.klass}"`,
);
});
test('the card keeps the operator claim as a report and says the mode plainly', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
vehicle('m', 42.36, -71.06, Date.now() / 1000, {
routeId: 'Red',
status: 'STOPPED_AT',
stopId: '49001',
}),
],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
app.layer._transitPartsForTest().selection.selectVehicle(entry.key);
const card = app.overlaySources.get('transit-selected')?.[0];
assert.ok(card, 'a card is shown');
const text = card.details.join(' | ');
assert.match(text, /Subway/, 'the mode is said in words');
assert.match(text, /Last report: stopped at stop 49001/);
assert.match(
text,
/Shown at street level/,
'and the surface caveat is stated',
);
});
test('a vehicle reporting every 30 s moves continuously at its own speed', async (t) => {
// Measured on the live MBTA feed: half the fleet updates every thirty
// seconds while the browser polls every fifteen. Gliding over a fixed poll
// interval drew that half-minute of travel in fifteen seconds and then left
// the fleet perfectly still for the other fifteen — 46 vehicles moving, then
// zero, then 43, then zero. Played back a lag behind, the segment takes the
// thirty seconds the fixes describe and runs straight into the next one.
const app = harness(t, { floorAt: () => 5 });
const start = Date.now();
const firstFixAt = start - AGE_MS;
let fixAt = firstFixAt;
let north = 42.36;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('b', north, -71.06, fixAt / 1000)],
{
fetchedAt: Date.now(),
},
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
// Polls at 15 s (a repeat), 30 s (moved 0.0025°), 45 s (repeat), 60 s (moved)…
const samples = [];
// Each poll lands at the end of the previous fifteen frames, the way the
// manager tick does in the app: frames run continuously between polls.
for (let poll = 1; poll <= 6; poll += 1) {
if (poll % 2 === 0) {
fixAt += 2 * TRANSIT_POLL_MS;
north += 0.0025;
}
await app.layer.update();
for (let f = 0; f < 15; f += 1) {
app.advance(1_000);
app.frame();
samples.push({ at: Date.now(), lat: lat(entry), playT: entry.playT });
}
}
// A thirty-second reporter under a thirty-five-second prior holds once,
// before its first segment: nobody knows its cadence until the second fix
// arrives, and the hold is what earns it the longer lag. From the first
// frame it moves, the clock runs at exactly one second per second with no
// dead interval, and the drawn speed is the fix speed.
const firstMove = samples.findIndex(
(s, i) => i > 0 && s.lat !== samples[i - 1].lat,
);
assert.ok(firstMove > 0, 'it starts moving');
const playing = samples.filter(
(s, i) => i >= firstMove && s.playT < entry.fixes.at(-1).t,
);
assert.ok(
playing.length >= 40,
`long enough to judge (${playing.length} samples)`,
);
for (let i = 1; i < playing.length; i += 1) {
const dPlay = playing[i].playT - playing[i - 1].playT;
const dWall = playing[i].at - playing[i - 1].at;
// 1x, or the 0.95x it runs at while banking the longer lag it has just
// learned it needs — never a freeze, never a rush.
assert.ok(
dPlay <= dWall + 1 && dPlay >= 0.95 * dWall - 1,
`no freeze and no rush (${dPlay} ms of play in ${dWall} ms)`,
);
const metresPerS =
((playing[i].lat - playing[i - 1].lat) * 111_320) / (dWall / 1000);
const fixSpeed = (0.0025 * 111_320) / ((2 * TRANSIT_POLL_MS) / 1000);
assert.ok(
metresPerS <= fixSpeed * 1.01 + 0.05 &&
metresPerS >= fixSpeed * 0.94 - 0.05,
`at the fix speed: ${metresPerS.toFixed(2)} vs ${fixSpeed.toFixed(2)} m/s`,
);
}
});
test('the selected card anchors to the live marker every frame and republishes text only on change', async (t) => {
// The anchor was a clone refreshed on a 250 ms throttle under a 60 Hz
// sprite, so the card stepped behind a smooth vehicle. It is a getter now,
// read by the overlay host on every paint; the text keeps its throttle and
// is re-sent only when it has changed.
const app = harness(t);
const start = Date.now();
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('b', 42.36, -71.06, reported())], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
// Its floor resolves, so the marker is shown: a hidden vehicle has no card.
app.settle();
const entry = app.vehicles()[0];
const published = [];
app.layer._setTransitOverlayHostForTest({
setEntries: (id, entries) => published.push(entries[0]),
setVisible() {},
clearSource() {},
});
app.layer._transitPartsForTest().selection.selectVehicle(entry.key);
assert.equal(published.length, 1);
const card = published[0];
assert.equal(typeof card.position, 'function', 'the anchor is a getter');
app.advance(TRANSIT_POLL_MS);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('b', 42.3625, -71.0625, reported())],
{
fetchedAt: Date.now(),
},
),
}));
await app.layer.update();
// Frames at 16 ms through the segment: the anchor never lags the marker,
// whether or not a card refresh happened to run.
const firstFixAt = start - AGE_MS;
app.advance(firstFixAt + LAG_MS + 2_000 - Date.now());
let worst = 0;
for (let f = 0; f < 60; f += 1) {
app.advance(16);
app.frame();
const anchor = card.position();
worst = Math.max(
worst,
Cesium.Cartesian3.distance(anchor, entry.marker.position),
);
}
assert.ok(
worst < 1e-6,
`the anchor is the marker's own position (worst gap ${worst} m)`,
);
assert.ok(app.state()._moving.has(entry), 'and it was moving throughout');
// The text: throttled, and republished only when it changes. Sixty frames
// of one second is sixty throttle windows, but the card copy only changes
// when the reported age or the motion state does.
const before = published.length;
for (let f = 0; f < 5; f += 1) {
app.advance(16);
app.frame();
}
assert.equal(
published.length,
before,
'identical text within a second is not re-sent',
);
// A hidden marker anchors nothing.
entry.marker.show = false;
assert.equal(card.position(), null);
});
test('DETECT gets the same candidate list back until something could have changed it', async (t) => {
// Detection pulls the list on every paint. It used to be a fleet scan with
// an allocation and, above the cap, a sort — on the demo path, sixty times
// a second. The list is rebuilt on polls, visibility sweeps, removals and
// selection, and handed back as-is between them.
const app = harness(t);
const fleet = () =>
Array.from({ length: 40 }, (_, i) =>
vehicle(`v${i}`, 42.36 + i * 0.0002, -71.06, reported(), {
routeId: `${i}`,
}),
);
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', fleet(), { fetchedAt: Date.now() }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const first = app.layer.getDetectableObjects({ maxCount: 10, seed: 7 });
assert.equal(first.length, 10);
const again = app.layer.getDetectableObjects({ maxCount: 10, seed: 7 });
assert.equal(again, first, 'the same array, not a rebuilt one');
app.layer.getDetectableObjects({ maxCount: 5, seed: 7 });
app.advance(250);
app.layer._transitPartsForTest().queries.refreshDetectCache();
const other = app.layer.getDetectableObjects({ maxCount: 5, seed: 7 });
assert.notEqual(other, first, 'a different cap is a different list');
assert.equal(other.length, 5);
// Positions are the markers' own Cartesians: live, no copy.
const entry = app.vehicles().find((v) => v.key === first[0].sourceId);
assert.equal(first[0].position, entry.marker.position);
assert.equal(first[0].id, entry.record.routeId);
// Selecting a vehicle changes what the list says, so it is rebuilt.
app.layer._transitPartsForTest().selection.selectVehicle(first[0].sourceId);
app.layer.getDetectableObjects({ maxCount: 10, seed: 7 });
app.advance(250);
app.layer._transitPartsForTest().queries.refreshDetectCache();
const afterSelect = app.layer.getDetectableObjects({ maxCount: 10, seed: 7 });
assert.notEqual(afterSelect, first);
assert.equal(afterSelect[0].skipLabel, true);
// A poll rebuilds it; removing an UNSELECTED vehicle leaves the chosen set.
const chosenBefore = afterSelect.map((c) => c.sourceId);
app.advance(TRANSIT_POLL_MS);
await app.layer.update();
app.settle();
app.layer._transitPartsForTest().queries.refreshDetectCache();
const afterPoll = app.layer.getDetectableObjects({ maxCount: 10, seed: 7 });
assert.notEqual(afterPoll, afterSelect, 'rebuilt after the poll');
assert.deepEqual(
afterPoll.map((c) => c.sourceId),
chosenBefore,
'same stable selection',
);
});
test('under FLIR and NVG every sprite is white-hot with a dark halo, and selection cannot undo it', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
vehicle('bus', 42.36, -71.06, reported(), { routeId: '742' }),
vehicle('metro', 42.361, -71.06, reported(), { routeId: 'Red' }),
vehicle('tram', 42.362, -71.06, reported(), { routeId: 'Green-E' }),
],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const byRoute = new Map(app.vehicles().map((v) => [v.record.routeId, v]));
const bus = byRoute.get('742');
const plainImage = bus.marker.image;
const plainWidth = bus.marker.width;
const modeColour = bus.marker.color.clone();
app.style('thermal');
for (const entry of app.vehicles()) {
assert.ok(
Cesium.Color.equals(entry.marker.color, Cesium.Color.WHITE),
`${entry.record.routeId} is white-hot`,
);
assert.notEqual(entry.marker.image, plainImage, 'a haloed raster');
assert.match(
Buffer.from(entry.marker.image.split(',')[1], 'base64').toString(),
/#05080C/,
'with the dark ring',
);
assert.ok(entry.marker.width > plainWidth * 1.2, 'and larger');
}
// Selecting under FLIR grows the sprite and keeps it white — cyan would dim it.
app.layer._transitPartsForTest().selection.selectVehicle(bus.key);
assert.ok(
Cesium.Color.equals(bus.marker.color, Cesium.Color.WHITE),
'still white when selected',
);
assert.ok(
bus.marker.width > byRoute.get('Red').marker.width,
'and bigger than its neighbours',
);
app.layer._transitPartsForTest().selection.clearSelection();
assert.ok(
Cesium.Color.equals(bus.marker.color, Cesium.Color.WHITE),
'and white when deselected',
);
assert.equal(bus.marker.width, byRoute.get('Red').marker.width);
// Back to normal: the shipped look, exactly.
app.style('normal');
assert.ok(
Cesium.Color.equals(bus.marker.color, modeColour),
'mode colour restored',
);
assert.equal(bus.marker.image, plainImage, 'plain raster restored');
assert.equal(bus.marker.width, plainWidth, 'shipped size restored');
// A style that is not a sensor changes nothing.
app.style('anime');
assert.equal(bus.marker.image, plainImage);
});
test('a cockpit vision override wins over the map style while it lasts, and destroy stops listening', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('b', 42.36, -71.06, reported())], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
const plain = entry.marker.image;
app.vision('nvg', true);
assert.ok(
Cesium.Color.equals(entry.marker.color, Cesium.Color.WHITE),
'cockpit NVG restyles',
);
app.style('normal');
assert.ok(
Cesium.Color.equals(entry.marker.color, Cesium.Color.WHITE),
'the map style cannot undo a live cockpit override',
);
app.vision('normal', false);
assert.equal(
entry.marker.image,
plain,
'leaving the cockpit restores the map style',
);
assert.equal(app.state()._styleListeners.length, 2);
app.layer.destroy(app.viewer);
assert.equal(app.state()._styleListeners.length, 0, 'listeners removed');
assert.doesNotThrow(
() => app.style('thermal'),
"and a later event is nobody's business",
);
});
test('DETECT contacts carry a bracket tier per mode, keyed or keyless alike', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
vehicle('bus', 42.36, -71.06, reported(), { routeId: '742' }),
vehicle('metro', 42.361, -71.06, reported(), { routeId: 'Red' }),
vehicle('tram', 42.362, -71.06, reported(), { routeId: 'Green-E' }),
],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const tiers = new Map(
app.layer.getDetectableObjects().map((c) => [c.id, c.tier]),
);
assert.equal(tiers.get('742'), 'transit_bus');
assert.equal(tiers.get('RED'), 'transit_subway');
assert.equal(tiers.get('GREEN-E'), 'transit_tram');
});
test('the floor cycle samples the rendered mesh for the cells it warms, with its own sprites excluded', async (t) => {
// A transit-only session never warmed a mesh floor: the layer warmed the
// DEM and stopped, so under a photogrammetric city it drew on bare earth
// seventeen metres down. The same cells now go to the application's mesh
// sampler after every warm — bounded by the sampler's own per-call cap and
// proximity gate — with the layer's billboards excluded so a vertical probe
// cannot land on a bus.
const app = harness(t, { floorAt: () => 5 });
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
vehicle('a', 42.36, -71.06, reported()),
vehicle('b', 42.37, -71.07, reported()),
vehicle('far', 42.62, -71.44, reported()),
],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
assert.ok(
app.meshCalls.length >= 1,
'the sampler was asked after the floor resolved',
);
const call = app.meshCalls[0];
assert.equal(call.scene, app.viewer.scene);
assert.deepEqual(
call.points.map((p) => p.lat).sort(),
[42.36, 42.37],
'the in-view cells, and not the one off screen',
);
assert.equal(
call.options.excludeObjects.length,
2,
'both in-view billboards excluded',
);
assert.ok(
Math.abs(call.options.viewerLat - BOSTON.lat) < 1e-6,
'viewer subpoint in degrees',
);
assert.ok(Math.abs(call.options.viewerLon - BOSTON.lon) < 1e-6);
});
test('a failing prefix of cells cannot starve the vehicle behind it of a floor', async (t) => {
// Codex's reproduction: 301 visible cold entries, the first 300 in cells
// that never resolve. The budget used to take the first 300 unresolved
// vehicles in map order every cycle, so the 301st was never even asked for.
const count = FLOOR_WARM_PER_POLL + 1;
const app = harness(t, {
// Only the LAST vehicle's cell ever answers.
floorAt: (lat) =>
Math.abs(lat - (42.36 + (count - 1) * 0.002)) < 1e-9 ? 12 : undefined,
view: { west: -71.2, south: 42.3, east: -70.9, north: 43.2 },
});
const fleet = Array.from({ length: count }, (_, i) =>
vehicle(`v${i}`, 42.36 + i * 0.002, -71.06, reported()),
);
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', fleet, { fetchedAt: Date.now() }),
}));
app.layer.enable(app.viewer);
await app.layer.update();
// The unique-cell budget resets per poll; the next poll rotates priority.
app.settle(3);
app.advance(TRANSIT_POLL_MS);
await app.layer.update();
app.settle(3);
const last = app.vehicles().find((v) => v.record.id === `v${count - 1}`);
assert.equal(last.heightResolved, true, 'the 301st vehicle got its floor');
assert.ok(Math.abs(last.heightM - 13.5) < 1e-9);
assert.ok(
app.ground._warmed.some((p) => Math.abs(p.lat - last.record.lat) < 1e-9),
'because its cell was actually asked for',
);
});
test('a resolved floor still adopts a later mesh sample or a regime change on a parked vehicle', async (t) => {
// Codex's reproduction: resolve the DEM at 10 m, then the shared floor
// changes to a 50 m mesh sample; the parked entry kept 11.5 m for ever
// because ingestion only re-read a resolved height when the cell changed.
const app = harness(t, { floorAt: () => 10 });
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('p', 42.36, -71.06, reported())], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
assert.ok(Math.abs(entry.heightM - 11.5) < 1e-9, 'DEM floor, lifted');
const height = () =>
Cesium.Cartographic.fromCartesian(entry.marker.position).height;
assert.ok(Math.abs(height() - 11.5) < 0.5);
// The mesh lands on the same cell.
app.ground._warm.set(app.ground._key(42.36, -71.06), 50);
app.advance(TRANSIT_POLL_MS);
await app.layer.update();
app.frame();
assert.ok(
Math.abs(entry.heightM - 51.5) < 1e-9,
'the parked vehicle adopts the mesh floor',
);
assert.ok(Math.abs(height() - 51.5) < 0.5, 'and is drawn on it');
// Leaving the photoreal regime: the shared floor answers the DEM again.
app.ground._warm.set(app.ground._key(42.36, -71.06), 10);
app.advance(TRANSIT_POLL_MS);
await app.layer.update();
app.frame();
assert.ok(Math.abs(height() - 11.5) < 0.5, 'and follows it back down');
});
test('a stamp an hour ahead is bounded, so the honest fixes that follow are not refused', async (t) => {
// Codex's reproduction: the first fix stamped one hour ahead, then a
// corrected clock and normal travel. The marker stayed at its first
// position for as long as the wrong clock stayed newer than real time, and
// survived eviction because every refused snapshot refreshed its record.
const app = harness(t, { floorAt: () => 5 });
const ahead = (Date.now() + 3_600_000) / 1000;
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('c', 42.36, -71.06, ahead)], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
assert.ok(
entry.fixes[0].t <= Date.now() + FUTURE_STAMP_TOLERANCE_MS,
'the stamp is bounded by the fetch time',
);
// The clock is corrected; the bus travels north 200 m per poll.
let north = 42.36;
for (let poll = 1; poll <= 4; poll += 1) {
app.advance(TRANSIT_POLL_MS);
north += 0.0018;
const fixAtS = (Date.now() - 2_000) / 1000;
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('c', north, -71.06, fixAtS)], {
fetchedAt: Date.now(),
}),
}));
await app.layer.update();
}
assert.ok(
entry.fixes.length >= 3,
`honest fixes were accepted (${entry.fixes.length} in history)`,
);
assert.equal(entry.track.pendingCount, 0);
app.run(120_000);
assert.ok(
Math.abs(lat(entry) - 42.36) > 0.003,
'and the marker has moved on',
);
});
test('a run of out-of-order fixes restarts the history instead of freezing the vehicle', async (t) => {
const app = harness(t, { floorAt: () => 5 });
const later = (Date.now() + 5_000) / 1000; // within tolerance, but newer than what follows
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('o', 42.36, -71.06, later)], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
const firstRecord = entry.record;
// The feed's clock steps BACK: every following stamp is older than the first.
for (let poll = 1; poll <= ORDER_REJECTS_BEFORE_RESET; poll += 1) {
app.advance(TRANSIT_POLL_MS);
const stale = later - 60 + poll;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('o', 42.365, -71.06, stale, { label: `poll${poll}` })],
{
fetchedAt: Date.now(),
},
),
}));
await app.layer.update();
if (poll < ORDER_REJECTS_BEFORE_RESET) {
assert.equal(
entry.record,
firstRecord,
`refusal ${poll} does not refresh the record`,
);
assert.equal(entry.track.pendingCount, poll);
}
}
assert.equal(
entry.track.pendingCount,
0,
'reset after three advancing observations',
);
assert.equal(entry.fixes.length, 2);
app.settle();
assert.equal(entry.resets, 1);
assert.ok(
Math.abs(lat(entry) - 42.365) < 1e-6,
'placed at the fix it restarted from',
);
});
test('selected trail and marker share revised start floors, with unknown history left as gaps', async (t) => {
const app = harness(t, { floorAt: () => 10 });
let position = 42.36;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('trail', position, -71.06, reported())],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
app.advance(15000);
position += 0.0001;
await app.layer.update();
app.settle();
const entry = app.vehicles()[0],
parts = app.layer._transitPartsForTest();
parts.selection.selectVehicle(entry.key);
app.run(8000, 16);
let trail = parts.trails.diagnostics();
assert.ok(trail.segments > 0);
assert.equal(trail.entitiesAdded, 0);
assert.ok(
Cesium.Cartesian3.equals(
trail.head.positions.at(-1),
entry.marker.position,
),
);
const body = trail.body,
revision = trail.revision;
app.run(1000, 16);
assert.equal(parts.trails.diagnostics().body, body);
app.ground._warm.set(app.ground._key(42.36, -71.06), 30);
parts.height.rereadFloors();
app.frame();
trail = parts.trails.diagnostics();
assert.ok(trail.revision > revision);
assert.ok(
Cesium.Cartesian3.equals(
trail.head.positions.at(-1),
entry.marker.position,
),
);
assert.ok(
Cesium.Cartographic.fromCartesian(entry.marker.position).height > 30,
);
assert.ok(entry.trailVertices <= 640);
parts.selection.clearSelection();
assert.equal(parts.trails.diagnostics(), null);
});
test('selection restores retained history without changing clock or health; churn aborts late reads', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('b', 42.36, -71.06, reported())], {
fetchedAt: Date.now(),
}),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0],
parts = app.layer._transitPartsForTest();
const liveT = entry.fixes[0].t;
const payload = {
version: 1,
feedId: 'mbta',
vehicleId: 'b',
oldestT: liveT - 30000,
newestT: liveT,
truncated: false,
epochs: [{ id: 1, trip: '', route: '1', mode: 'bus' }],
fixes: [
[liveT - 30000, 42.359, -71.06, 1, 1],
[liveT - 15000, 42.3595, -71.06, 1, 1],
[liveT, 42.37, -71.06, 1, 1],
],
};
const priorFetch = globalThis.fetch;
let complete,
signal,
requests = 0;
globalThis.fetch = (url, options) => {
if (!String(url).includes('/trail/')) return priorFetch(url, options);
requests++;
signal = options.signal;
return new Promise((resolve) => {
complete = () => resolve(new Response(JSON.stringify(payload)));
});
};
const health = entry.track.acceptedAt,
clock = entry.playT;
parts.selection.selectVehicle(entry.key);
complete();
for (let i = 0; i < 20; i++) await Promise.resolve();
assert.equal(entry.track.count, 3);
assert.equal(
entry.fixes.at(-1).lat,
42.36,
'live position wins the conflict',
);
assert.equal(entry.track.acceptedAt, health);
assert.equal(entry.playT, clock);
assert.equal(requests, 1);
parts.selection.selectVehicle(entry.key);
assert.equal(requests, 2);
parts.selection.clearSelection();
assert.equal(signal.aborted, true);
const count = entry.track.count;
complete();
for (let i = 0; i < 20; i++) await Promise.resolve();
assert.equal(entry.track.count, count);
assert.equal(parts.trails.requestDiagnostics().pending, false);
parts.selection.selectVehicle(entry.key);
app.layer.disable(app.viewer);
assert.equal(signal.aborted, true);
complete();
for (let i = 0; i < 20; i++) await Promise.resolve();
assert.equal(app.vehicles().length, 0);
});
test('a long selected history shortens its oldest geometry before sacrificing the active corridor', async (t) => {
const app = harness(t);
const parts = app.layer._transitPartsForTest();
const { mergeHistory, seek } = await import('../../data/contactPlayback.js');
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('long', 42.36, -71.06, reported(), { routeId: 'CR-1' })],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0],
newest = entry.fixes[0].t;
mergeHistory(
entry.track,
Array.from({ length: 100 }, (_, i) => ({
t: newest - (99 - i) * 15000,
lat: 42.36 - (99 - i) * 0.009,
lon: -71.06,
epoch: entry.currentEpoch,
})),
);
for (let i = 0; i < 1000; i++)
app.ground._warm.set(app.ground._key(41.45 + i * 0.001, -71.06), 12);
seek(entry.track, newest - 7500, {
wallNowMs: Date.now(),
monoNowMs: performance.now(),
});
parts.rendering.sampleIdle(entry);
parts.selection.selectVehicle(entry.key);
assert.equal(entry.trailTruncated, true);
assert.ok(entry.trailVertices <= 640);
assert.ok(
entry.displayPaths.has(entry.sample.fromSeq),
'active path is prepared first',
);
assert.ok(
entry.trailSegments.every(
(segment, i, list) => i === 0 || list[i - 1].fromT <= segment.fromT,
),
);
});
test('sparse rail reports preserve a bounded marker corridor with and without selection', async (t) => {
const app = harness(t);
const parts = app.layer._transitPartsForTest();
const { seek } = await import('../../data/contactPlayback.js');
let latitude = 42.36 - 16000 / 111320;
let timestamp = reported();
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('sparse', latitude, -71.06, timestamp, { routeId: 'CR-1' })],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
app.advance(180000);
latitude = 42.36;
timestamp += 180;
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
assert.equal(entry.mode, 'rail');
assert.equal(entry.track.count, 2, 'both 88.9 m/s reports pass admission');
assert.equal(entry.fixes[1].t - entry.fixes[0].t, 180000);
for (let i = 0; i < 1500; i++)
app.ground._warm.set(app.ground._key(42.2 + i * 0.0002, -71.06), 12);
seek(entry.track, entry.fixes[0].t + 90000, {
wallNowMs: Date.now(),
monoNowMs: performance.now(),
});
parts.rendering.sampleIdle(entry);
for (const selected of [false, true, false]) {
if (selected) parts.selection.selectVehicle(entry.key);
else parts.selection.clearSelection();
parts.trails.prepareEntry(entry);
const path = entry.displayPaths.get(entry.sample.fromSeq);
assert.ok(path, 'active corridor survives the body cap');
assert.ok(path.positions.length <= 640);
const position = parts.trails.samplePosition(
entry,
entry.sample,
new Cesium.Cartesian3(),
true,
);
assert.ok(
position && [position.x, position.y, position.z].every(Number.isFinite),
);
parts.rendering.placeSample(entry, true);
assert.equal(entry.marker.show, true);
assert.ok(Cesium.Cartesian3.equals(entry.marker.position, position));
assert.ok(entry.trailVertices <= 640);
}
});
test('missing rectangle still rejects the far side, and frustum rejection wins', async (t) => {
const app = harness(t, { altitude: 100_000 });
app.viewer.camera.computeViewRectangle = () => undefined;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[
vehicle('near', 42.36, -71.06, reported()),
vehicle('far', -42.36, 108.94, reported()),
],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.advance(250);
app.frame();
assert.deepEqual(
[...app.state()._visible].map((e) => e.record.id),
['near'],
);
app.viewer.camera.frustum = {
computeCullingVolume: () => ({
computeVisibility: () => Cesium.Intersect.OUTSIDE,
}),
};
app.advance(250);
app.layer._transitPartsForTest().rendering.refreshVisibility();
assert.equal(app.state()._visible.size, 0);
assert.equal(app.state()._moving.size, 0);
assert.equal(app.holds().includes('transit'), false);
});
test('motion transitions migrate billboards and update cached positions; disable clears owners', async (t) => {
const app = harness(t);
let latitude = 42.36;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus', latitude, -71.06, reported())],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
const old = entry.marker;
app.layer.getDetectableObjects();
app.advance(15000);
latitude += 0.001;
await app.layer.update();
app.run(8000, 16);
assert.equal(entry.markerCollection, app.state()._animatedMarkers);
assert.notEqual(entry.marker, old);
assert.equal(entry.detectContact.position, entry.marker.position);
app.run(30000, 16);
assert.equal(entry.markerCollection, app.state()._markers);
const parts = app.layer._transitPartsForTest();
const contacts = app.layer.getDetectableObjects();
const metric = contacts[0].metric;
for (let i = 0; i < 20; i++) {
assert.equal(app.layer.getDetectableObjects(), contacts);
assert.equal(contacts[0].metric, metric);
}
parts.rendering.requestVisibility();
app.layer.disable(app.viewer);
assert.equal(app.state()._detectCache, null);
assert.equal(entry.detectContact, null);
assert.equal(app.state()._visible.size, 0);
assert.equal(app.state()._visibilityTimer, null);
assert.equal(app.state()._maintenanceTimer, null);
assert.equal(entry.wakeTimer, null);
assert.equal(app.state()._inFlight.size, 0);
assert.equal(parts.trails.requestDiagnostics().pending, false);
});
test('every styling path retains the mode palette and exact unpadded size', async (t) => {
const app = harness(t);
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus', 42.36, -71.06, reported(), { routeId: '741' })],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0],
parts = app.layer._transitPartsForTest();
const check = (hex, display) => {
assert.equal(entry.marker.color.toCssHexString().toUpperCase(), hex);
const svg = Buffer.from(
entry.marker.image.split(',')[1],
'base64',
).toString();
const width = Number(svg.match(/viewBox="-\d+ -\d+ (\d+)/)[1]);
assert.ok(Math.abs((entry.marker.width * 96) / width - display) < 1e-9);
};
check('#5EF08A', 20);
parts.selection.selectVehicle(entry.key);
check('#5EF08A', 30);
parts.rendering.paintMode(entry, 'subway');
check('#FF4538', 30);
app.style('thermal');
check('#FFFFFF', 39);
parts.selection.clearSelection();
check('#FFFFFF', 26);
app.vision('nvg', true);
check('#FFFFFF', 26);
parts.selection.selectVehicle(entry.key);
check('#FFFFFF', 39);
app.vision('normal', false);
check('#FF4538', 30);
parts.selection.clearSelection();
check('#FF4538', 20);
});
test('QA fleets at both budgets exercise real marker motion', async (t) => {
const app = harness(t, { altitude: 30000 });
app.layer.enable(app.viewer);
await app.layer.update();
for (const count of [800, 3000]) {
const loaded = app.layer._loadTransitFleetForTest(count, BOSTON, 1);
assert.equal(loaded.count, count);
assert.equal(loaded.moving, count);
assert.equal(loaded.fixBytes, count * 128 * 48);
const entry = app.vehicles()[0];
const before = Cesium.Cartesian3.clone(entry.marker.position);
app.advance(100);
app.frame();
assert.ok(Cesium.Cartesian3.distance(before, entry.marker.position) > 0.09);
}
});
test('visible motion is recullable without a camera event and releases its hold offscreen', async (t) => {
const app = harness(t, { altitude: 30000 });
app.layer.enable(app.viewer);
await app.layer.update();
app.layer._loadTransitFleetForTest(1, BOSTON, 1);
assert.equal(app.state()._moving.size, 1);
app.viewer.camera.frustum = {
computeCullingVolume: () => ({
computeVisibility: () => Cesium.Intersect.OUTSIDE,
}),
};
app.layer._transitPartsForTest().rendering.maintainPresentation();
app.advance(250);
assert.equal(app.state()._moving.size, 0);
assert.equal(app.state()._renderHeld, false);
});
test('mesh refinement reaches all 100 retained DEM cells within five poll budgets', async (t) => {
const { createHeight } = await import('./height.js');
t.mock.timers.enable({ apis: ['setTimeout'] });
let mono = 0;
t.mock.method(performance, 'now', () => mono);
const cells = Array.from({ length: 100 }, (_, i) => ({
lat: i / 1000,
lon: 0,
}));
const entry = { key: 'selected', to: cells[0], fixes: [cells.at(-1)] };
const probed = new Set();
const state = {
_enabled: true,
_selectedKey: entry.key,
_vehicles: new Map([[entry.key, entry]]),
_floorCursor: 0,
_heightDirty: new Set(),
_viewer: { camera: { positionCartographic: { height: 100 } } },
};
const height = createHeight({
state,
services: {
render: { governorRequestRender() {} },
ground: {
coarseFloorCoord: (lat, lon) => ({ lat, lon }),
cachedGroundFloor: () => 10,
GROUND_FLOOR_LIFT_M: 1.5,
},
mesh: {
sampleMeshFloorCells(scene, points) {
for (const point of points) probed.add(point.cell);
},
},
},
parts: {
trails: {
prepareEntry(e, collect) {
if (collect)
for (const point of cells)
collect(point, height.requestHeight(point.lat, point.lon));
},
},
rendering: { requestVisibility() {} },
},
});
t.after(() => height.clear());
for (let poll = 0; poll < 5; poll++) {
mono = poll * TRANSIT_POLL_MS;
height.anchorFloors();
}
assert.equal(probed.size, 100);
});
test('selection churn releases past geometry on every retained vehicle', async (t) => {
const app = harness(t, { floorAt: () => 10 });
const { mergeHistory, seek } = await import('../../data/contactPlayback.js');
const parts = app.layer._transitPartsForTest();
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
Array.from({ length: 5 }, (_, i) =>
vehicle(`churn-${i}`, 42.36, -71.06, reported()),
),
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
for (const entry of app.vehicles()) {
const newest = entry.fixes[0].t;
mergeHistory(
entry.track,
Array.from({ length: 100 }, (_, i) => ({
t: newest - (99 - i) * 5000,
lat: 42.36 - (99 - i) * 0.0001,
lon: -71.06,
epoch: entry.currentEpoch,
})),
);
for (let i = 0; i < 20; i++)
app.ground._warm.set(app.ground._key(42.345 + i * 0.001, -71.06), 10);
seek(entry.track, newest - 7500, {
wallNowMs: Date.now(),
monoNowMs: performance.now(),
});
parts.rendering.sampleIdle(entry);
parts.selection.selectVehicle(entry.key);
assert.ok(entry.trailSegments.length > 50);
parts.selection.clearSelection();
for (const retained of app.vehicles()) {
assert.equal(retained.trailSegments?.length || 0, 0);
assert.equal(retained.trailVertices || 0, 0);
assert.ok(
[...retained.displayPaths.keys()].every(
(seq) => seq >= retained.sample.fromSeq,
),
);
}
assert.equal(parts.trails.diagnostics(), null);
assert.equal(
entry.displayPaths.size,
2,
'only active and future corridors remain',
);
seek(entry.track, newest - 1000, {
wallNowMs: Date.now(),
monoNowMs: performance.now(),
});
parts.rendering.sampleIdle(entry);
assert.equal(
entry.displayPaths.size,
1,
'marker playback releases completed corridors',
);
}
});
test('tab visibility synchronizes playback before the returning frame and removes its listener', async (t) => {
const app = harness(t, { floorAt: () => 10 });
app.layer.enable(app.viewer);
app.layer._loadTransitFleetForTest(1, BOSTON, 5);
app.settle();
const entry = app.vehicles()[0];
app.frame();
const before = entry.playT;
const resets = entry.track.resets;
document.visibilityState = 'hidden';
document.dispatchEvent(new Event('visibilitychange'));
app.advance(30000);
document.visibilityState = 'visible';
document.dispatchEvent(new Event('visibilitychange'));
assert.equal(entry.track.resetReason, 're-entry');
assert.equal(entry.track.resets, resets + 1);
assert.ok(entry.playT > before);
assert.equal(entry.playT, Date.now() - entry.track.targetDelayMs);
const synchronized = entry.playT;
app.frame();
assert.equal(
entry.playT,
synchronized,
'first frame does not replay hidden elapsed time',
);
document.dispatchEvent(new Event('visibilitychange'));
assert.equal(entry.track.resets, resets + 1, 'visible duplicate is a no-op');
const remove = t.mock.method(document, 'removeEventListener');
app.layer.disable(app.viewer);
assert.ok(
remove.mock.calls.some((call) => call.arguments[0] === 'visibilitychange'),
);
});
for (const bounds of ['null', 'stale']) {
test(`browser visibility starts an injected fleet with ${bounds} bounds and an unsampled hidden marker`, async (t) => {
const app = harness(t);
app.viewer.camera.frustum = {
computeCullingVolume: () => ({
computeVisibility: () => Cesium.Intersect.INSIDE,
}),
};
app.layer.enable(app.viewer);
const loaded = app.layer._loadTransitFleetForTest(4, BOSTON, 70, [
'Red',
'741',
'742',
'Green-E',
]);
const state = app.state(),
parts = app.layer._transitPartsForTest();
state._viewBounds =
bounds === 'null' ? null : { south: 0, north: 1, west: 0, east: 1 };
for (const entry of app.vehicles()) {
state._visible.delete(entry);
state._moving.delete(entry);
entry.marker.show = false;
entry.sample = null;
}
state._visibilityAt = 0;
parts.rendering.refreshVisibility();
assert.equal(state._visible.size, loaded.count);
assert.equal(state._moving.size, loaded.count);
assert.equal(app.viewer.scene.requestRenderMode, false);
const entry = app.vehicles()[0],
before = Cesium.Cartesian3.clone(entry.marker.position);
app.run(1000, 16);
assert.ok(Cesium.Cartesian3.distance(before, entry.marker.position) > 0.9);
});
}
test('keyless loaded terrain resolves a pending surface without needing a frame first', async (t) => {
const app = harness(t, { floorAt: () => undefined });
let floor;
app.viewer.scene.globe.show = true;
app.viewer.scene.globe.getHeight = () => floor;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('bus', 42.36, -71.06, reported())],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
const entry = app.vehicles()[0],
parts = app.layer._transitPartsForTest();
assert.equal(entry.heightPending, true);
assert.equal(entry.marker.show, false);
floor = 12;
parts.height.rereadFloors();
app.state()._visibilityAt = 0;
parts.rendering.refreshVisibility();
assert.equal(entry.heightPending, false);
assert.equal(entry.surfaceReady, true);
assert.equal(entry.marker.show, true);
});
test('scripted keyless fleet keeps its known floor through selection and floor rereads', async (t) => {
const app = harness(t, { floorAt: () => undefined });
app.viewer.scene.globe.show = true;
app.layer.enable(app.viewer);
app.layer._loadTransitFleetForTest(4, BOSTON, 70);
const parts = app.layer._transitPartsForTest(),
entry = app.vehicles()[0];
parts.selection.selectVehicle(entry.key);
parts.height.rereadFloors();
app.run(1000, 16);
assert.equal(entry.surfaceReady, true);
assert.equal(entry.marker.show, true);
assert.ok(entry.trailSegments.length > 0);
assert.ok(app.state()._moving.has(entry));
});
for (const count of [800, 3000]) {
test(`real perspective frustum admits all ${count} moving budget fixtures`, async (t) => {
const altitude = count === 800 ? 600 : 30000;
const app = harness(t, { altitude, floorAt: () => undefined });
const camera = app.viewer.camera;
camera.positionWC = Cesium.Cartesian3.fromDegrees(
BOSTON.lon,
BOSTON.lat,
altitude,
);
camera.directionWC = Cesium.Cartesian3.negate(
Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(
camera.positionWC,
new Cesium.Cartesian3(),
),
new Cesium.Cartesian3(),
);
const enu = Cesium.Transforms.eastNorthUpToFixedFrame(camera.positionWC);
camera.upWC = Cesium.Matrix4.getColumn(enu, 1, new Cesium.Cartesian3());
camera.frustum = new Cesium.PerspectiveFrustum({
fov: Math.PI / 3,
aspectRatio: 1920 / 1080,
near: 1,
far: 100000,
});
app.layer.enable(app.viewer);
const loaded = app.layer._loadTransitFleetForTest(
count,
BOSTON,
count === 800 ? 5 : 120,
);
assert.equal(loaded.moving, count);
assert.equal(app.state()._visible.size, count);
const entry = app.vehicles()[0],
before = Cesium.Cartesian3.clone(entry.marker.position);
app.run(1000, 16);
assert.ok(Cesium.Cartesian3.distance(before, entry.marker.position) > 0.9);
assert.equal(app.viewer.scene.requestRenderMode, false);
});
}
test('click handler selects a migrated scripted billboard and prepares a valid depth-fail trail', async (t) => {
const app = harness(t, { floorAt: () => undefined });
app.layer.enable(app.viewer);
app.layer._loadTransitFleetForTest(4, BOSTON, 70);
const entry = app.vehicles()[0];
assert.equal(entry.markerCollection, app.state()._animatedMarkers);
app.viewer.scene.pick = () => ({ primitive: entry.marker, id: entry.key });
app
.state()
._clickHandler.getInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)({
position: new Cesium.Cartesian2(500, 400),
});
assert.equal(app.state()._selectedKey, entry.key);
const body = app.layer._transitPartsForTest().trails.diagnostics().body;
assert.ok(body.geometryInstances.length > 0);
body._batchTableAttributeIndices = { color: 0, depthFailColor: 1 };
assert.doesNotThrow(() =>
Cesium.Primitive._updateColorAttribute(
body,
body.depthFailAppearance.vertexShaderSource,
true,
),
);
assert.ok(entry.trailSegments.length > 0);
app.run(1000, 16);
assert.equal(entry.marker.show, true);
});
test('intercepted scripted reports resolve their explicit floor and wake without a preRender bootstrap', async (t) => {
const app = harness(t, { floorAt: () => undefined });
app.layer._setTransitFixtureFloorsForTest(['mbta:sim-straight'], 20);
let latitude = BOSTON.lat;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('sim-straight', latitude, BOSTON.lon, reported())],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.advance(15000);
latitude += 0.001;
await app.layer.update();
app.advance(8000);
const entry = app.vehicles()[0];
assert.equal(entry.heightPending, false);
assert.equal(entry.surfaceReady, true);
assert.equal(entry.marker.show, true);
assert.ok(app.state()._moving.has(entry));
assert.equal(app.viewer.scene.requestRenderMode, false);
const before = Cesium.Cartesian3.clone(entry.marker.position);
app.run(1000, 16);
assert.ok(Cesium.Cartesian3.distance(before, entry.marker.position) > 1);
app.layer.disable(app.viewer);
assert.equal(app.state()._qaFixtureFloors.size, 0);
});
test('hot path reuses sample outputs and reads camera altitude once per frame', async (t) => {
const h = harness(t);
await h.layer.enable(h.viewer);
h.layer._loadTransitFleetForTest(800, BOSTON, 5);
const state = h.state();
const parts = h.layer._transitPartsForTest();
const entry = state._vehicles.values().next().value;
const out = new Cesium.Cartesian3();
assert.equal(parts.trails.samplePosition(entry, entry.sample, out), out);
const sample = entry.sample;
const carto = h.viewer.camera.positionCartographic;
let reads = 0;
Object.defineProperty(h.viewer.camera, 'positionCartographic', {
configurable: true,
get() {
reads++;
return carto;
},
});
h.advance(17);
h.frame();
assert.equal(entry.sample, sample);
assert.ok(reads <= 2, `camera altitude reads/frame ${reads}, limit 2`);
const contacts = h.layer.getDetectableObjects();
const contact = contacts[0];
for (let i = 0; i < 100; i++) {
state._detectRevision++;
assert.equal(h.layer.getDetectableObjects(), contacts);
assert.equal(contacts[0], contact);
}
});
test('scripted straight and changing delay preserve ordinary-frame speed through polls', async (t) => {
const h = harness(t, { altitude: 600 });
const start = Date.now();
const modes = ['straight', 'gap', 'dup'];
h.serve('mbta', () => {
const elapsed = (Date.now() - start) / 1000;
return {
status: 200,
body: snapshot(
'mbta',
'MBTA',
modes.map((id) => {
let sec =
Math.floor(elapsed / (id === 'dup' ? 30 : 10)) *
(id === 'dup' ? 30 : 10);
if (id === 'gap' && sec >= 40 && sec < 130) sec = 40;
return vehicle(
id,
BOSTON.lat + (sec * 8) / 111320,
BOSTON.lon,
(start + sec * 1000) / 1000,
);
}),
{ fetchedAt: Date.now() },
),
};
});
await h.layer.enable(h.viewer);
let prior = new Map(),
worst = 0,
pair = null,
samples = 0;
for (let ms = 17; ms <= 200000; ms += 17) {
h.advance(17);
if (ms % 15011 === 0) await h.layer.update();
h.frame();
for (const e of h.vehicles()) {
const a = prior.get(e.key),
s = e.sample,
p = e.marker.position;
const b = {
x: p.x,
y: p.y,
z: p.z,
t: s.displayT,
from: s.fromSeq,
to: s.toSeq,
speed: s.segmentSpeedMps,
phase: s.phase,
};
if (
a &&
a.phase === 'playing' &&
b.phase === 'playing' &&
a.from === b.from &&
a.to === b.to &&
a.speed > 0 &&
b.t > a.t
) {
const error = Math.abs(
Math.hypot(b.x - a.x, b.y - a.y, b.z - a.z) /
((b.t - a.t) / 1000) /
a.speed -
1,
);
samples++;
if (error > worst) {
worst = error;
pair = { key: e.key, ms, a, b };
}
}
prior.set(e.key, b);
}
}
assert.ok(samples > 1000);
assert.ok(worst < 0.02, JSON.stringify({ worst, pair, samples }));
});
test('a lost prepared surface releases moving playback on that frame', async (t) => {
const h = harness(t);
await h.layer.enable(h.viewer);
h.layer._loadTransitFleetForTest(1, BOSTON, 5);
const parts = h.layer._transitPartsForTest();
assert.equal(h.state()._moving.size, 1);
h.state()._heightDirty.clear();
clearTimeout(h.state()._visibilityTimer);
h.state()._visibilityTimer = null;
t.mock.method(parts.trails, 'samplePosition', () => null);
h.advance(17);
h.frame();
assert.equal(h.state()._moving.size, 0);
assert.equal(h.state()._renderHeld, false);
});
test('sensor brackets clear the padded hot body and retain their contact during style changes', async (t) => {
const h = harness(t);
await h.layer.enable(h.viewer);
h.layer._loadTransitFleetForTest(1, BOSTON, 5);
const parts = h.layer._transitPartsForTest(),
entry = h.vehicles()[0];
const contact = h.layer.getDetectableObjects()[0];
for (const style of ['thermal', 'surveillance', 'noir', 'normal']) {
parts.rendering.setStylePreset(style);
assert.equal(entry.detectContact, contact);
assert.ok(contact.bracketHalfWidth >= entry.marker.width / 2 + 2, style);
assert.ok(contact.bracketHalfHeight >= entry.marker.height / 2 + 2, style);
}
});
test('selected anchor rejects nonfinite marker coordinates before sampling and during surface changes', async (t) => {
const app = harness(t, { floorAt: () => undefined });
app.layer.enable(app.viewer);
app.layer._loadTransitFleetForTest(4, BOSTON, 70);
const entry = app.vehicles()[0];
app.layer._transitPartsForTest().selection.selectVehicle(entry.key);
const card = app.overlaySources.get('transit-selected')[0];
assert.ok(card.position());
const position = Cesium.Cartesian3.clone(entry.marker.position);
entry.marker.position = new Cesium.Cartesian3(NaN, 0, 0);
assert.equal(card.position(), null);
entry.marker.position = position;
assert.equal(card.position(), entry.marker.position);
entry.marker.show = false;
assert.equal(card.position(), null);
});
test('preset clicks preserve both pick id paths through moving and stationary collection migration', async (t) => {
const app = harness(t, { floorAt: () => undefined });
app.layer.enable(app.viewer);
app.layer._loadTransitFleetForTest(4, BOSTON, 70);
const entry = app.vehicles()[0],
parts = app.layer._transitPartsForTest();
for (const style of ['normal', 'thermal', 'surveillance', 'noir', 'retro'])
for (const animated of [true, false]) {
parts.rendering.setStylePreset(style);
entry.sample.phase = animated ? 'playing' : 'held';
entry.sample.segmentSpeedMps = animated ? 1 : 0;
entry.sample.nextWakeMonoMs = Infinity;
entry.sample.segmentCourseDeg = entry.courseDeg;
parts.rendering.schedulePlayback(entry);
assert.equal(
entry.markerCollection,
animated ? app.state()._animatedMarkers : app.state()._markers,
);
assert.equal(entry.marker.id, entry.key);
for (const pick of [{ id: entry.key }, { primitive: entry.marker }]) {
app.viewer.scene.pick = () => pick;
app
.state()
._clickHandler.getInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)(
{ position: new Cesium.Cartesian2(500, 400) },
);
assert.equal(app.state()._selectedKey, entry.key);
parts.selection.clearSelection();
}
}
});
test('QA backwards seek reconstructs the pruned fixture paths before allocation measurement', async (t) => {
const app = harness(t, { floorAt: () => undefined });
app.layer.enable(app.viewer);
app.layer._loadTransitFleetForTest(4, BOSTON, 70);
app.run(10000, 16);
const entry = app.vehicles()[0];
entry.displayPaths.clear();
app.layer._resetTransitFixtureClockForTest();
assert.ok(
entry.displayPaths.size <= 6,
'rewind retains only the active and future fixture corridor',
);
assert.equal(entry.marker.show, true);
assert.equal(app.state()._moving.size, 4);
const before = Cesium.Cartesian3.clone(entry.marker.position);
app.run(1000, 16);
assert.ok(Cesium.Cartesian3.distance(before, entry.marker.position) > 0.9);
});
test('sensor clicks map displayed pixels into the pre-shader pick buffer', async (t) => {
const app = harness(t, { floorAt: () => undefined });
app.layer.enable(app.viewer);
app.layer._loadTransitFleetForTest(4, BOSTON, 70);
const entry = app.vehicles()[0],
scene = app.viewer.scene;
let actual;
scene.pick = (p) => {
actual = { x: p.x, y: p.y };
return { id: entry.key };
};
scene.canvas = {
width: 1920,
height: 1080,
clientWidth: 1920,
clientHeight: 1080,
};
const uniforms = { intensity: 1, pixelation: 1 };
scene.postProcessStages = {
getStageByName: () => ({ enabled: true, uniforms }),
};
const click = app
.state()
._clickHandler.getInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
app.layer._transitPartsForTest().rendering.setStylePreset('surveillance');
click({ position: new Cesium.Cartesian2(1440, 540) });
assert.ok(Math.abs(actual.x - 1472) < 1e-6);
assert.equal(actual.y, 540);
app.layer._transitPartsForTest().rendering.setStylePreset('thermal');
uniforms.pixelation = 1.5;
click({ position: new Cesium.Cartesian2(100.9, 200.1) });
assert.ok(Math.abs(actual.x - 100.5) < 1e-6);
assert.ok(Math.abs(actual.y - 201) < 1e-6);
assert.equal(app.state()._selectedKey, entry.key);
});
test('rejected route metadata stays on the accepted bus', async (t) => {
const app = harness(t);
let routeId = '741',
latitude = 42.36;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('b', latitude, -71.06, reported(), { routeId })],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
const image = entry.marker.image;
app.advance(TRANSIT_POLL_MS);
routeId = 'Red';
latitude = 43.36;
await app.layer.update();
assert.equal(entry.mode, 'bus');
assert.equal(entry.record.routeId, '741');
assert.equal(entry.marker.image, image);
assert.equal(entry.currentEpoch, 1);
assert.equal(entry.track.count, 1);
assert.doesNotMatch(
app.layer
.getDetectableObjects()
.map((c) => c.metric)
.join(' '),
/METRO/,
);
});
test('reject then selected history backfill cannot resurrect a 111 km displacement', async (t) => {
const app = harness(t);
let latitude = 42.36;
app.serve('mbta', () => ({
status: 200,
body: snapshot(
'mbta',
'MBTA',
[vehicle('b', latitude, -71.06, reported())],
{ fetchedAt: Date.now() },
),
}));
app.layer.enable(app.viewer);
await app.layer.update();
app.settle();
const entry = app.vehicles()[0];
app.advance(15000);
latitude += 0.001;
await app.layer.update();
app.settle();
const accepted = entry.fixes.map((f) => [f.t, f.lat, f.lon, 1, 1]);
app.advance(15000);
latitude = 43.36;
await app.layer.update();
assert.equal(entry.track.count, 2);
const pending = entry.track.pending;
const clock = entry.playT;
const priorFetch = globalThis.fetch;
globalThis.fetch = (url, options) =>
String(url).includes('/trail/')
? Promise.resolve(
new Response(
JSON.stringify({
version: 1,
feedId: 'mbta',
vehicleId: 'b',
oldestT: accepted[0][0],
newestT: pending.t,
truncated: false,
epochs: [{ id: 1, trip: '', route: '1', mode: 'bus' }],
fixes: [...accepted, [pending.t, latitude, -71.06, 1, 1]],
}),
),
)
: priorFetch(url, options);
const parts = app.layer._transitPartsForTest();
parts.selection.selectVehicle(entry.key);
for (let i = 0; i < 30; i++) await Promise.resolve();
assert.equal(entry.track.count, 2);
assert.equal(entry.playT, clock);
assert.equal(entry.track.pending, pending);
app.run(60000);
assert.ok(Math.abs(lat(entry) - 42.361) * 111320 < 1);
assert.equal(entry.track.resets, 0);
});
test('actual Traffic, Bikeshare and Transit lifecycles retain sensitivity in every enable/disable order', async (t) => {
const { createLifecycle: trafficLifecycle } =
await import('../traffic/lifecycle.js');
const { createLifecycle: bikeLifecycle } =
await import('../bikeshare/lifecycle.js');
const app = harness(t);
const noop = () => {};
const traffic = trafficLifecycle({
state: { _pointCollection: {}, _loadGeneration: 0 },
services: {
render: { holdContinuousRender: noop, releaseContinuousRender: noop },
},
source: {},
parts: {
flow: { ensureFlowStatus: noop },
animation: { animate: noop, clearDots: noop },
viewport: { onCameraChanged: noop },
ingestion: { cancelActiveFetch: noop },
},
}).methods;
const bike = bikeLifecycle({
state: {
_pointCollection: {},
_overlayHost: { setVisible: noop },
_cityRuntime: new Map(),
_failedCityIds: new Set(),
},
services: {
sprites: { restoreSpriteOrder: noop },
picking: { registerPickOwner: noop, unregisterPickOwner: noop },
},
source: {},
parts: {
selection: {
_installClickHandler: noop,
_clearSelection: noop,
_onKeyDown: noop,
},
viewport: {
onCameraChanged: noop,
runProximityCheck: noop,
deactivateAllCities: noop,
},
ingestion: { abortAllInFlight: noop },
},
}).methods;
app.viewer.camera.moveEnd = new Cesium.Event();
const layers = { traffic, bikeshare: bike, transit: app.layer };
const orders = [
['traffic', 'bikeshare', 'transit'],
['traffic', 'transit', 'bikeshare'],
['bikeshare', 'traffic', 'transit'],
['bikeshare', 'transit', 'traffic'],
['transit', 'traffic', 'bikeshare'],
['transit', 'bikeshare', 'traffic'],
];
try {
for (const enable of orders)
for (const disable of orders) {
const active = new Set();
for (const id of enable) {
layers[id].enable(app.viewer);
active.add(id);
assert.equal(app.viewer.camera.percentageChanged, 0.05);
assert.deepEqual(
new Set(cameraSensitivityClaims(app.viewer.camera)),
active,
);
}
for (const id of disable) {
layers[id].disable(app.viewer);
active.delete(id);
assert.equal(
app.viewer.camera.percentageChanged,
active.size ? 0.05 : 0.5,
`${enable} / ${disable} after ${id}`,
);
assert.deepEqual(
new Set(cameraSensitivityClaims(app.viewer.camera)),
active,
);
}
}
} finally {
for (const layer of Object.values(layers)) layer.disable(app.viewer);
}
});
test('moving and stationary transit stay adjacent above CCTV and Bikeshare during migration', async (t) => {
const {
registerSpriteCollection,
unregisterSpriteCollection,
restoreSpriteOrder,
} = await import('../../data/spriteOrder.js');
const app = harness(t);
app.layer.enable(app.viewer);
app.layer._loadTransitFleetForTest(1, BOSTON, 70);
const cctv = {},
bike = {},
flights = {};
app.primitives.push(cctv, bike, flights);
app.viewer.scene.primitives.raiseToTop = (c) => {
app.primitives.splice(app.primitives.indexOf(c), 1);
app.primitives.push(c);
};
const collections = new Map([
...app.sprites,
['cctv', cctv],
['bikeshare', bike],
['flights', flights],
]);
for (const [id, collection] of collections)
registerSpriteCollection(id, collection);
t.after(() => {
for (const [id, c] of collections) unregisterSpriteCollection(id, c);
});
const entry = app.vehicles()[0],
parts = app.layer._transitPartsForTest();
for (const moving of [true, false, true]) {
entry.sample.phase = moving ? 'playing' : 'held';
entry.sample.segmentSpeedMps = moving ? 1 : 0;
entry.sample.segmentCourseDeg = NaN;
parts.rendering.schedulePlayback(entry);
restoreSpriteOrder(app.viewer);
const active = app.primitives.indexOf(entry.markerCollection);
assert.ok(active > app.primitives.indexOf(cctv));
assert.ok(active > app.primitives.indexOf(bike));
assert.ok(active < app.primitives.indexOf(flights));
assert.equal(
Math.abs(
app.primitives.indexOf(app.state()._markers) -
app.primitives.indexOf(app.state()._animatedMarkers),
),
1,
);
assert.equal(entry.marker.id, entry.key);
}
});
test('transit polls the supplied source and cancels it when disabled', async (t) => {
const calls = [];
const source = {
async requestSnapshot(feedId, { signal }) {
calls.push({ feedId, signal });
return Response.json(
snapshot(
feedId,
'MBTA',
[vehicle('injected', 42.36, -71.06, reported())],
{ fetchedAt: Date.now() },
),
);
},
async getHistory() {
throw new Error('No selected vehicle');
},
};
const app = harness(t, { source });
globalThis.fetch = () => {
throw new Error('Unexpected global fetch');
};
app.layer.enable(app.viewer);
await app.layer.update();
assert.ok(calls.some(({ feedId }) => feedId === 'mbta'));
assert.ok(app.vehicles().some((entry) => entry.record.id === 'injected'));
const before = calls.length;
app.layer.disable(app.viewer);
await app.layer.update();
assert.equal(calls.length, before);
});
test('selected trail QA fixture survives repeated live CapMetro snapshots', async (t) => {
const { TRAIL_VISIBILITY_FEED } =
await import('../../../scripts/qa-transit-scenes.mjs');
const { getRegisteredTransitFeed } =
await import('../../data/transitFeeds.js');
const app = harness(t, { at: { lat: 30.267, lon: -97.7431 } });
app.layer.enable(app.viewer);
const { ingestion, selection } = app.layer._transitPartsForTest();
const now = Date.now();
ingestion.applySnapshot(
TRAIL_VISIBILITY_FEED,
{
fetchedAt: now,
vehicles: [vehicle('trail-visible', 30.267, -97.7431, now / 1000)],
},
{ stale: false },
);
const key = `${TRAIL_VISIBILITY_FEED.id}:trail-visible`;
selection.selectVehicle(key);
assert.equal(app.state()._selectedKey, key);
for (let poll = 0; poll <= MISSED_POLLS_TO_DROP; poll++) {
ingestion.applySnapshot(
getRegisteredTransitFeed('capmetro-austin'),
{
fetchedAt: now + poll,
vehicles: [vehicle('live-bus', 30.268, -97.7431, now / 1000)],
},
{ stale: false },
);
}
assert.ok(
app.state()._vehicles.has(key),
'live polls must not evict the selected fixture',
);
assert.equal(app.state()._selectedKey, key);
assert.ok(
app.state()._vehicles.has('capmetro-austin:live-bus'),
'live ingestion still runs',
);
});
for (const jump of ['setView', 'flyTo']) {
test(`${jump} recovers a cold hidden vehicle within one visibility interval`, async (t) => {
const app = harness(t, { floorAt: () => undefined });
let floor;
app.viewer.scene.globe.show = true;
app.viewer.scene.globe.getHeight = () => floor;
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [
vehicle('cold', 42.36, -71.06, reported()),
]),
}));
app.layer.enable(app.viewer);
await app.layer.update();
const entry = app.vehicles()[0];
assert.equal(entry.marker.show, false);
assert.equal(entry.heightPending, true);
// Surface tiles are now loaded at the camera destination. No poll, floor
// timer or preRender is allowed to bootstrap the visibility recovery.
clearTimeout(app.state()._floorTimer);
app.state()._floorTimer = null;
app.viewer.camera[jump] = () => {
floor = 12;
app.viewer.camera.changed.raiseEvent();
};
app.viewer.camera[jump]();
app.advance(250);
assert.equal(entry.marker.show, true);
assert.equal(entry.heightPending, false);
assert.equal(entry.surfaceReady, true);
assert.equal(entry.visibility.heightPending, false);
assert.equal(entry.visibility.surfaceReady, true);
});
}
test('selected history is prepared while its head is hidden and remains shown outside the head frustum', async (t) => {
const app = harness(t);
app.layer.enable(app.viewer);
app.layer._loadTransitFleetForTest(1, BOSTON);
const entry = app.vehicles()[0],
parts = app.layer._transitPartsForTest();
entry.marker.show = false;
app.state()._visible.delete(entry);
app.state()._moving.delete(entry);
parts.selection.selectVehicle(entry.key);
const trail = parts.trails.diagnostics();
assert.ok(trail.segments > 0);
assert.ok(trail.body);
assert.equal(trail.body.show, true);
parts.trails.update();
assert.equal(trail.body.show, true);
app.state()._vehicles.delete(entry.key);
parts.trails.update();
assert.equal(trail.body.show, false);
app.state()._vehicles.set(entry.key, entry);
});
test('selected draped history prepares geometry before surface heights resolve', async (t) => {
const app = harness(t, { floorAt: () => undefined });
app.layer.enable(app.viewer);
app.layer._loadTransitFleetForTest(1, BOSTON);
const entry = app.vehicles()[0],
parts = app.layer._transitPartsForTest();
delete entry.qaFloorM;
entry.marker.show = false;
entry.heightPending = true;
// Exercise the real GroundPolylinePrimitive branch, without a WebGL context.
const width = Cesium.ContextLimits._maximumAliasedLineWidth;
Cesium.ContextLimits._maximumAliasedLineWidth = 1;
t.after(() => {
Cesium.ContextLimits._maximumAliasedLineWidth = width;
});
app.viewer.scene.frameState = { context: { depthTexture: true } };
app.viewer.scene.groundPrimitives = app.viewer.scene.primitives;
parts.selection.selectVehicle(entry.key);
const trail = parts.trails.diagnostics();
assert.equal(trail.ground, true);
assert.ok(trail.body instanceof Cesium.GroundPolylinePrimitive);
assert.ok(trail.segments > 0);
assert.equal(trail.body.show, true);
assert.equal(
entry.displayPaths.size,
0,
'unresolved marker corridors remain unavailable',
);
});
// A camera pose can change without a viewport-membership revision (notably
// a top-down orbit). Exercise the actual frame pass with an otherwise idle fleet.
test('parked transit reprojects held and reported courses at the bounded camera cadence', async (t) => {
const { createRendering } = await import('./rendering.js');
t.mock.timers.enable({ apis: ['Date'], now: 1000 });
const position = Cesium.Cartesian3.fromDegrees(0, 0, 20);
const cameraPosition = Cesium.Cartesian3.fromDegrees(0, 0, 1000);
const scene = {
frameState: { mode: Cesium.SceneMode.SCENE3D },
canvas: { clientWidth: 1280, clientHeight: 800 },
camera: {
positionWC: cameraPosition,
heading: 0,
pitch: -Math.PI / 2,
roll: 0,
frustum: new Cesium.PerspectiveFrustum({
fov: Math.PI / 3,
aspectRatio: 1.6,
near: 1,
far: 1e7,
}),
},
};
const pose = (heading) => {
const camera = scene.camera;
camera.heading = heading;
camera.rightWC = new Cesium.Cartesian3(
0,
Math.cos(heading),
-Math.sin(heading),
);
camera.upWC = new Cesium.Cartesian3(
0,
Math.sin(heading),
Math.cos(heading),
);
camera.viewMatrix = Cesium.Matrix4.computeView(
cameraPosition,
new Cesium.Cartesian3(-1, 0, 0),
camera.upWC,
camera.rightWC,
new Cesium.Matrix4(),
);
};
pose(0);
const make = (key, courseDeg, bearing) => ({
key,
courseDeg,
record: { bearing },
sample: { segmentCourseDeg: NaN },
marker: { position, rotation: 0.7, show: true },
});
const held = make('held', 90, 270);
const reportedOnly = make('reported', null, 90);
const unknown = make('unknown', null, null);
const hidden = make('hidden', 90, 90);
const state = {
_viewer: { scene },
_enabled: true,
_vehicles: new Map(
[held, reportedOnly, unknown, hidden].map((e) => [e.key, e]),
),
_visible: new Set([held, reportedOnly, unknown]),
_moving: new Set(),
_heightDirty: new Set(),
_rotationAt: 0,
_rotationRevision: -1,
_cameraRevision: 0,
_rotationDirty: false,
};
const noop = () => {};
const rendering = createRendering({
state,
services: {
render: {
governorRequestRender: noop,
holdContinuousRender: noop,
releaseContinuousRender: noop,
},
},
parts: { height: { nearGround: () => false }, trails: { update: noop } },
});
rendering.onPreRender();
const before = [held.marker.rotation, reportedOnly.marker.rotation];
pose(Math.PI / 2);
t.mock.timers.tick(100);
rendering.onPreRender();
assert.equal(
held.marker.rotation,
before[0],
'no rotation pass before 200 ms',
);
t.mock.timers.tick(100);
rendering.onPreRender();
for (const [i, entry] of [held, reportedOnly].entries()) {
const delta = Math.atan2(
Math.sin(entry.marker.rotation - before[i]),
Math.cos(entry.marker.rotation - before[i]),
);
assert.ok(
Math.abs(delta - Math.PI / 2) < 0.01,
`${entry.key}: rotation delta ${delta}`,
);
assert.equal(entry.courseDeg, 90);
}
assert.equal(
unknown.marker.rotation,
0,
'no course means screen-up, never a stale angle',
);
assert.equal(
hidden.marker.rotation,
0.7,
'hidden vehicles are not projected',
);
state._selectedKey = unknown.key;
unknown.marker.rotation = 0.7;
rendering.onPreRender();
assert.equal(
unknown.marker.rotation,
0,
'selected unknown course also stays screen-up',
);
});