Merge remote-tracking branch 'origin/main' into fix/1281

# Conflicts:
#	docs/CURRENT-STATE.md
This commit is contained in:
Said Turksever committed 2026-10-23 12:12:27 +01:00
commit d8dce93ff4
325 files changed
+20500 -1424

No files matched your search

-1
View File
@@ -9,7 +9,6 @@ import {
FEED_STALE_AFTER_MS,
MAX_VEHICLES_TOTAL,
MISSED_POLLS_TO_DROP,
TRANSIT_POLL_MS,
isStaleVehicleFix,
transitVehicleKey,
vehicleReportTimeMs,
-2
View File
@@ -6,7 +6,6 @@ import {
FEED_EVICT_AFTER_MS,
FEED_STALE_AFTER_MS,
MISSED_POLLS_TO_DROP,
SELECTED_CARD_REFRESH_MS,
TRANSIT_POLL_MS,
VEHICLE_MAX_FIX_AGE_MS,
} from './policy.js';
@@ -1407,7 +1406,6 @@ test('the selected card lands where the vehicle stopped, not short of it', async
// segment ends left it a few metres behind, and then the render hold was
// released with no frame scheduled to close the gap.
const app = harness(t);
const start = Date.now();
app.serve('mbta', () => ({
status: 200,
body: snapshot('mbta', 'MBTA', [vehicle('b', 42.36, -71.06, reported())], {
+8 -38
View File
@@ -9,10 +9,7 @@ import {
correctHeight,
setTimeOffsetMs,
} from '../../data/contactPlayback.js';
export const FIX_HISTORY_MAX = 128;
export const DISPLAY_LAG_MIN_MS = 25_000;
export const DISPLAY_LAG_RESET_MS = 90_000;
export const MODE_MAX_SPEED_MPS = Object.freeze({
const MODE_MAX_SPEED_MPS = Object.freeze({
bus: 36, // ~130 km/h — a coach on a motorway
tram: 28, // ~100 km/h — interurban light rail
subway: 42, // ~150 km/h
@@ -32,22 +29,19 @@ export const MODE_MAX_SPEED_MPS = Object.freeze({
* @param {boolean} [inferred] Whether the mode came from a feed default.
* @returns {number} m/s
*/
export function speedCeilingMps(mode, inferred = false) {
function speedCeilingMps(mode, inferred = false) {
if (inferred) return MODE_MAX_SPEED_MPS.unknown;
return MODE_MAX_SPEED_MPS[mode] ?? MODE_MAX_SPEED_MPS.unknown;
}
/** Position deadband is disabled: even a short reported crawl is played. */
export const FIX_NOISE_M = 0;
/**
* Displacement (m) a glide must cover before its direction is worth believing.
* Below this the course derived from it is noise; the last good one is held.
*/
export const COURSE_MIN_TRAVEL_M = 12;
const COURSE_MIN_TRAVEL_M = 12;
/** Maximum display-course change per second (deg). A bus is not a gyroscope. */
export const COURSE_MAX_RATE_DEG_PER_S = 120;
const COURSE_MAX_RATE_DEG_PER_S = 120;
/**
* Longest stretch of elapsed time (s) one evaluation may spend. The limiter
* used to bank the time since the course last CHANGED, so a vehicle that held
@@ -56,15 +50,7 @@ export const COURSE_MAX_RATE_DEG_PER_S = 120;
* EVALUATED, and capped here so that even a first look after a long idle
* sweeps visibly instead of snapping.
*/
export const COURSE_STEP_MAX_S = 0.25;
/**
* Consecutive fixes at the same place before a vehicle is called STOPPED.
* One is not enough: a fifteen-second gap between identical fixes is also what
* a vehicle waiting at a light looks like, and so is a feed that repeated
* itself.
*/
export const STOPPED_FIXES = 2;
const COURSE_STEP_MAX_S = 0.25;
/**
* How long after its newest fix a vehicle stops being described as travelling.
@@ -75,7 +61,7 @@ export const STOPPED_FIXES = 2;
* otherwise is the same overreach as reading STOPPED off a stale status. Ninety
* seconds matches the threshold at which a whole feed reads as gone quiet.
*/
export const MOTION_UNKNOWN_AFTER_MS = 90_000;
const MOTION_UNKNOWN_AFTER_MS = 90_000;
const EARTH_M_PER_DEG = 111_320;
@@ -94,29 +80,13 @@ export function fixDistanceM(a, b) {
return Math.hypot(dLat, dLon);
}
/**
* Course in degrees clockwise from north, from one point to another.
* @param {{lat:number, lon:number}} from
* @param {{lat:number, lon:number}} to
* @returns {number} 0-360
*/
export function courseBetween(from, to) {
const dLat = (to.lat - from.lat) * EARTH_M_PER_DEG;
const dLon =
(to.lon - from.lon) *
EARTH_M_PER_DEG *
Math.cos((from.lat * Math.PI) / 180);
const deg = (Math.atan2(dLon, dLat) * 180) / Math.PI;
return (deg + 360) % 360;
}
/**
* Shortest signed difference between two courses, in degrees (-180, 180].
* @param {number} from
* @param {number} to
* @returns {number}
*/
export function courseDelta(from, to) {
function courseDelta(from, to) {
let delta = ((to - from + 540) % 360) - 180;
if (delta === -180) delta = 180;
return delta;
@@ -259,7 +229,7 @@ export function attachFloorToPlace(entry, fix, heightM) {
correctHeight(entry.track, scratch.seq, heightM);
}
}
export function displayCourse(entry, nowMs) {
function displayCourse(entry, nowMs) {
if (!entry) return null;
const target = Number.isFinite(entry.sample?.segmentCourseDeg)
? entry.sample.segmentCourseDeg
+1 -1
View File
@@ -9,7 +9,7 @@ import {
displacementPlausible,
} from './movement.js';
import { buildTransitSelectionCopy } from './policy.js';
import { seek, sampleAt } from '../../data/contactPlayback.js';
import { sampleAt } from '../../data/contactPlayback.js';
const entry = () => {
const e = { mode: 'bus', modeInferred: false, courseDeg: null };
initializePlayback(e);
+7 -7
View File
@@ -99,7 +99,7 @@ export const VISIBILITY_REFRESH_MS = 250;
* on screen. Generous on purpose: a vehicle that crosses the edge mid-glide
* should already have been moving, not pop into motion at the border.
*/
export const VIEW_MARGIN_RATIO = 0.6;
const VIEW_MARGIN_RATIO = 0.6;
/**
* Whether a vehicle is close enough to the camera's look-at area to be worth
@@ -516,10 +516,10 @@ export function createTransitSelectedOverlayEntry(key, position, copy, mode) {
}
/** Modes that ordinarily run below the street, drawn on the surface above themselves. */
export const UNDERGROUND_MODES = new Set(['subway']);
const UNDERGROUND_MODES = new Set(['subway']);
/** The mode in plain words, for a card a person reads rather than scans. */
export const TRANSIT_MODE_WORD = Object.freeze({
const TRANSIT_MODE_WORD = Object.freeze({
bus: msg('Bus'),
tram: msg('Tram'),
subway: msg('Subway'),
@@ -529,7 +529,7 @@ export const TRANSIT_MODE_WORD = Object.freeze({
});
/** Short, fixed-width mode word for the detection card's class field. */
export const TRANSIT_MODE_ABBR = Object.freeze({
const TRANSIT_MODE_ABBR = Object.freeze({
bus: 'BUS',
tram: 'TRAM',
subway: 'METRO',
@@ -588,7 +588,7 @@ export function transitDetectionId(record, feed = null) {
}
/** Longest route text the single-line detection label can carry. */
export const DETECTION_ID_MAX_CHARS = 14;
const DETECTION_ID_MAX_CHARS = 14;
/**
* Cut long route text with an ellipsis, so a reader can see it was cut.
@@ -600,7 +600,7 @@ export const DETECTION_ID_MAX_CHARS = 14;
* @param {string} text
* @returns {string} Uppercase, at most DETECTION_ID_MAX_CHARS characters.
*/
export function elideRouteText(text) {
function elideRouteText(text) {
const value = String(text || '').toUpperCase();
if (value.length <= DETECTION_ID_MAX_CHARS) return value;
return `${value.slice(0, DETECTION_ID_MAX_CHARS - 1)}\u2026`;
@@ -659,6 +659,6 @@ export function transitDetectionMetric(
* @param {number} [nowMs]
* @returns {string}
*/
export function transitVehicleState(entry, nowMs = Date.now()) {
function transitVehicleState(entry, nowMs = Date.now()) {
return displayMotion(entry, nowMs).word;
}
-1
View File
@@ -22,7 +22,6 @@ import {
import {
MARKER_PIXEL_SIZE,
ROTATION_REFRESH_MS,
SELECTED_CARD_REFRESH_MS,
VISIBILITY_REFRESH_MS,
SELECTED_PIXEL_SIZE,
TRANSIT_MODE_COLORS,