fix(cockpit): a provider's subject rides where it is drawn, and the documented provider rides a moving train

- A frame with no speedMps gets an exact anchor: an inertial one ran on at
  zero speed and closed at 0.75 m/s, so the camera fell kilometres behind a
  moving train. The docs example now passes speedMps and anchor: 'exact',
  a name for its HUD, and isEntryAllowed for a train bound to its line.
- Transit's snapshot height is the floor where playback draws the vehicle,
  not the newest report's floor uphill, and null while that floor is cold,
  so a view waits for the surface instead of standing on the ellipsoid.
- Rail's direction follows playback once it has placed the train: a report
  that runs back (a reversal, or GPS jitter) no longer turns the view
  while the drawn train still runs forward.
- A frame's id is trimmed as the candidate's is.
- A double-click on the selected vessel no longer enters an application's
  subject that took entry after it.
- The docs import from gods-eye-view/..., the package's name.
This commit is contained in:
Sameh Khamis committed 2026-11-13 18:23:50 -08:00
1 parent 974023510f
commit 4bb7b411e9
10 files changed
+161 -21

No files matched your search

+9 -3
View File
@@ -252,8 +252,8 @@ the last; a worded member (labels, entry face, look) that throws keeps its
last good words.
```js
import { getLocale, onLocaleChange } from '@gods-eye-view/core/i18n';
import { pointAtS } from '@gods-eye-view/core/layers/rail/paths';
import { getLocale, onLocaleChange } from 'gods-eye-view/i18n';
import { pointAtS } from 'gods-eye-view/layers/rail/paths';
const words = {
en: { view: 'Ride view', exit: 'Exit ride view', exitText: 'EXIT RIDE' },
@@ -281,6 +281,8 @@ const ride = {
}
);
},
// Only a train bound to its line can be ridden: read() needs its path.
isEntryAllowed: () => Boolean(rail.getSelectedTrain()?.path),
hold: (id) => rail.setCockpitSubject(id),
read(nowMs, ctx) {
const train = rail.getCockpitSubject();
@@ -291,8 +293,12 @@ const ride = {
latitude: point.lat,
longitude: point.lon,
heightM: train.position.railHeightM, // null stands it on the shared floor
name: train.label,
headingDeg:
train.path.direction < 0 ? point.bearingDeg + 180 : point.bearingDeg,
speedMps: train.motion.reportedSpeedMps ?? train.motion.derivedSpeedMps,
// The layer already moves the train smoothly: sit on it.
anchor: 'exact',
eye: { upM: 3.5 },
};
},
@@ -309,7 +315,7 @@ createApplicationControls({ /* ... */ services: { cockpitSubjects: [ride] } });
The `./i18n` entry gives `getLocale()` and `onLocaleChange()`:
Core translates only its own strings, so a provider words its labels, entry
face and HUD for the page's language (labels may be functions, as above; the
HUD is read again on a change). `@gods-eye-view/core/layers/rail/paths` is the
HUD is read again on a change). `gods-eye-view/layers/rail/paths` is the
Rail layer's pure path module (`createRailPath`, `pointAtS`, `projectOnPath`)
for a provider placing a train along its line. The frame, HUD view and
candidate fields are documented in `src/cockpitSubjectProviders.js`; the Rail
+9 -4
View File
@@ -2241,7 +2241,8 @@ at most `COCKPIT_GROUND_WAIT_TIMEOUT_MS`, after which the view ends and the
subject is released: no height stands in for a floor never read), slews toward `headingDeg` at the provider's rate, and may
turn off the floor clamps (`groundClamp: false`, for a subject inside a
structure) or keep the anchor exactly on the subject (`anchor: 'exact'`, for a
layer that already moves it smoothly); `cameraIntervalMs: 0` updates the
layer that already moves it smoothly; a frame with no `speedMps` is always
exact, since an inertial anchor runs on at the frame's speed); `cameraIntervalMs: 0` updates the
camera every rendered frame. A frame that is null, malformed or for another
subject ends the view (`restoreTracking: false`), as an aircraft tracked
meanwhile and the provider's layer turning off do; the exit control asks the
@@ -2252,7 +2253,7 @@ picture under the HUD (with passing light streaks, which stop under reduced
motion), arrow-key look-around when the provider asks for it, and a progress
line along the bottom edge (`src/cockpitProgress.js`: close ticks merge,
labels never overlap). Core translates none of the provider's text: a host
words it for the page's language (`@gods-eye-view/core/i18n`:
words it for the page's language (`gods-eye-view/i18n`:
`getLocale`, `onLocaleChange`). Its entry face, labels and look may be
functions, read again when they are shown and on a change of language, as the
HUD view is.
@@ -2268,14 +2269,18 @@ feed held, clicks and Escape left to the view; the held copy stays the one
drawn of its train while held, another feed's copy hidden, and it keeps
moving, still selected, when its category is turned off) and
`focusTrain(key)` (false for a train the layer does not draw); its pure path
module is exported as `@gods-eye-view/core/layers/rail/paths`. That export
module is exported as `gods-eye-view/layers/rail/paths`. That export
and these read APIs are experimental and may change without notice. A snapshot read again while nothing in it changed is
the one read before, and its `path.line` is a frozen copy. Transit
offers the same for a vehicle (`getSelectedVehicle`, `getCockpitSubject`,
`setCockpitSubject`, `focusVehicle`): a held vehicle keeps playing when it is
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. `docs/APPLICATION.md` (Cockpit subjects) shows a minimal provider. `src/ui/cockpitSubjectView.test.mjs`,
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
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`,
`src/layers/rail/cockpitSubject.test.mjs` and the Transit lifecycle tests
cover them.