fix(transit): a held vehicle's snapshot height is its drawn marker's while the frame pass places it

Near the ground the marker follows the terrain sampled between two reports,
which a straight blend of the reports' floors misses over uneven ground.
This commit is contained in:
Sameh Khamis committed 2026-11-13 18:36:42 -08:00
1 parent 4bb7b411e9
commit 6c55daf9ac
3 files changed
+25 -5

No files matched your search

+2 -2
View File
@@ -2277,8 +2277,8 @@ offers the same for a vehicle (`getSelectedVehicle`, `getCockpitSubject`,
out of the camera's view, and its sprite, 3D model, card, trail and detection out of the camera's view, and its sprite, 3D model, card, trail and detection
box are hidden; released, it is out of play until a visibility sweep finds it in box are hidden; released, it is out of play until a visibility sweep finds it in
view. Its snapshot's `position.groundHeightM` is the floor where playback draws view. Its snapshot's `position.groundHeightM` is the floor where playback draws
it (its segment's two reports' floors at the playback fraction), not the newest it (the drawn marker's height while it is placed, else its segment's two
report's, and null while that floor is unknown. Rail's `path.direction` is the reports' floors at the playback fraction), not the newest report's, and null while that floor is unknown. Rail's `path.direction` is the
way the drawn train runs: once playback has placed it, a report that runs back way the drawn train runs: once playback has placed it, a report that runs back
turns it only when playback reaches that report. `docs/APPLICATION.md` (Cockpit subjects) shows a minimal provider. `src/ui/cockpitSubjectView.test.mjs`, turns it only when playback reaches that report. `docs/APPLICATION.md` (Cockpit subjects) shows a minimal provider. `src/ui/cockpitSubjectView.test.mjs`,
`src/cockpitSubjectProviders.test.mjs`, `src/cockpitProgress.test.mjs`, `src/cockpitSubjectProviders.test.mjs`, `src/cockpitProgress.test.mjs`,
+19 -2
View File
@@ -1,3 +1,4 @@
import * as Cesium from 'cesium';
import { import {
getRegisteredTransitFeed, getRegisteredTransitFeed,
transitRouteLabel, transitRouteLabel,
@@ -77,9 +78,12 @@ export function createTransitCockpitSubject({ state, parts, services }) {
return reuse(previous, next); return reuse(previous, next);
} }
const scratchCartographic = new Cesium.Cartographic();
/** /**
* The floor under the vehicle where playback draws it: its segment's two * The floor under the vehicle where playback draws it, without the shared
* reports' floors at the playback fraction, without the shared lift. Null * lift: the drawn marker's height while the frame pass places it (in view,
* or held), which near the ground follows the terrain between reports;
* else its segment's two reports' floors at the playback fraction. Null
* while either floor is unknown, as on cold terrain, so a view waits for * while either floor is unknown, as on cold terrain, so a view waits for
* the surface. Not `entry.heightM`, the floor at the newest report, which * the surface. Not `entry.heightM`, the floor at the newest report, which
* playback has not reached yet. * playback has not reached yet.
@@ -87,6 +91,19 @@ export function createTransitCockpitSubject({ state, parts, services }) {
function drawnFloorOf(entry) { function drawnFloorOf(entry) {
const segment = entry.segment; const segment = entry.segment;
if (!segment || entry.heightPending) return null; if (!segment || entry.heightPending) return null;
const placed =
entry.surfaceReady === true &&
entry.hasRendered &&
entry.marker?.position &&
(state._visible.has(entry) || entry.key === state._cockpitSubjectKey);
if (placed) {
const drawn = Cesium.Cartographic.fromCartesian(
entry.marker.position,
undefined,
scratchCartographic,
);
if (drawn) return drawn.height - liftM;
}
const from = segment.from?.h; const from = segment.from?.h;
const to = segment.to?.h; const to = segment.to?.h;
if (!Number.isFinite(from) || !Number.isFinite(to)) return null; if (!Number.isFinite(from) || !Number.isFinite(to)) return null;
+4 -1
View File
@@ -4247,7 +4247,10 @@ test('the selected vehicle reads as a snapshot of its place, motion and source',
assert.equal(view.provenance.positionSource, 'gps'); assert.equal(view.provenance.positionSource, 'gps');
assert.deepEqual(view.stop, { stopId: 'place-1', status: 'IN_TRANSIT_TO' }); assert.deepEqual(view.stop, { stopId: 'place-1', status: 'IN_TRANSIT_TO' });
const entry = app.vehicles()[0]; const entry = app.vehicles()[0];
assert.equal(view.position.groundHeightM, entry.heightM - 1.5); // Where it is drawn, without the shared lift (read back from the marker).
assert.ok(
Math.abs(view.position.groundHeightM - (entry.heightM - 1.5)) < 1e-3,
);
assert.ok(Number.isFinite(view.position.latitude)); assert.ok(Number.isFinite(view.position.latitude));
}); });