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

+59
View File
@@ -328,6 +328,7 @@ test('sensor sampler waits for postRender and reads framebuffer pixels; stopped
globalThis.Image = previousImage;
});
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
const readRows = [];
let listener,
reads = 0,
render = true;
@@ -336,6 +337,7 @@ test('sensor sampler waits for postRender and reads framebuffer pixels; stopped
UNSIGNED_BYTE: 2,
readPixels(x, y, w, h, format, type, buffer) {
reads++;
readRows.push(y);
buffer.set([255, 255, 255, 255]);
},
};
@@ -408,13 +410,70 @@ test('sensor sampler waits for postRender and reads framebuffer pixels; stopped
},
};
const page = { evaluate: async (fn, ...args) => fn(...args) };
const sampledLayer =
window.__godsEyeView.dataManager.layers.get('transit').module;
const originalState = sampledLayer._transitStateForTest;
sampledLayer._transitStateForTest = () => {
const state = originalState();
state._vehicles.get('bus').marker.position.y = 0.0025; // Screen y = 199.5.
return state;
};
const pixels = await sampleTransitPixels(page, {});
sampledLayer._transitStateForTest = originalState;
assert.deepEqual(
readRows.slice(0, 9),
[201, 200, 199, 201, 200, 199, 201, 200, 199],
'fractional CSS coordinates must invert the floored framebuffer row',
);
assert.ok(
reads >= 13,
'centre, rings and background come from WebGL readPixels',
);
assert.ok(Math.abs(pixels[0].centre - 1) < 1e-6);
assert.equal(listener, null);
// An opaque window/panel is not a sensor core. Choose another solid patch,
// and reject a raster with no white interior even though alpha/picking pass.
for (let i = 0; i < raster.length; i += 4) raster.fill(0, i, i + 3);
assert.equal((await sampleTransitPixels(page, {})).length, 0);
raster.fill(255);
for (let y = 6; y <= 9; y++)
for (let x = 0; x < 16; x++) {
const i = (y * 16 + x) * 4;
raster.fill(0, i, i + 3);
}
assert.equal(
(await sampleTransitPixels(page, {})).length,
1,
'a body patch away from the central panel remains eligible',
);
// A narrow centre can be opaque yet put the framebuffer sample on its
// antialiased side. Prefer the wider solid band above it, before reading luma.
raster.fill(0);
for (let y = 0; y < 16; y++)
for (let x = 0; x < 16; x++) {
const wide = y >= 3 && y <= 5;
if ((wide && x >= 3 && x <= 12) || (x >= 7 && x <= 9))
raster.fill(255, (y * 16 + x) * 4, (y * 16 + x + 1) * 4);
}
const readStart = readRows.length;
assert.equal((await sampleTransitPixels(page, {})).length, 1);
assert.ok(
readRows[readStart] > 204,
'centre samples use the widest solid band',
);
raster.fill(255);
sampledLayer._transitStateForTest = () => {
const state = originalState();
state._vehicles.get('bus').mode = 'tram';
return state;
};
const tramStart = readRows.length;
assert.equal((await sampleTransitPixels(page, {})).length, 1);
assert.ok(
readRows[tramStart] > 204,
'equal-width tram bands prefer the upper solid body',
);
sampledLayer._transitStateForTest = originalState;
scene.pick = () => ({ id: 'overlapping-label' });
assert.equal(
(await sampleTransitPixels(page, {})).length,