diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 9627e41d7..489341732 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -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 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 -it (its segment's two reports' floors at the playback fraction), not the newest -report's, and null while that floor is unknown. Rail's `path.direction` is the +it (the drawn marker's height while it is placed, else its segment's two +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 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`, diff --git a/src/layers/transit/cockpitSubject.js b/src/layers/transit/cockpitSubject.js index 556c50ab8..d598e572a 100644 --- a/src/layers/transit/cockpitSubject.js +++ b/src/layers/transit/cockpitSubject.js @@ -1,3 +1,4 @@ +import * as Cesium from 'cesium'; import { getRegisteredTransitFeed, transitRouteLabel, @@ -77,9 +78,12 @@ export function createTransitCockpitSubject({ state, parts, services }) { return reuse(previous, next); } + const scratchCartographic = new Cesium.Cartographic(); /** - * The floor under the vehicle where playback draws it: its segment's two - * reports' floors at the playback fraction, without the shared lift. Null + * The floor under the vehicle where playback draws it, without the shared + * 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 * the surface. Not `entry.heightM`, the floor at the newest report, which * playback has not reached yet. @@ -87,6 +91,19 @@ export function createTransitCockpitSubject({ state, parts, services }) { function drawnFloorOf(entry) { const segment = entry.segment; 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 to = segment.to?.h; if (!Number.isFinite(from) || !Number.isFinite(to)) return null; diff --git a/src/layers/transit/lifecycle.test.mjs b/src/layers/transit/lifecycle.test.mjs index 9f0bae557..71d9d1df4 100644 --- a/src/layers/transit/lifecycle.test.mjs +++ b/src/layers/transit/lifecycle.test.mjs @@ -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.deepEqual(view.stop, { stopId: 'place-1', status: 'IN_TRANSIT_TO' }); 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)); });