fix(transit): keep the real silhouettes under sensor presets, make the selected trail visible, put Transit in Movement (#596)

## Changes

Follow-up to #587 after the owner's first look at the merged transit layer on Google 3D tiles.

- **Sensor presets keep the real silhouettes.** Under NVG, thermal (white-hot, black-hot, Ironbow) and noir, vehicles are the same silhouettes as in normal view, filled opaque white with a 2 px dark halo, at the CRT display sizes (26 px fleet, 39 px selected). The solid rounded "signature" bodies are gone.
- **The selected vehicle's trail is visible.** The retained history is now draped onto 3D tiles and terrain (`GroundPolylinePrimitive`, `ClassificationType.BOTH`, the way the traffic heat-lines are drawn), 3 px core over a 5 px dark backing, per-segment age colour, a short draped head clipped to the sprite. Two defects kept it off screen before: Cesium's per-instance attribute getters return copies, so the renderer's show/colour edits were discarded, and a marker did not recover after the camera jumped to it, which hid the trail with it. Trail geometry is now prepared on selection independent of floor resolution and marker visibility; sparse long intervals (rail) keep a bounded marker corridor instead of dropping the interval; classification survives map-source changes.
- **Transit sits in the Movement panel group** between Street Traffic and Bike Share (the panel orders by `PANEL_GROUPS`, not by catalog order; the catalog is moved too).

## How it was verified

- `npm run format:check` (854 files), `npm run check:boundaries`, `npm test` (4,062 passing), `npm run build` clean; tracking regression 109/109 in a real browser on Google 3D tiles.
- Browser harness (`scripts/qa-transit.mjs`), keyed on Google 3D tiles and keyless: keyed 194 pass, with the trail pixel check passing at 300, 600 and 900 m; the remaining keyed failures are the per-frame allocation gate already recorded as a follow-up from #587 (CPU and frame time within budget), four readability lines at threshold edges, and the card-anchor check now counting frames where the selected marker is legitimately off screen; keyless 193 pass with the remaining misses being threshold edges on the thin tram silhouette (centre 0.83–0.88 against 0.85) and one bus ring 0.28 against 0.25 on a pure-white roof.
- New harness checks: `Google 3D selected trail visible at 6 of 8 projected samples` (repeated off/on framebuffer captures at 300/600/900 m, samples outside the sprite footprint, a background-change negative control; fails on main, passes here), a camera-arrival check (select a moving vehicle, `setView` onto it, marker shown and trail body drawn within 2 s), `PANEL_ORDER` pinned, raster tests comparing every sensor raster's mask and aspect ratio to the normal silhouette.
- Direct look on a keyed build: a live CapMetro bus at 30 m/s selected through the layer API draws its draped trail along the highway lane (harness capture `keyed-600m-trail-visible.jpg`).
- Adversarial review by Codex Astra before the last two fix passes (stack-switch classification, marker corridor under the vertex cap, pixel-gate loophole), all three fixed with regressions.

Docs: `docs/CURRENT-STATE.md` and `CHANGELOG.md` updated in this PR. No data-source changes.
This commit is contained in:
Bilawal Sidhu authored and GitHub committed 2026-09-15 13:26:41 -05:00
1 parent cd22ca3559
commit 0bb61f53c6
24 files changed
+1945 -205

No files matched your search

+192 -7
View File
@@ -2178,7 +2178,7 @@ test('under FLIR and NVG every sprite is white-hot with a dark halo, and selecti
assert.notEqual(entry.marker.image, plainImage, 'a haloed raster');
assert.match(
Buffer.from(entry.marker.image.split(',')[1], 'base64').toString(),
/#000000/,
/#05080C/,
'with the dark ring',
);
assert.ok(entry.marker.width > plainWidth * 1.2, 'and larger');
@@ -2547,7 +2547,7 @@ test('selected trail and marker share revised start floors, with unknown history
assert.ok(
Cesium.Cartographic.fromCartesian(entry.marker.position).height > 30,
);
assert.ok(entry.trailVertices <= 2048);
assert.ok(entry.trailVertices <= 640);
parts.selection.clearSelection();
assert.equal(parts.trails.diagnostics(), null);
});
@@ -2659,7 +2659,7 @@ test('a long selected history shortens its oldest geometry before sacrificing th
parts.rendering.sampleIdle(entry);
parts.selection.selectVehicle(entry.key);
assert.equal(entry.trailTruncated, true);
assert.ok(entry.trailVertices <= 2048);
assert.ok(entry.trailVertices <= 640);
assert.ok(
entry.displayPaths.has(entry.sample.fromSeq),
'active path is prepared first',
@@ -2671,6 +2671,63 @@ test('a long selected history shortens its oldest geometry before sacrificing th
);
});
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;
@@ -2783,13 +2840,13 @@ test('every styling path retains the mode palette and exact unpadded size', asyn
parts.rendering.paintMode(entry, 'subway');
check('#FF4538', 30);
app.style('thermal');
check('#FFFFFF', 36);
check('#FFFFFF', 39);
parts.selection.clearSelection();
check('#FFFFFF', 30);
check('#FFFFFF', 26);
app.vision('nvg', true);
check('#FFFFFF', 30);
check('#FFFFFF', 26);
parts.selection.selectVehicle(entry.key);
check('#FFFFFF', 36);
check('#FFFFFF', 39);
app.vision('normal', false);
check('#FF4538', 30);
parts.selection.clearSelection();
@@ -3673,3 +3730,131 @@ test('transit polls the supplied source and cancels it when disabled', async (t)
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',
);
});