feat(rail): add a Rail layer with keyless live trains

Live trains from 30 keyless operator feeds: GPS positions from 29 and
Irish Rail's station reports, shown as the operators publish them. 28
work out of the box; Renfe long and medium distance and KTMB appear once
their pack is built, because their vehicles name no route.

Packs are opt-in (scripts/build-rail-paths.mjs): the operator's own
route lines, stop names, next stops and trip-update delays for live
trains. Nothing downloads when the app starts, no train is placed from a
timetable, and no request carries a deployer's key.
This commit is contained in:
Denis Groshev committed 2026-11-11 16:06:22 +01:00
1 parent 14db73f201
commit 830c080803
114 files changed
+26639 -26

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
import { createRailLayer } from '../../layers/rail/index.js';
import * as render from '../../renderGovernor.js';
import * as sprites from '../../data/spriteOrder.js';
import * as picking from '../../data/pickRegistry.js';
import * as overlays from '../../overlays/worldOverlay.js';
import { applyTrackedCameraFrame } from '../../data/trackedCamera.js';
import { WORLD_FOCUS_FRAMING, requestWorldFocus } from '../../worldFocus.js';
import {
railFeedCredit,
registerDynamicCredit,
} from '../../data/dataCredits.js';
/**
* Construct the Rail layer on the application's scene owners and its shared
* ground floor, the same floor Transit and the aircraft stand on.
* @param {{surface: object, source?: object}} options
* @returns {object}
*/
export function createApplicationRail({ surface, source }) {
if (!surface?.groundFloor)
throw new TypeError('Rail needs the application surface services');
const { groundFloor } = surface;
return createRailLayer({
source,
services: {
render,
sprites,
picking,
overlays,
credits: { registerDynamicCredit, railFeedCredit },
ground: {
GROUND_FLOOR_LIFT_M: groundFloor.GROUND_FLOOR_LIFT_M,
cachedGroundFloor: groundFloor.cachedGroundFloor,
warmGroundFloor: groundFloor.warmGroundFloor,
neighborFloorM: groundFloor.neighborFloorM,
coarseFloorCoord: groundFloor.coarseFloorCoord,
},
// A selected train is followed as a tracked aircraft is, or flown to
// once as a vessel is (src/layers/rail/follow.js).
camera: {
framing: WORLD_FOCUS_FRAMING.train,
requestWorldFocus,
applyTrackedCameraFrame,
isCockpitActive: () =>
globalThis.document?.body?.classList?.contains('cockpit-mode') ===
true,
},
},
});
}