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

+132 -52
View File
@@ -21,23 +21,70 @@ export function trailAlpha(ageMs) {
/** One static history batch and one frequently updated head. Never entities. */
export function createContactTrailRenderer(scene) {
const heads = scene.primitives.add(new Cesium.PolylineCollection());
const backing = heads.add({
positions: [],
width: 4,
show: false,
material: Cesium.Material.fromType('Color', {
color: new Cesium.Color(0.02, 0.03, 0.05, 0.8),
}),
});
const head = heads.add({
positions: [],
width: 2,
show: false,
material: Cesium.Material.fromType('Color', {
color: Cesium.Color.WHITE.clone(),
}),
});
const ground =
!!scene.frameState?.context &&
Cesium.GroundPolylinePrimitive.isSupported(scene);
const collection = ground ? scene.groundPrimitives : scene.primitives;
// One 25 m corridor is clipped in material coordinates. Only subdivision
// crossings build geometry; frame updates write a scalar, never vertex buffers.
const headMaterial = (alpha) =>
new Cesium.Material({
fabric: {
type: 'TransitTrailHead',
uniforms: {
color: new Cesium.Color(0.37, 0.94, 0.54, alpha),
fraction: 0,
backing: true,
},
source: `czm_material czm_getMaterial(czm_materialInput materialInput) {
if (materialInput.st.s > fraction) discard;
czm_material m = czm_getDefaultMaterial(materialInput);
bool edge = abs(materialInput.st.t - 0.5) > 0.3;
m.diffuse = czm_gammaCorrect(vec4(edge && backing ? vec3(0.02, 0.03, 0.05) : color.rgb, 1.0)).rgb;
m.alpha = color.a;
return m;
}`,
},
translucent: true,
});
const material = headMaterial(0.8),
depthMaterial = headMaterial(0.55);
depthMaterial.uniforms.backing = false;
const head = { positions: [], show: false, width: 3, material };
const backing = { positions: [], show: false, width: 5 };
let headPrimitive = null,
headRebuilds = 0;
function geometry(positions, width, material = false) {
return ground
? new Cesium.GroundPolylineGeometry({ positions, width, granularity: 0 })
: new Cesium.PolylineGeometry({
positions,
width,
arcType: Cesium.ArcType.NONE,
vertexFormat: material
? Cesium.PolylineMaterialAppearance.VERTEX_FORMAT
: Cesium.PolylineColorAppearance.VERTEX_FORMAT,
});
}
function primitive(instances, appearance, depthFailAppearance) {
return ground
? new Cesium.GroundPolylinePrimitive({
geometryInstances: instances,
appearance,
classificationType: Cesium.ClassificationType.BOTH,
allowPicking: false,
asynchronous: true,
show: visible,
})
: new Cesium.Primitive({
geometryInstances: instances,
appearance,
depthFailAppearance,
allowPicking: false,
asynchronous: false,
show: visible,
});
}
let body = null,
segments = [],
revision = -1,
@@ -68,7 +115,10 @@ export function createContactTrailRenderer(scene) {
completed = -1;
lastSecond = -1;
active = null;
if (body) scene.primitives.remove(body);
if (headPrimitive) collection.remove(headPrimitive);
headPrimitive = null;
head.show = backing.show = false;
if (body) collection.remove(body);
body = null;
const instances = [];
attributeIds.length = 0;
@@ -80,18 +130,13 @@ export function createContactTrailRenderer(scene) {
instances.push(
new Cesium.GeometryInstance({
id: `${i}:${lane}`,
geometry: new Cesium.PolylineGeometry({
positions: segment.positions,
width: lane ? 2 : 4,
arcType: Cesium.ArcType.NONE,
vertexFormat: Cesium.PolylineColorAppearance.VERTEX_FORMAT,
}),
geometry: geometry(segment.positions, lane ? 3 : 5),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
tint(0.8, !lane),
),
depthFailColor: Cesium.ColorGeometryInstanceAttribute.fromColor(
tint(0.8, !lane),
tint(0.55),
),
show: new Cesium.ShowGeometryInstanceAttribute(false),
},
@@ -100,20 +145,12 @@ export function createContactTrailRenderer(scene) {
}
}
if (instances.length) {
const depthFailAppearance = new Cesium.PolylineColorAppearance({
translucent: true,
fragmentShaderSource:
'in vec4 v_color; void main() { out_FragColor = czm_gammaCorrect(vec4(v_color.rgb, v_color.a * 0.2)); }',
});
body = scene.primitives.add(
new Cesium.Primitive({
geometryInstances: instances,
appearance: new Cesium.PolylineColorAppearance({ translucent: true }),
depthFailAppearance,
allowPicking: false,
asynchronous: false,
show: visible,
}),
body = collection.add(
primitive(
instances,
new Cesium.PolylineColorAppearance({ translucent: true }),
new Cesium.PolylineColorAppearance({ translucent: true }),
),
);
rebuilds++;
}
@@ -122,6 +159,7 @@ export function createContactTrailRenderer(scene) {
if (!sample || !renderedPosition) {
head.show = false;
backing.show = false;
if (headPrimitive) headPrimitive.show = false;
return;
}
const second = Math.floor(sample.displayT / 1000);
@@ -152,8 +190,10 @@ export function createContactTrailRenderer(scene) {
attributeIds[i][lane],
);
if (!attributes) continue;
attributes.show[0] = i <= done ? 1 : 0;
attributes.show = attributes.show;
// Cesium getters return copies. Write back the array we changed.
const show = attributes.show;
show[0] = i <= done ? 1 : 0;
attributes.show = show;
}
}
completed = done;
@@ -166,16 +206,20 @@ export function createContactTrailRenderer(scene) {
attributeIds[i][lane],
);
if (!attributes) continue;
const rgba = attributes.color;
Cesium.ColorGeometryInstanceAttribute.toValue(
tint(alpha, !lane),
attributes.color,
rgba,
);
attributes.color = attributes.color;
Cesium.ColorGeometryInstanceAttribute.toValue(
tint(alpha, !lane),
attributes.depthFailColor,
);
attributes.depthFailColor = attributes.depthFailColor;
attributes.color = rgba;
if (!ground) {
const depthRgba = attributes.depthFailColor;
Cesium.ColorGeometryInstanceAttribute.toValue(
tint(0.55),
depthRgba,
);
attributes.depthFailColor = depthRgba;
}
}
}
lastSecond = second;
@@ -183,16 +227,43 @@ export function createContactTrailRenderer(scene) {
const show = visible && !!segment && sample.fraction < 1;
head.show = show;
backing.show = show;
if (headPrimitive) headPrimitive.show = show;
if (!show) return;
if (active !== segment) {
active = segment;
if (headPrimitive) collection.remove(headPrimitive);
headPrimitive = collection.add(
primitive(
new Cesium.GeometryInstance({
geometry: geometry(segment.positions, 5, true),
}),
new Cesium.PolylineMaterialAppearance({
material,
translucent: true,
}),
new Cesium.PolylineMaterialAppearance({
material: depthMaterial,
translucent: true,
}),
),
);
headRebuilds++;
headPositions[0] = segment.positions[0];
headPositions[1] = endpoint;
}
Cesium.Cartesian3.clone(renderedPosition, endpoint);
head.positions = headPositions;
backing.positions = headPositions;
Cesium.Color.clone(tint(0.8), head.material.uniforms.color);
material.uniforms.fraction = Math.max(
0,
Math.min(
1,
(sample.displayT - segment.fromT) / (segment.toT - segment.fromT),
),
);
depthMaterial.uniforms.fraction = material.uniforms.fraction;
Cesium.Color.clone(tint(0.8), material.uniforms.color);
Cesium.Color.clone(tint(0.55), depthMaterial.uniforms.color);
}
return {
replaceHistory,
@@ -205,12 +276,15 @@ export function createContactTrailRenderer(scene) {
},
setVisible(next) {
visible = next;
heads.show = next;
if (headPrimitive) headPrimitive.show = next && head.show;
if (body) body.show = next;
},
destroy() {
if (body) scene.primitives.remove(body);
scene.primitives.remove(heads);
if (body) collection.remove(body);
if (headPrimitive) collection.remove(headPrimitive);
headPrimitive = null;
material.destroy();
depthMaterial.destroy();
body = null;
segments = [];
},
@@ -222,6 +296,12 @@ export function createContactTrailRenderer(scene) {
body,
head,
backing,
headPrimitive,
headStrategy: ground
? 'draped corridor with material clip'
: 'depth-fail corridor with material clip',
ground,
headRebuilds,
entitiesAdded: 0,
};
},