@inventure71/paddockjs 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -1
- package/INSTALL_AND_UPDATE.md +10 -1
- package/README.md +65 -15
- package/docs/architecture.md +17 -13
- package/docs/component_inventory.md +72 -0
- package/docs/concepts.md +2 -2
- package/docs/custom_model_controller.md +2 -2
- package/docs/data_contract.md +96 -21
- package/docs/index.md +2 -1
- package/docs/learnings.md +6 -6
- package/docs/rules.md +7 -7
- package/docs/sense_contract.md +1 -1
- package/docs/system_specs.md +25 -18
- package/docs/training.md +13 -12
- package/package.json +1 -1
- package/src/api/PaddockSimulatorController.js +74 -4
- package/src/app/F1SimulatorApp.js +191 -28
- package/src/app/banners/raceDataBannerController.js +71 -36
- package/src/app/banners/stewardMessageController.js +5 -4
- package/src/app/camera/cameraConstants.js +2 -0
- package/src/app/camera/cameraController.js +86 -29
- package/src/app/camera/cameraGeometry.js +12 -1
- package/src/app/domBindings.js +89 -9
- package/src/app/layoutScheduler.js +27 -0
- package/src/app/layoutSupport.js +165 -0
- package/src/app/raceOverlayClearanceSupport.js +158 -0
- package/src/app/readouts/carOverviewRenderer.js +16 -8
- package/src/app/readouts/controlStateRenderer.js +1 -1
- package/src/app/readouts/raceStatusRenderer.js +30 -21
- package/src/app/readouts/readoutFormatters.js +2 -0
- package/src/app/readouts/telemetryRenderer.js +17 -8
- package/src/app/readouts/timingTowerRenderer.js +62 -10
- package/src/app/runtime/runtimeVisibility.js +66 -5
- package/src/config/cssValues.js +16 -0
- package/src/config/defaultOptions.js +51 -38
- package/src/config/themeOptions.js +825 -0
- package/src/config/timingGapMode.js +13 -0
- package/src/config/urlOptions.js +25 -0
- package/src/data/championship.js +14 -9
- package/src/environment/controllerLoop.js +17 -3
- package/src/environment/index.d.ts +1 -2
- package/src/environment/options.js +1 -1
- package/src/environment/runtime.js +3 -11
- package/src/environment/scenarios.js +1 -1
- package/src/environment/sensors/surfaceRays.js +41 -33
- package/src/environment/sensors/trackRays.js +44 -49
- package/src/index.d.ts +110 -9
- package/src/index.js +25 -1
- package/src/simulation/driver/driverController.js +1 -1
- package/src/simulation/driver/edgeRecovery.js +1 -1
- package/src/simulation/driver/racingControls.js +2 -2
- package/src/simulation/driver/rejoinControls.js +2 -2
- package/src/simulation/pit/pitKinematics.js +2 -2
- package/src/simulation/pit/pitRouting.js +2 -4
- package/src/simulation/race/raceSetup.js +2 -6
- package/src/simulation/race/raceStep.js +1 -1
- package/src/simulation/race/safetyCar.js +1 -1
- package/src/simulation/race/stalledDnf.js +1 -1
- package/src/simulation/timing/lapTelemetry.js +28 -9
- package/src/simulation/track/pitLaneLayout.js +7 -3
- package/src/simulation/track/spatialQueries.js +5 -28
- package/src/simulation/track/trackQueryIndex.js +180 -135
- package/src/simulation/track/trackStatePolicy.js +0 -1
- package/src/simulation/vehicle/contactResolution.js +3 -3
- package/src/simulation/vehicle/vehicleKinematics.js +2 -2
- package/src/simulation/vehicle/vehiclePhysics.js +3 -3
- package/src/simulation/vehicle/vehicleSnapshots.js +12 -1
- package/src/simulation/vehicle/vehicleState.js +1 -1
- package/src/simulation/warmup/runtimeWarmup.js +0 -1
- package/src/styles.css +1185 -136
- package/src/ui/bannerTemplates.js +38 -28
- package/src/ui/cameraControlsTemplate.js +16 -5
- package/src/ui/carOverviewTemplate.js +12 -5
- package/src/ui/raceCanvasTemplate.js +31 -8
- package/src/ui/raceControlsTemplate.js +27 -10
- package/src/ui/shellTemplate.js +3 -1
- package/src/ui/telemetryDrawerTemplate.js +23 -10
- package/src/ui/telemetryTemplates.js +58 -32
- package/src/ui/templateUtils.js +48 -0
- package/src/ui/timingTowerTemplate.js +29 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- Add the semantic theme customization system. Themes may be partial, but resolved themes are complete light/dark packages; one-sided light/dark token overrides generate and cache the opposite mode, derivative theme packages cannot invent unknown tokens, component/team theme selectors are supported, and theme plus driver/team colors are validated before becoming CSS variables.
|
|
8
|
+
- Make indexed track queries canonical and internal. Browser mounts, headless environments, Policy Runner, model-sense visualization, and hot simulation paths now use the package-owned index, while unsuitable edge cases stay behind focused internal fallback integrations. The public `trackQueryIndex` option is removed.
|
|
9
|
+
- Add the playable keyboard demo and pit controls. The local preview now exposes a playable route with steering/throttle/brake controls plus pit request, commit, clear, and target compound commands through the normal expert action fields.
|
|
10
|
+
- Finalize the opt-in `driver` camera. Hosts can enable the control with `ui.driverCamera: true` or start directly in it with `initialCameraMode: 'driver'`; the camera follows the selected car from a lower screen anchor, rotates the world around the car heading, respects zoom bounds, and falls back safely when no selected car or snapshot is available.
|
|
11
|
+
- Keep `car.trackState` as a documented public snapshot shape with stable car-center track classification fields, and make package readouts tolerate partial startup or external-renderer snapshots without throwing.
|
|
12
|
+
|
|
13
|
+
### Fixes And Hardening
|
|
14
|
+
|
|
15
|
+
- Sanitize public URL-like options such as `backLinkHref` so unsafe schemes cannot become clickable package-owned links.
|
|
16
|
+
- Sanitize runtime snapshot colors and escape package asset URLs before writing readout CSS values, so external-renderer or partial-snapshot data cannot inject inline style declarations.
|
|
17
|
+
- Harden track query correctness and performance with deterministic indexed lookup coverage, progress hints as optimization-only inputs, and benchmark coverage for hot query paths.
|
|
18
|
+
- Preserve scheduler cancellation behavior in `createPaddockDriverControllerLoop()` so stopped scheduled playback cancels package-owned and custom scheduler handles cleanly.
|
|
19
|
+
|
|
20
|
+
### Migration Notes
|
|
21
|
+
|
|
22
|
+
- Remove any host usage of `trackQueryIndex`; indexed queries are always canonical/internal in 3.0.0.
|
|
23
|
+
- Rename the strict physics mode from `physicsMode: 'simulator'` to `physicsMode: 'advanced'`. The old string is no longer accepted and falls back to the default `physicsMode: 'arcade'`.
|
|
24
|
+
- Prefer the semantic `theme` contract over one-off color aliases. Legacy aliases such as `accentColor`, `greenColor`, and `yellowColor` still map to semantic tokens for migration.
|
|
25
|
+
- Use `ui.driverCamera: true` when the generated camera controls should expose the Driver button, or `initialCameraMode: 'driver'` when the simulator should start in driver camera mode.
|
|
26
|
+
- `backLinkHref` now accepts only relative URLs, hash URLs, and absolute `http:` / `https:` URLs; unsafe or malformed values fall back to the package default.
|
|
27
|
+
|
|
28
|
+
### Verification Notes
|
|
29
|
+
|
|
30
|
+
- The normal package handoff gate is `npm run check`.
|
|
31
|
+
- The final release gate is `npm run check:release`, which adds slow characterization coverage and the full browser smoke matrix.
|
|
32
|
+
|
|
3
33
|
## 2.0.1
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
|
@@ -11,7 +41,7 @@
|
|
|
11
41
|
### Major Changes
|
|
12
42
|
|
|
13
43
|
- Promote the `v6` simulator work as the next major PaddockJS release. This release keeps the package boundary focused on browser-mounted simulator components, composable simulator surfaces, and browser-free JavaScript environment/control APIs.
|
|
14
|
-
- Add the stricter opt-in `physicsMode: '
|
|
44
|
+
- Add the stricter opt-in `physicsMode: 'advanced'` vehicle model with 2D velocity/yaw dynamics, traction limits, speed-sensitive steering, steering scrub, slip telemetry, reduced off-road grip, and advanced-mode AI tuning. The default remains `physicsMode: 'arcade'` for existing hosts.
|
|
15
45
|
- Add richer procedural track generation with semantic profiles (`race`, `training-short`, `training-medium`, and `training-technical`), validated connected-region centerlines, profile-level generation controls, deterministic `trackGeneration` options, DRS zones, pit-lane access geometry, and cached track query indexes.
|
|
16
46
|
- Add hard barrier-wall consequences and opt-in stalled off-track DNF handling. Barrier contact now marks cars destroyed/DNF in both physics modes, while `rules.modules.stalledDnf` can retire cars that remain stopped off legal surfaces.
|
|
17
47
|
- Expand the headless environment contract for real training loops: compact vector/object/full observation output, optional `Float32Array` vectors, schema caching, no-state/minimal/full result output, per-driver episode info, selected-driver resets, reset-only scenario placements, reward normalization, evaluation helpers, rollout metrics, and a shared driver-controller loop.
|
package/INSTALL_AND_UPDATE.md
CHANGED
|
@@ -112,12 +112,21 @@ npm run check
|
|
|
112
112
|
Install a specific version when the host needs a controlled upgrade:
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
|
-
npm install @inventure71/paddockjs@
|
|
115
|
+
npm install @inventure71/paddockjs@3.0.0
|
|
116
116
|
npm run check
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
After updating, smoke-test the page that mounts the simulator. Browser behavior changes should be checked in the consuming host, because host CSS, container size, and route handling are outside the package.
|
|
120
120
|
|
|
121
|
+
### 3.0.0 Migration Notes
|
|
122
|
+
|
|
123
|
+
- Remove any `trackQueryIndex` host option. Indexed track queries are always canonical package internals in 3.0.0.
|
|
124
|
+
- Rename strict vehicle-physics usage from `physicsMode: 'simulator'` to `physicsMode: 'advanced'`. The old string is no longer accepted and will resolve to the default `physicsMode: 'arcade'`.
|
|
125
|
+
- Prefer the semantic `theme` contract for new customization. Partial themes are valid, resolved themes are complete, and one-sided light/dark token overrides generate and cache the opposite mode. Legacy aliases such as `accentColor`, `greenColor`, and `yellowColor` remain migration aliases.
|
|
126
|
+
- Use `ui.driverCamera: true` to show the generated Driver camera button, or `initialCameraMode: 'driver'` to start in that mode.
|
|
127
|
+
- `backLinkHref` is sanitized; relative URLs, hash URLs, and absolute `http:` / `https:` URLs are accepted, while unsafe schemes fall back to the package default.
|
|
128
|
+
- Full public snapshots include `car.trackState` for the stable car-center track classification shape.
|
|
129
|
+
|
|
121
130
|
## Package Release Workflow
|
|
122
131
|
|
|
123
132
|
The package repo owns its release process:
|
package/README.md
CHANGED
|
@@ -40,9 +40,11 @@ npm run changeset
|
|
|
40
40
|
|
|
41
41
|
Local development and showcase builds require Node `20.19.0` or newer. CI currently runs the package check on Node 22 and releases on Node 24.
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## 3.0 Release Highlights
|
|
44
44
|
|
|
45
|
-
The `
|
|
45
|
+
The `3.0.0` release line adds semantic theme customization with complete resolved light/dark themes, reusable derivative theme packages, component/team theme selectors, cached opposite-mode color generation, and validated color tokens. Indexed track queries are now canonical/internal, so hosts should remove any `trackQueryIndex` option usage. The local preview adds first-class Customization and playable keyboard routes, including theme package switching, selected-team theming, component overrides, pit request/commit/clear, and target-compound controls. The public camera contract includes the opt-in `driver` camera, and full public car snapshots document `car.trackState` as the stable car-center track classification shape.
|
|
46
|
+
|
|
47
|
+
The release also hardens public URL handling for options such as `backLinkHref`, keeps package readouts safe for partial startup or external-renderer snapshots, preserves scheduler cancellation behavior in `createPaddockDriverControllerLoop()`, and keeps the release workflow centered on `npm run check` plus the final `npm run check:release` gate.
|
|
46
48
|
|
|
47
49
|
The package still does not ship trained model weights, model storage, a Python Gymnasium/PettingZoo package, static obstacles, weather, reliability failures, fuel-load effects, or debug mutation APIs. Those remain host-owned or future scope.
|
|
48
50
|
|
|
@@ -57,7 +59,7 @@ import { createPaddockEnvironment } from '@inventure71/paddockjs/environment';
|
|
|
57
59
|
The package root remains the browser component API. The environment subpath is intentionally browser-free and does not import DOM, PixiJS, or package CSS.
|
|
58
60
|
PaddockJS is a bring-your-own-model environment. It does not choose an ML framework, store model weights, or ship a trained driver. The shared `createPaddockDriverControllerLoop()` helper lets a user-owned controller run against either browser expert mode or `createPaddockEnvironment()` with the same batched `decideBatch(context)` call shape.
|
|
59
61
|
|
|
60
|
-
Browser and headless environments default to `physicsMode: 'arcade'` for existing hosts. Opt into the stricter vehicle model with `physicsMode: '
|
|
62
|
+
Browser and headless environments default to `physicsMode: 'arcade'` for existing hosts. Opt into the stricter vehicle model with `physicsMode: 'advanced'` when you want 2D velocity/yaw dynamics, traction limits, steering scrub, derived slip telemetry, reduced off-road grip, and advanced-mode AI tuning:
|
|
61
63
|
|
|
62
64
|
```js
|
|
63
65
|
const env = createPaddockEnvironment({
|
|
@@ -65,7 +67,7 @@ const env = createPaddockEnvironment({
|
|
|
65
67
|
entries,
|
|
66
68
|
controlledDrivers: ['budget'],
|
|
67
69
|
frameSkip: 2,
|
|
68
|
-
physicsMode: '
|
|
70
|
+
physicsMode: 'advanced',
|
|
69
71
|
});
|
|
70
72
|
|
|
71
73
|
let result = env.reset();
|
|
@@ -104,7 +106,7 @@ The starter script imports the public `@inventure71/paddockjs/environment` subpa
|
|
|
104
106
|
|
|
105
107
|
Each default ray reports track-transition distance and car distance. A track hit uses `kind: 'exit'` when the ray leaves the road and `kind: 'entry'` when an off-track ray points back to the road. Richer sensor layouts are opt-in: rays can use per-ray lengths, predefined layouts such as `driver-front-heavy`, channels for `roadEdge`, `kerb`, `illegalSurface`, and `car`, and `precision: 'driver' | 'debug'`. Driver precision is the default model-facing contract; debug precision is only for clearly labeled diagnostics. Surface channels are computed only when requested. Barrier walls are rendered and enforced as hard destruction boundaries in both physics modes, but they are not model-facing ray targets. `observation.object.self.onTrack` follows the simulator's wheel-level legality rules, so track, kerb, and legal pit-lane/box surfaces are on-track for reward and observation purposes. `pitIntent: 0` is always accepted as the no-op clear value, including environments where pit stops are disabled.
|
|
106
108
|
|
|
107
|
-
For realistic local-perception policies, use `physicsMode: '
|
|
109
|
+
For realistic local-perception policies, use `physicsMode: 'advanced'` with `observation.profile: 'physical-driver'`. It exposes yaw rate, local boundary distances, contact-patch surface readings, richer opponent radar, and surface-aware ray fields in the versioned vector schema. The profile defaults track lookahead to `[]` so the policy does not receive privileged future curvature unless the host explicitly opts back in.
|
|
108
110
|
|
|
109
111
|
External training code can inspect the environment contract without guessing field ranges:
|
|
110
112
|
|
|
@@ -163,7 +165,7 @@ const env = createPaddockEnvironment({
|
|
|
163
165
|
drivers,
|
|
164
166
|
entries,
|
|
165
167
|
controlledDrivers: agentIds,
|
|
166
|
-
physicsMode: '
|
|
168
|
+
physicsMode: 'advanced',
|
|
167
169
|
participantInteractions: { defaultProfile: 'batch-training' },
|
|
168
170
|
observation: {
|
|
169
171
|
profile: 'physical-driver',
|
|
@@ -216,6 +218,7 @@ const env = createPaddockEnvironment({
|
|
|
216
218
|
`isolated-training` is the no-collision profile for real cars that should still remain in race order. `batch-training` is the preferred no-collision profile for same-environment learner batches because it is also excluded from race order. Both profiles hide the car from other cars' ray sensors and `nearbyCars` observations by default; use `phantom-race` or explicit `detectableByRays` / `detectableAsNearby` overrides only when sensor visibility is intentional.
|
|
217
219
|
|
|
218
220
|
Browser expert mode is opt-in through the normal mount API. When enabled, the visual simulator advances only when host code calls `simulator.expert.step(actions)`. Expert mode is a mount-time boundary; changing `expert` through `restart(nextOptions)` is rejected so ticker ownership cannot silently change under a mounted simulator.
|
|
221
|
+
Browser expert mode uses the same episode lifecycle as the headless environment. The default episode horizon is a high safety cap (`1_000_000` expert steps, roughly 4.6 hours at 60Hz with `frameSkip: 1`) so normal user-facing sessions should end by race finish, not by the safety cap. Training and evaluation loops should set a smaller explicit `expert.episode.maxSteps` or `episode.maxSteps` when they need bounded rollouts; `episode.endOnRaceFinish` keeps normal race completion as the terminal condition.
|
|
219
222
|
Set `expert.visualizeSensors` to draw expert sensor rays inside the actual race canvas for visual debugging:
|
|
220
223
|
|
|
221
224
|
```js
|
|
@@ -343,7 +346,7 @@ const simulator = createPaddockSimulator({
|
|
|
343
346
|
});
|
|
344
347
|
```
|
|
345
348
|
|
|
346
|
-
`drivers` is the host-owned project/pilot list. `entries` is the optional driver/car/team pairing sheet. The car/driver overview uses the existing driver and vehicle rating components from each entry. Driver and vehicle entries can also include `customFields` as extra label/value metadata. Entries can include `team: { id, name, color, icon, pitCrew }`; the timing tower uses the team icon
|
|
349
|
+
`drivers` is the host-owned project/pilot list. `entries` is the optional driver/car/team pairing sheet. The car/driver overview uses the existing driver and vehicle rating components from each entry. Driver and vehicle entries can also include `customFields` as extra label/value metadata. Entries can include `team: { id, name, color, icon, theme, pitCrew }`; the timing tower uses the team icon, defaults team color to the car color when omitted, and can map `team.theme` into the semantic theme system. `team.pitCrew` accepts `speed`, `consistency`, and `reliability` values from `0` to `1` for optional pit-service variability. Assets, including the default car image and generic driver helmet, are bundled by default, so the host website does not need to provide simulator images or textures.
|
|
347
350
|
|
|
348
351
|
The returned object supports:
|
|
349
352
|
|
|
@@ -360,17 +363,39 @@ The returned object supports:
|
|
|
360
363
|
- `getPitIntent(driverId)`
|
|
361
364
|
- `getPitTargetCompound(driverId)`
|
|
362
365
|
- `getSimulationSpeed()`
|
|
366
|
+
- `setTimingGapMode(mode)`
|
|
367
|
+
- `getTimingGapMode()`
|
|
368
|
+
- `toggleTimingGapMode()`
|
|
363
369
|
- `servePenalty(penaltyId)`
|
|
364
370
|
- `cancelPenalty(penaltyId)`
|
|
365
371
|
- `getSnapshot()`
|
|
366
372
|
- `expert` when explicitly enabled, otherwise `null`
|
|
367
373
|
|
|
374
|
+
Timing gap mode is controller-owned, not tied to whether the manual tower toggle is visible. `setTimingGapMode('interval')` makes timing rows show interval to the car ahead, `setTimingGapMode('leader')` makes them show gap to P1, and `toggleTimingGapMode()` switches between those two values. These methods can be called after mounting or while the race is running; mounted timing towers update immediately and keep the same selected driver/order state.
|
|
375
|
+
|
|
368
376
|
Useful UI options:
|
|
369
377
|
|
|
370
378
|
```js
|
|
371
379
|
preset: 'timing-overlay',
|
|
372
380
|
theme: {
|
|
373
|
-
|
|
381
|
+
mode: 'system',
|
|
382
|
+
use: 'trackside',
|
|
383
|
+
tokens: {
|
|
384
|
+
primary: { light: '#008c55', dark: '#00ff84' },
|
|
385
|
+
pitLane: '#7c3aed',
|
|
386
|
+
},
|
|
387
|
+
themes: {
|
|
388
|
+
trackside: {
|
|
389
|
+
extends: 'default',
|
|
390
|
+
tokens: { yellowFlag: { dark: '#ffcc00' } },
|
|
391
|
+
components: {
|
|
392
|
+
button: { background: 'pitLane', text: 'primaryText', border: 'primary' },
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
componentThemes: {
|
|
397
|
+
'race-controls': 'trackside',
|
|
398
|
+
},
|
|
374
399
|
timingTowerMaxWidth: '360px',
|
|
375
400
|
raceViewMinHeight: '720px',
|
|
376
401
|
},
|
|
@@ -387,13 +412,31 @@ ui: {
|
|
|
387
412
|
raceDataBannerSize: 'auto',
|
|
388
413
|
raceDataTelemetryDetail: true,
|
|
389
414
|
timingTowerVerticalFit: 'expand-race-view',
|
|
415
|
+
timingGapMode: 'interval',
|
|
416
|
+
timingGapModeToggle: true,
|
|
417
|
+
timingEntryVerticalPadding: 5,
|
|
390
418
|
},
|
|
391
419
|
debug: {
|
|
392
420
|
physicsModeIndicator: false,
|
|
393
421
|
}
|
|
394
422
|
```
|
|
395
423
|
|
|
396
|
-
`preset` is resolved before explicit host options. Available presets are `dashboard`, `timing-overlay`, `compact-race`, and `full-dashboard`; hosts can start from a preset and override any `ui`, `debug`, or `theme` field. `debug.physicsModeIndicator: true` renders a small top-left race-canvas square: blue for arcade physics and red for
|
|
424
|
+
`preset` is resolved before explicit host options. Available presets are `dashboard`, `timing-overlay`, `compact-race`, and `full-dashboard`; hosts can start from a preset and override any `ui`, `debug`, or `theme` field. `debug.physicsModeIndicator: true` renders a small top-left race-canvas square: blue for arcade physics and red for advanced physics. It defaults to `false` for package consumers and is intended only for debug/development use. `theme` resolves semantic package tokens into complete light/dark themes, can define reusable named theme packages with `extends`, and can assign a named theme to component scopes such as `race-controls` through `componentThemes`. Component theme keys accept package `data-paddock-component` names and camelCase aliases such as `raceControls` or `timingTower`; `selectedDriverPanel` targets both selected-driver package surfaces. Selected-driver surfaces such as the car/driver overview and race-data panel use the selected team's theme by default when one is available. Unknown theme tokens or component slots are ignored, one-sided light/dark token overrides generate and cache the opposite mode, and theme plus driver/team colors are validated before they are written to CSS variables. Legacy aliases such as `accentColor`, `greenColor`, and `yellowColor` still map to the semantic token system for migration.
|
|
425
|
+
|
|
426
|
+
### Layout Support Contract
|
|
427
|
+
|
|
428
|
+
PaddockJS supports the all-in-one shell and host mount roots in containers at least `320px` wide. Hosts should size the outer mount root with normal responsive CSS, for example `width: 100%; min-width: 0;`, and avoid adding host rules that target package internals such as `.sim-grid`, `.sim-timing`, or `.race-data-panel`. The host owns page placement; PaddockJS owns the shell, timing-board width, camera safe area, lower-thirds, drawer behavior, focus styles, and touch target sizing. Package-owned subcomponents can have smaller internal widths when their own layout is designed for it; for example, the broadcast timing tower remains valid at its package-owned narrow width. Responsive narrow template variants are enabled by default and can be disabled with `ui.responsiveNarrowLayout: false` or the matching composable mount option when a host intentionally wants the older stacked timing/drawer behavior.
|
|
429
|
+
|
|
430
|
+
The package uses container-width density tiers:
|
|
431
|
+
|
|
432
|
+
- `320px` to `519px`: mobile density. Controls wrap, embedded timing towers stop reserving a side gutter and use the package timing reveal, telemetry drawers reveal over the race view, and lower-thirds compress inside the race view. The lower-third may cover empty timing-tower chrome, but if it would cover any timing entries the race view adds package-owned banner clearance to its minimum height. If the entries remain clear, no extra banner height is reserved.
|
|
433
|
+
- `520px` to `759px`: narrow tablet density. Race surfaces keep readable controls while standalone telemetry and overview modules collapse to single-column layouts.
|
|
434
|
+
- `760px` to `1119px`: tablet density. Side-by-side host sections may still give an individual component a narrow container, so package components use their own container width rather than viewport width.
|
|
435
|
+
- `1120px` and wider: desktop density. The broadcast timing tower can sit in the left race gutter at its package-owned width, and telemetry/drawer layouts use wider horizontal space.
|
|
436
|
+
|
|
437
|
+
If the shell/mount root receives less than `320px` inline size, or an individual package component is constrained below its component-specific minimum, PaddockJS marks it with `data-paddock-size-unsupported="true"` and shows an accessible `Unsupported size` placeholder. That is intentional: below the support envelope, the package prefers a clear placeholder over clipped controls, horizontal scrolling, or overlapping race UI. The placeholder is package-owned and uses `role="status"` with polite live-region behavior.
|
|
438
|
+
|
|
439
|
+
Package controls, drawer toggles, lower-third actions, and close buttons maintain practical `44px` touch targets. Timing rows are compact selectable broadcast entries with configurable vertical padding. Touch driving controls are not part of the package; the playable preview remains keyboard/expert-action driven and does not add a virtual joystick.
|
|
397
440
|
|
|
398
441
|
If `trackSeed` is omitted, each mounted browser simulator creates a fresh procedural circuit. Passing `trackSeed` makes the track deterministic so multiple embeds can share the same generated circuit; repeated procedural seeds are cached within the page runtime as immutable track definitions. Treat values returned by `createProceduralTrack()` as read-only and pass custom mutable copies when experimenting with track-definition edits. `restart({ trackSeed })` rebuilds the race on the deterministic circuit for the new seed. Asset URL changes are not restartable; destroy and mount a new simulator when changing assets.
|
|
399
442
|
|
|
@@ -523,12 +566,14 @@ const simulator = await mountF1Simulator(root, {
|
|
|
523
566
|
|
|
524
567
|
For deterministic single-skill training or visual checkpoint comparison, set `rules.modules.tireDegradation.enabled: false` so tyre energy remains fixed while the car still drives through normal steering, throttle, brake, and surface physics. `rules.modules.stalledDnf` defaults to `enabled: false` so base simulator and training environments keep stuck off-track cars live unless a host opts in. Set `enabled: true` to retire cars that are off legal racing or pit surfaces while below `speedThresholdKph` for `maxStoppedSeconds`.
|
|
525
568
|
|
|
526
|
-
Supported rulesets are `paddock`, `grandPrix2025`, `fia2025`, and `custom`. Presets only choose defaults; explicit module config wins. Weather, reliability, and fuel-load performance effects are reserved future modules and are not active `
|
|
569
|
+
Supported rulesets are `paddock`, `grandPrix2025`, `fia2025`, and `custom`. Presets only choose defaults; explicit module config wins. Weather, reliability, and fuel-load performance effects are reserved future modules and are not active `3.0.0` behavior. Penalty subsections use `strictness` from `0` to `1` instead of plain booleans. Track limits use the white line as the legal edge and require all four wheel contact patches to be fully outside the same side of the line before recording a violation, so normal kerb riding is not punished. Per-car `surface` is resolved from the worst wheel surface, snapshots include `car.wheels` for per-wheel surface and white-line state, and asymmetric left/right wheel resistance adds a small capped yaw tug toward the slower side when only one side is on a worse surface. Collision stewarding is driven by a body collision hull, not transparent sprite bounds or wheel-only overlap, and contact events include shape ids, contact type, depth, and time of impact. It considers impact severity, closing speed, and whether one car clearly hit another from behind; clear rear contact penalizes only the physically trailing car, including lapped traffic cases, while unclear meaningful contact records shared-fault penalties for both cars. Stalled off-track DNFs emit `car-dnf` with `reason: 'stalled-off-track'`, freeze the car, remove it from active control/collision/sensor/pit participation, and classify it with the existing DNF metadata. Pit-lane speeding is enforced on the main fast lane, working lane, service areas, and garage boxes, but not on pit-entry or pit-exit connector roads. Track-limit warnings are emitted as `track-limits` events, while penalty decisions are exposed through `snapshot.penalties` plus `penalty` events. Penalty consequences support warning, time, drive-through, stop-go, position-drop, grid-drop, and disqualification payloads. Time consequences are additive, drive-through and stop-go penalties are service obligations, and unserved service penalties convert to configured time at final classification.
|
|
527
570
|
|
|
528
|
-
`initialCameraMode` accepts `'overview'`, `'leader'`, `'selected'`, `'show-all'`, or `'pit'`; invalid values fall back to `'leader'`. The overview camera frames the active generated track bounds, including package-owned track padding and pit-lane extent, instead of using a fixed world-center zoom. Camera mode changes ease from the current camera target to the next target after the initial frame, so switching between leader, selected, overview, show-all, and pit views does not snap the world view.
|
|
571
|
+
`initialCameraMode` accepts `'overview'`, `'leader'`, `'selected'`, `'driver'`, `'show-all'`, or `'pit'`; invalid values fall back to `'leader'`. The overview camera frames the active generated track bounds, including package-owned track padding and pit-lane extent, instead of using a fixed world-center zoom. Camera mode changes ease from the current camera target to the next target after the initial frame, so switching between leader, selected, driver, overview, show-all, and pit views does not snap the world view.
|
|
529
572
|
|
|
530
573
|
The pit camera targets the generated operational pit-lane geometry, including the lane, boxes, service areas, and queue areas, while excluding the longer entry/exit access roads. It zooms out as needed to fit that work area inside the active race-view safe area, and its control is hidden when the active track has no pit lane.
|
|
531
574
|
|
|
575
|
+
The driver camera follows the selected car from a lower screen anchor and rotates the world so the car's heading points upward. Its control is opt-in through `ui.driverCamera: true`, and setting `initialCameraMode: 'driver'` enables that control automatically.
|
|
576
|
+
|
|
532
577
|
Zoom buttons and wheel zoom apply to every camera mode. Zoom-out is bounded by the active generated track frame so the camera cannot pull far beyond the circuit. The renderer keeps grass coverage larger than the simulated world, and the transparent canvas host uses the same grass color as a fallback around the framed circuit.
|
|
533
578
|
|
|
534
579
|
`layoutPreset: 'left-tower-overlay'` creates a left broadcast gutter inside the race view, frames the camera around the remaining race area, and places the timing tower in that gutter without covering camera controls. The project/radio lower-third stays inside the race window and can intentionally render over the timing sidebar instead of shrinking around it.
|
|
@@ -537,9 +582,9 @@ Composable hosts should pass `{ includeRaceDataPanel: true }` to `mountRaceCanva
|
|
|
537
582
|
|
|
538
583
|
`raceDataTelemetryDetail: true` makes the project lower-third include a compact S1/S2/S3 sector strip with live sector elapsed time and per-sector progress. That telemetry project lower-third stays visible until dismissed, muted, or replaced, while radio mode keeps its normal schedule. The standalone `mountTelemetrySectorBanner()` surface remains available only when a host explicitly mounts it.
|
|
539
584
|
|
|
540
|
-
`includeTimingTower: true` embeds the timing tower directly inside the race canvas and reserves camera space from the measured tower gutter when the tower is a side overlay. In narrow
|
|
585
|
+
`includeTimingTower: true` embeds the timing tower directly inside the race canvas and reserves camera space from the measured tower gutter when the tower is a side overlay. In narrow hosts, the embedded timing tower becomes a package-owned left reveal panel opened by the race view's Timing control instead of taking permanent height above the track. The control updates `aria-expanded`, the closed tower is inert/hidden to assistive tech, and the renderer/camera keep resizing through the transition without remounting the simulator. This responsive narrow behavior is enabled by default; pass `responsiveNarrowLayout: false` to `mountRaceCanvas()` or set `ui.responsiveNarrowLayout: false` to opt out.
|
|
541
586
|
|
|
542
|
-
`mountRaceTelemetryDrawer()` is a higher-level template that mounts an external top control row, race canvas, embedded timing tower, lower-third, top steward message, safety-car control, and a
|
|
587
|
+
`mountRaceTelemetryDrawer()` is a higher-level template that mounts an external top control row, race canvas, embedded timing tower, lower-third, top steward message, safety-car control, and a telemetry drawer together. Pass `{ raceDataTelemetryDetail: true }` when the drawer lower-third should include compact sector detail. On narrow hosts the responsive template is enabled by default: the top workbench controls stack into full-width rows, the safety-car and telemetry actions become a two-column row, the telemetry drawer reveals over the race view instead of forcing the race canvas to reserve permanent drawer height, and the race view measures lower-third/timing-entry geometry before adding extra vertical clearance. Pass `{ responsiveNarrowLayout: false }` to opt out.
|
|
543
588
|
|
|
544
589
|
The drawer top row contains camera controls, a `1x` simulation-speed button that cycles through `2x`, `3x`, `4x`, `5x`, `10x`, and back to `1x`, a `Mute banners` toggle, safety car, and the telemetry toggle so those controls do not cover the race. For other camera-control placements the speed button is hidden by default and can be enabled with `ui.simulationSpeedControl: true`.
|
|
545
590
|
|
|
@@ -553,10 +598,15 @@ Banner and timing layout options:
|
|
|
553
598
|
- `raceDataBannerSize: 'custom'` preserves the default CSS-variable-driven lower-third size for hosts that want to tune their own banner geometry.
|
|
554
599
|
- `timingTowerVerticalFit: 'expand-race-view'` lets the race window grow tall enough for the tower.
|
|
555
600
|
- `timingTowerVerticalFit: 'scroll'` keeps the race window height and scrolls the timing list inside the cropped tower.
|
|
601
|
+
- `timingGapMode: 'interval'` starts the timing tower in `Int` mode, showing interval to the car ahead. Use `'leader'` to start in `Gap` mode, showing the total gap to P1.
|
|
602
|
+
- `timingGapModeToggle: true` shows the compact timing-tower header toggle by default. Set it to `false` when the host should own mode changes through `setTimingGapMode()`, `getTimingGapMode()`, or `toggleTimingGapMode()`.
|
|
603
|
+
- `timingEntryVerticalPadding: 5` adds `5px` above and below each timing entry. Set one non-negative number to change all entry rows together.
|
|
604
|
+
|
|
605
|
+
The same timing fit values can be passed to `mountRaceCanvas()` when `includeTimingTower` is enabled. Standalone timing towers are capped by `--timing-board-max-width` and fill their mount root height; placing the root in a fixed-height container makes only the timing entries scroll. Timing entries always stack from the top as compact content-sized rows, so P1/P2 occupy the same vertical positions whether the race has 2 cars or 20. Use `timingEntryVerticalPadding` when the tower needs looser or denser row spacing.
|
|
556
606
|
|
|
557
|
-
The
|
|
607
|
+
The timing tower defaults to `Int` mode and includes a single compact `Int`/`Gap` header toggle unless `ui.timingGapModeToggle: false` is set. The toggle lives in the timing header's gap column and is package-owned: it keeps the same header alignment as `POS`, `TEAM`, `PROJECT`, and `TYRE`, uses a practical `44px` hit target, and draws selected/focus states inside the tower header so the broadcast proportions do not shift. Hosts should not replace this with custom CSS against package internals. If a host needs a different control placement, hide the header toggle and call the controller methods from host-owned UI instead.
|
|
558
608
|
|
|
559
|
-
|
|
609
|
+
Hosts can set the initial mode with `ui.timingGapMode` and can change it at any time with `setTimingGapMode('interval' | 'leader')`, `getTimingGapMode()`, or `toggleTimingGapMode()`. `Int` shows the interval to the car ahead, while `Gap` shows total gap to the leader. Seconds gaps are measured from hidden fixed timing-line crossings on the track. If a car is one or more whole laps behind in the selected mode, the tower shows labels such as `+1` or `+2` instead of a seconds estimate.
|
|
560
610
|
|
|
561
611
|
Other UI switches:
|
|
562
612
|
|
package/docs/architecture.md
CHANGED
|
@@ -108,7 +108,7 @@ Responsibilities:
|
|
|
108
108
|
- `options.js` validates `controlledDrivers`, scenario participants, reset placement config, frame skip, action policy, sensor config, and episode limits.
|
|
109
109
|
- `actions.js` maps normalized public controls onto simulator steering/throttle/brake controls plus pit intent and optional target compound requests. It treats steering, throttle, and brake as required vehicle-control fields so broken model output is reported instead of silently becoming zeroed controls.
|
|
110
110
|
- `scenarios.js` owns environment reset placement presets, absolute placements, and relative traffic layouts. It applies them through simulator state APIs during environment creation/reset only; it must not create policy-time teleport or assisted-control paths.
|
|
111
|
-
- `observations.js`, `observationVector.js`, `sensors.js`, and `events.js` build sensor-style observations, versioned fixed-schema vectors, track lookahead/curvature, ray/nearby-car readings, and global/per-driver events. `observations.js` owns object observation assembly and direct compact-source preparation, while `observationVector.js` is the single owner of model-facing vector order, schema entries, and typed-array formatting for both object-backed and direct vector paths. Observation output formatting is separate from object/vector construction so compact training loops can request vector-only results while `getObservationSpec()` remains the schema source. Vector-only mode uses a direct vector path and can return `Float32Array` buffers without building the full public object observation. When a loop requests vector-only, schema-free, no-state output, `runtime.js` uses the internal `snapshotTraining()` path instead of the heavier public observation snapshot; returned state still uses public snapshot shapes. Environment-owned simulations
|
|
111
|
+
- `observations.js`, `observationVector.js`, `sensors.js`, and `events.js` build sensor-style observations, versioned fixed-schema vectors, track lookahead/curvature, ray/nearby-car readings, and global/per-driver events. `observations.js` owns object observation assembly and direct compact-source preparation, while `observationVector.js` is the single owner of model-facing vector order, schema entries, and typed-array formatting for both object-backed and direct vector paths. Observation output formatting is separate from object/vector construction so compact training loops can request vector-only results while `getObservationSpec()` remains the schema source. Vector-only mode uses a direct vector path and can return `Float32Array` buffers without building the full public object observation. When a loop requests vector-only, schema-free, no-state output, `runtime.js` uses the internal `snapshotTraining()` path instead of the heavier public observation snapshot; returned state still uses public snapshot shapes. Environment-owned simulations always use the internal track query index because repeated track/surface/ray queries otherwise dominate step cost. `sensors.js` is a compatibility barrel; nearby-car scans, ray defaults, ray config normalization, track rays, surface rays, indexed ray-band intersections, car rays, body senses, contact-patch senses, boundary senses, opponent radar, and ray geometry live under `src/environment/sensors/`. Expensive surface-ray channels are opt-in and appear in the vector schema whenever requested. Replay-ghost sensor type flags are also part of compact ray/nearby-car vectors so vector-only consumers can distinguish real cars from opt-in references. Driver rays use a precision-aware cached origin state and shared per-observation ray target context. Driver `precision: 'driver'` road-edge, kerb, and illegal-surface channels use the canonical indexed projection path with driver-precision validation. Destroyed/out-of-race cars keep the active ray schema but short-circuit to miss-valued rays before creating track ray contexts, which prevents terminal cars in batch training from repeatedly querying far-out geometry. Pit-connector and unusual cases can still use sampled validation internally. Ray precision defaults to the model-facing `driver` contract; `debug` precision is diagnostic-only and must not be silently substituted into policy inputs or model-sense visualization.
|
|
112
112
|
- `metrics.js` owns reward-neutral per-driver facts such as progress delta, legal progress delta, off-track/severe-cut state, destroyed state, low-speed state, spin/backwards/instability state, lap completion, lap time, and contact count. Contact metrics dedupe the identifiers on each normalized physical contact event so one event increments each involved driver once. It must not encode or choose rewards.
|
|
113
113
|
- `recorder.js` owns neutral rollout transition export. It snapshots observations, actions, rewards, next observations, terminal flags, and info at record time and does not interpret rewards or policies.
|
|
114
114
|
- `controllerLoop.js` owns browser/headless controller orchestration around an injected runtime. It may lazily reset a fresh runtime before scheduled playback, but public `reset()` remains an explicit playback stop. Controller contexts report current `actions` separately from `previousActions`, where previous means the last action map actually applied before the current physics step. It maintains an observation cache so reset-only `resetDrivers()` results can stay small without handing null observations to the next batch decision for non-reset controlled drivers. Scheduled playback catches controller/runtime failures, stops the loop, and exposes the thrown value through loop stats.
|
|
@@ -119,7 +119,7 @@ The environment subpath must not import `src/index.js`, package CSS, PixiJS, `F1
|
|
|
119
119
|
|
|
120
120
|
`src/simulation/rulesConfig.js` is the public rules normalization facade. Defaults live in `src/simulation/rules/ruleDefaults.js`, ruleset presets in `rulesetPresets.js`, merge/value helpers in `ruleConfigMerge.js`, module normalization in `moduleConfig.js`, and penalty consequence normalization in `penaltyConfig.js`. Race modules should be added through those focused files first, then consumed by simulation logic through the normalized `race.rules` object.
|
|
121
121
|
|
|
122
|
-
`src/simulation/track/trackModel.js` is the canonical track-model facade. Procedural centerline generation, validation, sample rebuilding, start-straight normalization, sectors, DRS zones, pit-lane layout/access/state, spatial query indexing, and spatial queries live in focused `src/simulation/track/*` modules. `src/simulation/trackModel.js` remains a compatibility re-export for tests or older internal imports, but new production code should import the canonical track module. `buildTrackModel()` attaches a non-enumerable internal `track.queryIndex` after samples, DRS, sectors, and pit-lane geometry are finalized, and cached built models freeze their static geometry so consumers cannot mutate shared pit-lane data between builds. Race simulations clone the built track to own mutable timing/pit state and
|
|
122
|
+
`src/simulation/track/trackModel.js` is the canonical track-model facade. Procedural centerline generation, validation, sample rebuilding, start-straight normalization, sectors, DRS zones, pit-lane layout/access/state, spatial query indexing, and spatial queries live in focused `src/simulation/track/*` modules. `src/simulation/trackModel.js` remains a compatibility re-export for tests or older internal imports, but new production code should import the canonical track module. `buildTrackModel()` attaches a non-enumerable internal `track.queryIndex` after samples, DRS, sectors, and pit-lane geometry are finalized, and cached built models freeze their static geometry so consumers cannot mutate shared pit-lane data between builds. Race simulations clone the built track to own mutable timing/pit state and always attach a fresh runtime index fork. `trackQueryIndex.js` owns the internal indexed-query orchestration over typed centerline segment data, arc-length buckets, surface band thresholds, and deterministic nearest/ray query selection. Generic uniform grid operations live in `trackQueryGrid.js`, scratch buffer/epoch reuse lives in `trackQueryScratch.js`, query diagnostics live in `trackQueryStats.js`, and pit-lane road/box grid construction plus pit candidate lookups live in `trackQueryPitIndex.js`. Public callers still use `nearestTrackState()` and the same snapshot shapes; the index is internal infrastructure and must not leak into JSON snapshots or host contracts. Indexed nearest lookup treats progress hints only as optimization bounds, resolves equal-distance centerline segment ties deterministically inside the index, and proves finite-point nearest projections through the tight segment grid before falling back to a deterministic global indexed segment scan only if the grid cannot produce candidates. Missing indexes are programmer errors on built runtime tracks. Ray-boundary queries walk the tight segment grid along the ray corridor instead of querying the expanded recovery grid, which keeps compact training-track profiles from producing thousands of false candidate segments per ray. The model explicitly straightens the start/finish window so the starting grid, pit-entry approach area, and immediate post-line exit area are geometrically straight instead of merely choosing the least-curved generated segment. The pit lane is generated next to that start straight for every built track and exposes entry/exit connectors, explicit procedural road centerlines that overlap the track surface, connect on the lane-facing side of the main track, are tangent to the track at merge points and to the main pit lane at pit-lane endpoints, a straight main fast lane sized from the team/box run plus bounded entry/exit buffers, a parallel working lane, one shared service area per team, queue geometry behind each service area, and two garage boxes per team. `src/rendering/proceduralTrackAsset.js` is now only the Pixi container lifecycle wrapper; main-circuit drawing, pit-lane drawing, grid rendering, finish-line rendering, material constants, render geometry, and offset-stroke safety live under `src/rendering/track/`. `F1SimulatorApp` overlays the dynamic pit-lane status light because open/closed/red-flag state belongs to race control, not the static track texture.
|
|
123
123
|
|
|
124
124
|
`src/simulation/rules/` owns focused rule calculation helpers. Each stewarded rule should have its own function and, once non-trivial, its own file. `raceSimulation.js` should orchestrate timing/state and call those helpers; rule modules should return events or penalty payloads rather than mutating the whole simulation directly.
|
|
125
125
|
|
|
@@ -149,7 +149,7 @@ Responsibilities:
|
|
|
149
149
|
- Restart behavior for race data, seed, and track changes. Asset URL changes and browser expert mode changes are intentionally outside restart because texture loading and ticker ownership are part of initialization.
|
|
150
150
|
- Lifecycle cleanup, including partial-init failure cleanup and destruction of replaced PixiJS display children without destroying shared textures.
|
|
151
151
|
|
|
152
|
-
`src/app/camera/` owns camera state, camera constants, camera-safe-area measurement, track/pit-lane bounds fitting, zoom limits, mode availability, and target/frame calculation for overview, leader, selected, show-all, and pit cameras.
|
|
152
|
+
`src/app/camera/` owns camera state, camera constants, camera-safe-area measurement, track/pit-lane bounds fitting, zoom limits, mode availability, rotation, and target/frame calculation for overview, leader, selected, driver, show-all, and pit cameras.
|
|
153
153
|
|
|
154
154
|
`src/app/rendering/` owns PixiJS rendering surfaces:
|
|
155
155
|
|
|
@@ -164,7 +164,7 @@ Responsibilities:
|
|
|
164
164
|
|
|
165
165
|
`src/app/readouts/` owns generated-DOM readout rendering:
|
|
166
166
|
|
|
167
|
-
- `timingTowerRenderer.js` owns timing tower rows, gap
|
|
167
|
+
- `timingTowerRenderer.js` owns timing tower rows, runtime gap-mode labels/toggle state, waved-flag labels, and timing penalty badges. A single simulator may mount more than one timing tower through standalone and embedded composable surfaces, so timing row rendering fans out through plural timing-list bindings while keeping row markup/caching in this focused renderer.
|
|
168
168
|
- `telemetryRenderer.js` owns selected-car telemetry and lap/sector readouts.
|
|
169
169
|
- `carOverviewRenderer.js` owns driver/vehicle overview field selection and rendering.
|
|
170
170
|
- `raceStatusRenderer.js` owns race mode/lap/DRS/contact/start-light/finish readouts.
|
|
@@ -188,7 +188,9 @@ Environment sensor target selection belongs to `src/environment/sensors/sensorTa
|
|
|
188
188
|
|
|
189
189
|
`F1SimulatorApp.js` should stay a facade/orchestrator. New camera, rendering, readout, banner, or runtime behavior should usually land in the matching `src/app/*/` ownership module rather than expanding the app class.
|
|
190
190
|
|
|
191
|
-
`src/app/domBindings.js` owns DOM selector lookup
|
|
191
|
+
`src/app/domBindings.js` owns DOM selector lookup plus null-safe text and CSS custom-property writes for package-generated UI surfaces. It exposes both singular compatibility handles and plural collections for package-owned readouts that can appear more than once in the same simulator root, such as timing tower rows, lap counters, race-control status slots, and race-data panels. Shared helpers in this module resolve plural collections with singular fallbacks, skip duplicate node work, and avoid repeated identical text/style writes, so focused renderers do not each reimplement that control flow. Race-data panel bindings are grouped at lookup time so banner renderers and controls update embedded lower-thirds and standalone panels without repeated runtime DOM queries. Car/driver overview field label/value nodes are also grouped once at lookup time so overview renders update cached field bindings instead of rediscovering children on every changed overview render.
|
|
192
|
+
|
|
193
|
+
`src/app/layoutSupport.js` owns the package support-envelope observer for browser UI surfaces. It measures generated `data-paddock-component` surfaces and all-in-one shells, sets `data-paddock-size-unsupported` when a shell/mount is narrower than `320px`, when a component is below its component-specific inline minimum, or when a surface is below its minimum usable block size, and leaves CSS to show the accessible package-owned placeholder. Component-specific inline minima matter because package internals such as the broadcast timing tower are intentionally narrower than `320px` and must not self-report unsupported at their valid package-owned width. `src/app/raceOverlayClearanceSupport.js` owns the related narrow race-overlay measurement: it observes responsive embedded timing canvases, checks whether the lower-third would cover timing-row entries in the open reveal geometry, and sets a package CSS variable/class only when extra bottom clearance is required. The lower-third may cover timing-tower chrome/background that has no entries. These observers do not change simulation state, camera math, public API, or host layout; they only record whether the current package-owned surface can render safely.
|
|
192
194
|
|
|
193
195
|
## Simulation Core
|
|
194
196
|
|
|
@@ -247,13 +249,13 @@ Responsibilities:
|
|
|
247
249
|
|
|
248
250
|
`src/simulation/rules/penaltyStats.js` owns penalty-stat aggregation and lookup helpers.
|
|
249
251
|
|
|
250
|
-
`src/simulation/vehicle/contactResolution.js` owns physical collision resolution and contact velocity response. Stewarding remains separate from physical response. In
|
|
252
|
+
`src/simulation/vehicle/contactResolution.js` owns physical collision resolution and contact velocity response. Stewarding remains separate from physical response. In advanced mode, every collision branch that changes speed also keeps `velocityX/Y` synchronized, including contacts where one car is fixed by pit entry, queue, service, or exit routing.
|
|
251
253
|
|
|
252
254
|
`src/simulation/vehicle/runoffResponse.js` owns non-penalty off-track vehicle correction.
|
|
253
255
|
|
|
254
256
|
`src/simulation/snapshots/raceSnapshots.js` owns public, render, and observation snapshot assembly.
|
|
255
257
|
|
|
256
|
-
Weather effects, reliability failures, and fuel-load performance effects are not active `
|
|
258
|
+
Weather effects, reliability failures, and fuel-load performance effects are not active `3.0.0` behavior. Their future ownership should stay separate from the race orchestrator: weather belongs in a future `src/simulation/weather/` module that returns track/session condition modifiers, reliability belongs in a future `src/simulation/reliability/` module that emits deterministic failure/degradation events, and fuel-load effects belong in a future vehicle-adjacent performance module that adjusts mass/pace through explicit inputs rather than hidden mutation.
|
|
257
259
|
|
|
258
260
|
`snapshot()` is the full public data contract and must remain stable for host callbacks, environment state, and external callers. Internal browser/rendering paths use lean read models such as `snapshotRender()` so the 60 FPS loop does not serialize setup, wheels, penalties, and other non-render fields every frame. Environment and sensor code may use similarly narrow internal views, cache shared per-observation ray origin state, and broadphase car-ray checks before exact footprint intersection, but those must not change the public `RaceSnapshot` shape.
|
|
259
261
|
|
|
@@ -265,9 +267,9 @@ Weather effects, reliability failures, and fuel-load performance effects are not
|
|
|
265
267
|
|
|
266
268
|
`src/simulation/vehicle/wheelSurface.js` owns per-wheel track sampling and the effective car surface. Surface priority, analytic main-track classification, and pit-lane/connector classification are split into `surfacePriority.js`, `mainTrackWheelSurface.js`, and `pitWheelSurface.js`. On normal main-track running it uses the cached wheel geometry and the car center's track normal to analytically classify each wheel patch from signed offsets. It falls back to full patch sampling near pit connectors and inside pit-lane states. `vehiclePhysics.js` consumes that effective surface through the car track state, and separately compares left-side and right-side wheel resistance to add a small capped yaw bias toward the slower side.
|
|
267
269
|
|
|
268
|
-
`src/simulation/vehicle/vehiclePhysics.js` owns vehicle integration using the already-resolved wheel surfaces and wheel-state resistance imbalance. It exposes the normalized `physicsMode` contract and routes between the compatibility arcade integrator and the opt-in
|
|
270
|
+
`src/simulation/vehicle/vehiclePhysics.js` owns vehicle integration using the already-resolved wheel surfaces and wheel-state resistance imbalance. It exposes the normalized `physicsMode` contract and routes between the compatibility arcade integrator and the opt-in advanced integrator. The advanced path owns 2D velocity integration, yaw response, traction-budget calculation, speed-sensitive steering, steering scrub, aero drag/downforce balance, per-wheel surface averaging, derived slip-angle telemetry, advanced surface coefficients, and telemetry fields; steering scrub opposes signed forward motion so reverse slides do not gain energy from steering loss. Driver AI and environment policies still provide only steering/throttle/brake/pit intent.
|
|
269
271
|
|
|
270
|
-
`src/simulation/vehicle/vehicleState.js` owns external state mutation from simulator and environment reset APIs. It rejects non-finite numeric partial state and resynchronizes
|
|
272
|
+
`src/simulation/vehicle/vehicleState.js` owns external state mutation from simulator and environment reset APIs. It rejects non-finite numeric partial state and resynchronizes advanced-mode velocity from speed/heading when callers do not provide an explicit velocity vector, so scenario placement and `resetDrivers()` cannot leave stale physics velocity behind.
|
|
271
273
|
|
|
272
274
|
`src/simulation/rules/pitLaneSpeedingSteward.js` owns pit-speed-limit review. `raceSimulation.js` calls it after authoritative race-state recalculation so it reads the same pit-lane part classification as snapshots and track-limit stewarding. The steward only checks speed-limited pit-lane parts (`fast-lane`, `working-lane`, service areas, and garage boxes), leaving pit-entry and pit-exit connector roads legal but not speed-limited.
|
|
273
275
|
|
|
@@ -277,7 +279,7 @@ Browser mounts generate a fresh procedural seed when `trackSeed` is omitted; exp
|
|
|
277
279
|
|
|
278
280
|
`src/rendering/renderSnapshot.js` owns interpolation for rendering. It accepts either the full public snapshot or the lean render snapshot; render-only callers should prefer the lean snapshot.
|
|
279
281
|
|
|
280
|
-
The app runtime pauses its PixiJS ticker when the race canvas is outside the viewport or the document is hidden, then resets the frame clock before resuming. This prevents host pages with several simulator embeds from running every race while only one is visible, and avoids a large simulation catch-up step when the canvas re-enters view. Camera safe-area layout measurements are cached and invalidated by resize observation so the 60 FPS render path does not force repeated DOM geometry reads. Camera framing derives a cached active-track bounds box from generated samples plus pit-lane extent; overview uses that frame directly, and every camera uses it as the zoom-out floor. The first camera frame is applied immediately, then later camera mode changes interpolate target position and scale so leader/selected/overview/show-all/pit switches do not jump the world view. Runtime readouts avoid redundant DOM writes for unchanged text, timing markup, finish classification, and static selected-car overview data, and `updateDom()` reuses its supplied full snapshot for camera-control availability instead of taking another public snapshot. Static pit-lane status graphics are redrawn only when their status/geometry key changes, and DRS trail histories are pruned in place. Startup marks the initial DOM refresh time so the first ticker frame does not immediately redo the same full readout update. At `5x` and `10x` browser playback, noncritical DOM/timing refreshes are throttled more aggressively while fixed-step simulation, event emission, and rendering continue from authoritative simulation state. Long project-radio delays are also treated as stale schedule state instead of replaying every missed lower-third transition.
|
|
282
|
+
The app runtime pauses its PixiJS ticker when the race canvas is outside the viewport or the document is hidden, then resets the frame clock before resuming. Runtime visibility uses `IntersectionObserver` plus a bounded canvas-geometry check on observer, scroll, resize, focus, and page-show events so a visible canvas cannot remain paused because of a stale observer result. Ticker synchronization also compares the intended runtime state with PixiJS' actual `ticker.started` state before deciding whether to call `start()` or `stop()`. This prevents host pages with several simulator embeds from running every race while only one is visible, and avoids a large simulation catch-up step when the canvas re-enters view. Camera safe-area layout measurements are cached and invalidated by resize observation so the 60 FPS render path does not force repeated DOM geometry reads. Camera framing derives a cached active-track bounds box from generated samples plus pit-lane extent; overview uses that frame directly, and every camera uses it as the zoom-out floor. The first camera frame is applied immediately, then later camera mode changes interpolate target position and scale so leader/selected/overview/show-all/pit switches do not jump the world view. Runtime readouts avoid redundant DOM writes for unchanged text, timing markup, finish classification, and static selected-car overview data, and `updateDom()` reuses its supplied full snapshot for camera-control availability instead of taking another public snapshot. Static pit-lane status graphics are redrawn only when their status/geometry key changes, and DRS trail histories are pruned in place. Startup marks the initial DOM refresh time so the first ticker frame does not immediately redo the same full readout update. At `5x` and `10x` browser playback, noncritical DOM/timing refreshes are throttled more aggressively while fixed-step simulation, event emission, and rendering continue from authoritative simulation state. Long project-radio delays are also treated as stale schedule state instead of replaying every missed lower-third transition.
|
|
281
283
|
|
|
282
284
|
## Data Layer
|
|
283
285
|
|
|
@@ -305,6 +307,8 @@ The app runtime pauses its PixiJS ticker when the race canvas is outside the vie
|
|
|
305
307
|
- Race telemetry drawer template.
|
|
306
308
|
- Car/driver overview panel.
|
|
307
309
|
|
|
310
|
+
The package-owned component inventory is maintained in [Component Inventory](component_inventory.md). Use it before changing templates, DOM bindings, readout renderers, layout observers, or local-preview component usage so shared component behavior stays in focused helpers instead of drifting across all-in-one, composable, and derived mixed variants.
|
|
311
|
+
|
|
308
312
|
`src/ui/bannerTemplates.js` owns package banner markup:
|
|
309
313
|
|
|
310
314
|
- Top steward message for warnings and penalties.
|
|
@@ -314,11 +318,11 @@ The app runtime pauses its PixiJS ticker when the race canvas is outside the vie
|
|
|
314
318
|
|
|
315
319
|
`src/ui/templateUtils.js` owns shared template helpers such as escaping and loading-state markup. Banner templates and non-banner component templates should share helpers through this module instead of importing from each other.
|
|
316
320
|
|
|
317
|
-
`src/ui/shellTemplate.js` composes those component templates into the default all-in-one simulator DOM and package-owned layout presets such as `left-tower-overlay`. Telemetry graph/table surfaces are independent package-owned components controlled by `ui.telemetryModules` when used through stack/drawer templates; app readout modules render them from `car.lapTelemetry` without requiring host-owned DOM. The telemetry stack can embed the car/driver overview, owns vertical scrolling when the host constrains its height, and is also embedded by the hide/show race telemetry drawer. The overview is also a separately mountable component. The project and radio lower-thirds share the same race-data panel markup; runtime state only swaps content and mode classes. The optional project telemetry detail lives inside that shared lower-third, while the broadcast sector banner remains a separate lower-third-style sector graph that can be mounted standalone or embedded inside the race canvas only when a host explicitly asks for that independent popup. Readout modules bind the selected car name/code/color to it through the same readout path as the other telemetry surfaces. The race telemetry drawer template composes an external top control row, race canvas with embedded timing tower, project/radio lower-third, top steward message, safety-car control, and the telemetry stack in a
|
|
321
|
+
`src/ui/shellTemplate.js` composes those component templates into the default all-in-one simulator DOM and package-owned layout presets such as `left-tower-overlay`. Telemetry graph/table surfaces are independent package-owned components controlled by `ui.telemetryModules` when used through stack/drawer templates; app readout modules render them from `car.lapTelemetry` without requiring host-owned DOM. The telemetry stack can embed the car/driver overview, owns vertical scrolling when the host constrains its height, and is also embedded by the hide/show race telemetry drawer. The overview is also a separately mountable component. The project and radio lower-thirds share the same race-data panel markup; runtime state only swaps content and mode classes. The optional project telemetry detail lives inside that shared lower-third, while the broadcast sector banner remains a separate lower-third-style sector graph that can be mounted standalone or embedded inside the race canvas only when a host explicitly asks for that independent popup. Readout modules bind the selected car name/code/color to it through the same readout path as the other telemetry surfaces. The race telemetry drawer template composes an external top control row, race canvas with embedded timing tower, project/radio lower-third, top steward message, safety-car control, and the telemetry stack in a drawer. Its open/close state is owned by `F1SimulatorApp`; the safety-car control, project/radio banner mute toggle, and telemetry toggle stay in the top row while telemetry is open. Wide drawer layouts reserve final race-view space with a stable margin while the sidebar itself animates with a compositor transform; responsive narrow drawer layouts are enabled by default, stack the top workbench controls by container width, and reveal over the race view so mobile canvas height does not permanently collapse. The drawer race area uses the configured workbench height so host embeds do not leave unused black space below the simulation. The left-tower overlay preset is responsible for internal component placement and package-owned proportions; the project/radio lower-third remains owned by the race canvas so it can either use the race space beside the timing tower in `auto` sizing mode or overlap the tower when space is constrained. Composable hosts can also ask the race-canvas template to embed the timing tower directly with `includeTimingTower`, using the same expand-vs-scroll vertical fit contract on wide containers and a package-owned left reveal panel on narrow containers. In responsive narrow mode, the race-canvas min-height is governed by the timing tower/race-view minimum until runtime measurement shows the lower-third would cover one or more timing rows; only then does CSS receive the measured clearance needed to move the banner below the entries. `F1SimulatorApp` owns the timing reveal state, keeps closed narrow timing panels inert/hidden, installs the overlay-clearance observer, and resizes the renderer/camera during open, close, and host resize transitions without remounting the simulator. `src/app/layoutScheduler.js` owns the shared RAF coalescing/cancellation primitive used by layout support and overlay-clearance measurement so resize and mutation observers do not duplicate scheduling state. `ui.responsiveNarrowLayout` and matching composable mount options only toggle package-owned narrow template classes; they do not change simulation state or require host-owned layout patches. `src/app/camera/` measures the resulting timing-tower gutter when framing the PixiJS camera, whether the tower comes from the prebuilt shell or from an embedded composable race canvas. It also owns camera mode availability, wheel/button zoom for every camera mode, opt-in driver-camera rotation, hiding the pit camera control when the active track has no pit-lane geometry, and fitting the pit camera from operational `track.pitLane` lane, box, service, and queue bounds instead of host-provided coordinates or long access-road endpoints. On narrow hosts, closed timing reveals are outside the canvas safe area and open reveals are measured continuously by resize observation. Hosts should not provide the internal simulator markup or tune preset internals with raw sizing options.
|
|
318
322
|
|
|
319
323
|
Penalty UI is display-only. `src/app/banners/stewardMessageController.js` may show track-limit warning events and `snapshot.penalties` in the top steward message, and `src/app/readouts/timingTowerRenderer.js` may show timing-row penalty badges when the host enables those UI options, but the UI must not infer, modify, or recalculate steward decisions.
|
|
320
324
|
|
|
321
|
-
`src/config/defaultOptions.js` owns preset resolution, telemetry module normalization, the public theme contract, and
|
|
325
|
+
`src/config/defaultOptions.js` owns preset resolution, telemetry module normalization, browser warmup option normalization defaults, and deriving team theme selectors from normalized entry team metadata. `src/config/themeOptions.js` owns the public theme contract: semantic token resolution, generated opposite-mode value caching, known-token filtering, safe CSS color/size token validation, named derivative theme packages, component slot maps, component/team theme selectors, and legacy alias mapping for the old small color API. Presets are merged before host options; resolved theme fields are applied as package CSS variables by the all-in-one app and composable controller. Runtime selected-team theme context is cached so theme variables are not recomputed during normal render loops. This keeps sizing/color customization explicit without turning internal layout ratios into public API.
|
|
322
326
|
|
|
323
327
|
`src/config/restartOptions.js` owns shared restart-option merging for both `mountF1Simulator().restart()` and `createPaddockSimulator().restart()`. It preserves omitted driver/entry data, merges nested UI banner and asset texture groups, and resets prior UI/theme state when a restart supplies a new preset so the two browser mount surfaces cannot drift.
|
|
324
328
|
|
|
@@ -326,7 +330,7 @@ Race completion is owned by `src/simulation/raceSimulation.js`. The simulation r
|
|
|
326
330
|
|
|
327
331
|
Composable hosts may choose where each package-owned component root is placed, but they still receive package-generated markup through the public mount functions. The controller marks each mounted root as an `f1-sim-component` styling scope so standalone pieces receive the same package variables as the all-in-one shell.
|
|
328
332
|
|
|
329
|
-
`src/styles.css` styles the generated shell, imports package fonts, caps timing-tower width for readability, and owns the
|
|
333
|
+
`src/styles.css` styles the generated shell, imports package fonts, caps timing-tower width for readability, and owns the timing-list scroll behavior. Timing rows are compact content-sized grid rows stacked from the top, with `ui.timingEntryVerticalPadding` setting one shared pixel padding value above and below every entry, so rank positions do not stretch or redistribute when the number of entries changes. The timing tower owns its runtime interval-vs-leader-gap switch, while `F1SimulatorApp` reads both seconds and whole-lap gap values from the race snapshot and formats positive lap deficits as `+N` before seconds. The compact `Int`/`Gap` header toggle is a timing-tower style contract, not host CSS: the grid reserves a stable gap column, the header labels and toggle share one vertical center, the visible pill stays inside the header, and the clickable target remains `44px` without changing tower width. Fixed hidden timing-line creation, crossing timestamps, and seconds-gap calculation stay inside `src/simulation/timing/`. Sector-map surfaces read `lapTelemetry.sectorProgress` for bar fill and `lapTelemetry.liveSectors` for live active-sector time; the simulation owns those values so UI components do not infer active split state from DOM state. Future sectors are cleared by the simulation and ignored defensively by the renderer, so banner/sidebar sector nodes cannot keep stale timing text or yellow/purple/green performance classes after the active sector changes. Component templates include lightweight package-owned loading overlays; `F1SimulatorApp` removes them after startup initialization has completed.
|
|
330
334
|
|
|
331
335
|
The host page should only provide:
|
|
332
336
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Component Inventory
|
|
2
|
+
|
|
3
|
+
This inventory maps package-owned UI surfaces to their shared bases, owner modules, runtime wiring, and intentional differences. Keep it synchronized with `src/ui/*Template.js`, `src/app/domBindings.js`, `src/app/readouts/*`, `src/app/banners/*`, `src/app/layoutSupport.js`, `src/app/raceOverlayClearanceSupport.js`, and `src/api/PaddockSimulatorController.js`.
|
|
4
|
+
|
|
5
|
+
## Shared Ownership Rules
|
|
6
|
+
|
|
7
|
+
- `src/ui/templateUtils.js` owns generic generated-markup helpers: escaping, loading overlays, unsupported-size overlays, and other repeated inert markup primitives.
|
|
8
|
+
- `src/api/PaddockSimulatorController.js` owns composable root mounting, root CSS variables, layout-support installation, and the composite query surface used by `F1SimulatorApp`.
|
|
9
|
+
- `src/app/domBindings.js` is the only runtime selector map for package-generated DOM. Renderers and controllers consume cached nodes from that map instead of rediscovering common readouts. When one simulator can legitimately own multiple copies of a readout surface, the binding should expose plural collections and the focused renderer should fan out updates.
|
|
10
|
+
- `src/app/layoutSupport.js` owns component support-envelope checks and `data-paddock-size-unsupported` state. It should stay generic and component-id driven.
|
|
11
|
+
- `src/app/layoutScheduler.js` owns RAF-backed layout sync coalescing and cleanup cancellation for component measurement helpers.
|
|
12
|
+
- `src/app/raceOverlayClearanceSupport.js` owns the measured lower-third vs embedded timing-tower clearance path. It is intentionally limited to race-canvas panels with timing towers, caches static tower/banner/toggle nodes per panel, and re-queries only dynamic timing rows during syncs.
|
|
13
|
+
- `src/config/themeOptions.js` owns theme token, component slot, component selector, and selected-team variable application. Templates expose `data-paddock-component`; runtime applies variables.
|
|
14
|
+
|
|
15
|
+
## Package Components
|
|
16
|
+
|
|
17
|
+
| Component or variant | Public mount or composition | Owner module | Shared base | Runtime owner | Intentional differences |
|
|
18
|
+
| --- | --- | --- | --- | --- | --- |
|
|
19
|
+
| All-in-one shell | `mountF1Simulator()` | `src/ui/shellTemplate.js` | Composes race controls, optional external camera controls, timing tower, race canvas, telemetry stack, shell unsupported overlay | `F1SimulatorApp` | Owns `data-f1-simulator-shell`, workspace/grid classes, layout preset classes, and the package root styling scope. Not individually mounted by composable hosts. |
|
|
20
|
+
| Race controls | `mountRaceControls()` and all-in-one shell | `src/ui/raceControlsTemplate.js` | Component surface frame helper, `createSafetyCarControlMarkup({ compact: true })`, loading overlay, unsupported overlay | `controlStateRenderer.js` plus `F1SimulatorApp.bindControls()` | Includes title, kicker, safe backlink, safety-car button, and restart. The compact safety button has no standalone component wrapper. |
|
|
21
|
+
| Safety-car control | `mountSafetyCarControl()` and drawer/race-controls compact embed | `src/ui/raceControlsTemplate.js` | Component surface frame helper for standalone, same `data-safety-car` button and control-state renderer | `controlStateRenderer.js` plus `F1SimulatorApp.bindControls()` | Standalone variant gets `data-paddock-component="safety-car-control"` and unsupported-size overlay. Compact variants are plain subcontrols inside race controls or drawer toolbar. |
|
|
22
|
+
| Camera controls | `mountCameraControls()`, all-in-one external controls, embedded race-canvas controls, drawer toolbar | `src/ui/cameraControlsTemplate.js` | Component surface frame helper, shared `data-camera-mode`, zoom, banner mute, optional speed button, loading overlay, unsupported overlay | `controlStateRenderer.js`, `CameraController`, `F1SimulatorApp.bindControls()` | External variant adds `camera-controls--external`; embedded variant omits that class. Driver camera and simulation-speed buttons are option gated. |
|
|
23
|
+
| Timing tower | `mountTimingTower()`, all-in-one shell, embedded race canvas, drawer race canvas | `src/ui/timingTowerTemplate.js` | Component surface frame helper, race-control status banner, loading overlay, unsupported overlay, shared timing gap controls | `timingTowerRenderer.js`, `raceStatusRenderer.js`, `controlStateRenderer.js`, `F1SimulatorApp.observeLayoutResize()` | Embedded variants receive generated instance ids for `aria-controls`. `domBindings.js` exposes plural timing lists/header/status nodes and focused renderers fan out rows, lap counters, status banners, gap controls, and resize observation across every mounted tower owned by the same simulator. Header gap toggle may be hidden while controller APIs remain active. Width/proportions are package-owned and must not be tuned by host CSS. |
|
|
24
|
+
| Race-control status banner | Timing tower internal component | `src/ui/raceControlStatusBanner.js` | Timing tower status slot | `raceStatusRenderer.js` | Internal only. It has no public mount because it is a timing-tower status affordance, not a standalone component. |
|
|
25
|
+
| Race canvas | `mountRaceCanvas()` and shell/drawer templates | `src/ui/raceCanvasTemplate.js` | Component surface frame helper, steward message, optional embedded camera controls, optional race-data panel, optional sector banner, loading overlay, unsupported overlay | `F1SimulatorApp`, `CameraController`, Pixi renderers, `raceStatusRenderer.js`, `stewardMessageController.js`, `raceDataBannerController.js` | Base canvas can be canvas-only; options add timing tower, race-data lower-third, sector banner, FPS, physics indicator, and responsive narrow timing reveal. Race canvas is required before runtime start. |
|
|
26
|
+
| Embedded timing race canvas | `mountRaceCanvas(root, { includeTimingTower: true })`, drawer template | `src/ui/raceCanvasTemplate.js` | Timing tower template plus timing reveal button and narrow responsive classes | `F1SimulatorApp.syncTimingPanelDisclosureState()`, `installRaceOverlayClearanceSupport()` | Generates unique timing-panel ids. Narrow mode makes the tower a left reveal panel and measures banner clearance only when needed. |
|
|
27
|
+
| Steward message | Race canvas internal component | `src/ui/bannerTemplates.js` | Shared banner markup conventions | `stewardMessageController.js` | Internal top race-control message for warnings/penalties. No public mount because it depends on race-canvas layering. |
|
|
28
|
+
| Race-data panel | `mountRaceDataPanel()`, race canvas lower-third, drawer lower-third | `src/ui/bannerTemplates.js` | Component surface frame helper, selected driver readouts, optional race-data telemetry detail, loading overlay, unsupported overlay | `domBindings.js` grouped panel bindings, `raceDataBannerController.js`, `telemetryRenderer.js` for sector detail | Standalone variant adds `race-data-panel--standalone`. Embedded variants are clipped/layered by race canvas. Project and radio modes share the same panel; runtime swaps content/classes across every mounted panel owned by the simulator. |
|
|
29
|
+
| Telemetry sector banner | `mountTelemetrySectorBanner()` and optional race-canvas embed | `src/ui/bannerTemplates.js` | Component surface frame helper, sector-bar markup, loading overlay, unsupported overlay | `telemetryRenderer.js` | Independent broadcast lower-third style sector surface. It is separate from race-data telemetry detail and should only render when explicitly mounted or embedded. |
|
|
30
|
+
| Telemetry stack | `mountTelemetryPanel()` and drawer content | `src/ui/telemetryTemplates.js` | Component surface frame helper, telemetry core, sector progress, lap times, sector times, optional car/driver overview, unsupported overlay | `telemetryRenderer.js`, `carOverviewRenderer.js` when overview is included | The stack owns scrolling when constrained. It may include overview by default, but drawer content intentionally disables overview. |
|
|
31
|
+
| Telemetry core | `mountTelemetryCore()` and telemetry stack | `src/ui/telemetryTemplates.js` | Telemetry module class helper, loading overlay, unsupported overlay | `telemetryRenderer.js` | Scalar selected-car telemetry only. It shares selected-code/name readouts with sector banner. |
|
|
32
|
+
| Telemetry sectors | `mountTelemetrySectors()` and telemetry stack | `src/ui/telemetryTemplates.js` | Telemetry module class helper, sector-bar markup, loading overlay, unsupported overlay | `telemetryRenderer.js` | Sector progress bars only. It shares sector bar selectors with race-data detail and sector banner. |
|
|
33
|
+
| Telemetry lap times | `mountTelemetryLapTimes()` and telemetry stack | `src/ui/telemetryTemplates.js` | Telemetry module class helper, loading overlay, unsupported overlay | `telemetryRenderer.js` | Current, last, and best lap table only. |
|
|
34
|
+
| Telemetry sector times | `mountTelemetrySectorTimes()` and telemetry stack | `src/ui/telemetryTemplates.js` | Telemetry module class helper, loading overlay, unsupported overlay | `telemetryRenderer.js` | Last/best sector table only. |
|
|
35
|
+
| Car/driver overview | `mountCarDriverOverview()` and optional telemetry stack embed | `src/ui/carOverviewTemplate.js` | Component surface frame helper, overview field slots, loading overlay, unsupported overlay | `domBindings.js` cached field bindings, `carOverviewRenderer.js` | Standalone and stack-embedded variants share identical markup. Runtime toggles vehicle vs driver fields without remounting and updates cached label/value bindings instead of querying each field during render. |
|
|
36
|
+
| Race telemetry drawer | `mountRaceTelemetryDrawer()` and complete preview template | `src/ui/telemetryDrawerTemplate.js` | Component surface frame helper, camera controls, compact safety-car control, race canvas with timing/data panel, telemetry stack, unsupported overlay | `F1SimulatorApp.setTelemetryDrawerOpen()`, `controlStateRenderer.js`, normal race/telemetry renderers | Higher-level mixed template. It intentionally owns toolbar placement, drawer ids, open/closed inert state, and responsive narrow drawer behavior. |
|
|
37
|
+
| Loading overlay | All startup-capable public surfaces except internal-only steward/status | `src/ui/templateUtils.js` | `createLoadingMarkup(label)` | `F1SimulatorApp.completeComponentLoading()` | Removed after startup. Component labels remain surface-specific for accessibility. |
|
|
38
|
+
| Unsupported-size overlay | Public mount roots, shell, drawer, race/data/telemetry surfaces | `src/ui/templateUtils.js` | `createUnsupportedSizeMarkup(label)` | `installLayoutSupport()` | Display is driven by measured support state. Minimum size thresholds remain centralized in `layoutSupport.js`. |
|
|
39
|
+
|
|
40
|
+
## Local Preview Variants
|
|
41
|
+
|
|
42
|
+
| Route or section | Package surfaces exercised | Owner in preview | Intentional difference |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| Templates complete broadcast | `createPaddockSimulator()` plus `mountRaceTelemetryDrawer()` | `local-preview/src/main.js`, `local-preview/src/showcaseCatalog.js` | Demonstrates the highest-level mixed workbench with race, timing, lower-third, top controls, and drawer in one package-owned template. |
|
|
45
|
+
| Templates dashboard, timing overlay, compact race, full dashboard | `mountF1Simulator()` with presets | `local-preview/src/main.js` | Demonstrates all-in-one shell presets and preset-first option override behavior. |
|
|
46
|
+
| Templates banner option | `mountRaceCanvas()` plus external `mountCameraControls()` | `local-preview/src/main.js` | Demonstrates a composable race window that owns timing tower and race-data lower-third while camera controls remain separate. |
|
|
47
|
+
| Components embedded window | `mountRaceCanvas()` with timing tower, data panel, sector banner plus external camera controls | `local-preview/src/main.js` | Exercises optional race-canvas embeds without the drawer wrapper. |
|
|
48
|
+
| Components separate pieces | Every public composable mount | `local-preview/src/main.js` `COMPONENT_PIECE_MOUNTS` | Proves separate host roots still receive package CSS variables, loading state, layout support, and shared runtime updates. The preview inventory table is the single owner of the separate-piece root id to mount-helper mapping; the telemetry drawer remains separate because it is a mixed higher-level template with its own simulator. |
|
|
49
|
+
| Customization route | Race canvas, overview, race-data panel, controls, timing tower, telemetry core | `local-preview/src/main.js` | Exercises theme packages, selected-team component theming, component overrides, and standalone vs embedded component theming. |
|
|
50
|
+
| API route | Race telemetry drawer | `local-preview/src/main.js` | Exercises controller methods and snapshot readout beside the package-owned workbench. |
|
|
51
|
+
| Behavior route | Multiple race-canvas variants | `local-preview/src/main.js` | Exercises timing expand/scroll, embedded camera controls, sector banner, finish panel, and deterministic behavior fixtures. |
|
|
52
|
+
| Stewarding route | Race canvas with race-data panel and penalty UI options | `local-preview/src/main.js` | Exercises steward message, timing penalty badges, and penalty controller APIs. |
|
|
53
|
+
| Playable route | Race telemetry drawer in browser expert mode | `local-preview/src/main.js`, `local-preview/src/playableKeyboardController.js` | Host-owned keyboard controller drives package-owned expert runtime; package UI remains the same drawer/canvas/timing/telemetry surfaces. |
|
|
54
|
+
| Policy Runner route | All-in-one browser expert mount | `local-preview/src/main.js`, `local-preview/src/policyRunner/*` | Host-owned model/controller panel drives package expert APIs and model-sense visualization without changing package component markup. |
|
|
55
|
+
|
|
56
|
+
## Duplication Map
|
|
57
|
+
|
|
58
|
+
| Pattern | Current shared owner | Remaining duplication | Rationale or next action |
|
|
59
|
+
| --- | --- | --- | --- |
|
|
60
|
+
| Component surface frames | `templateUtils.js` | The all-in-one shell and internal steward message keep bespoke wrappers | Public component surfaces, including race controls, standalone safety-car control, timing tower, race canvas, race telemetry drawer, telemetry stack, race-data panel, telemetry sector banner, detached telemetry modules, car/driver overview, and camera controls now share the frame helper. The shell is a package root with `data-f1-simulator-shell`; steward message is an internal layered race-canvas banner. |
|
|
61
|
+
| Loading overlays | `templateUtils.js` | Component labels differ | Intentional accessibility labels per surface. |
|
|
62
|
+
| Unsupported-size overlays | `templateUtils.js` plus `layoutSupport.js` | Component labels and min-size thresholds differ | Intentional because each component has a different support envelope. |
|
|
63
|
+
| Sector bar markup | `templateUtils.js` | Wrapper and bar classes differ between sector telemetry, sector banner, and race-data telemetry detail | Shared helper owns selectors and initial fill/time markup; call sites pass only their intentional class differences. |
|
|
64
|
+
| Component styling scope | `PaddockSimulatorController.mountComponent()` and shell root class | All-in-one shell sets root class in template, composable controller sets it on host roots | Intentional because all-in-one owns the root element, while composable hosts provide roots. |
|
|
65
|
+
| DOM selector lookup | `domBindings.js` | `raceOverlayClearanceSupport.js` and `layoutSupport.js` perform local measurement queries | Intentional: measurement helpers need scoped live layout queries and should not enlarge app readout bindings. Timing tower state uses plural cached bindings because duplicate towers are package-owned runtime readouts. `resolveNodes()` centralizes plural collection plus singular fallback handling so renderers and resize observers do not duplicate that control flow. Overlay clearance caches static panel nodes at install time; only timing-row queries remain live because timing rows are renderer-owned dynamic markup. |
|
|
66
|
+
| Text and CSS custom-property writes | `domBindings.js` | Direct property assignment remains for non-custom style properties such as `style.color` | `setText()` / `setTextAll()` and `setStyleProperty()` skip unchanged writes in frequent render paths. Readout and banner renderers should use these helpers for package text and CSS custom properties instead of local write guards. |
|
|
67
|
+
| Car/driver overview field rendering | `domBindings.js` and `carOverviewRenderer.js` | Direct renderer fallback still supports narrow unit stubs without full DOM binding | Intentional compatibility fallback. Runtime mounts use cached field label/value bindings from `domBindings.js`, so changed overview renders do not rediscover static field children. |
|
|
68
|
+
| Control active state | `controlStateRenderer.js` | Event binding remains in `F1SimulatorApp` | Intentional separation: app owns events/state mutation; renderer owns button state. |
|
|
69
|
+
| Race-data lower-third and standalone panel | `bannerTemplates.js`, `domBindings.js`, and `raceDataBannerController.js` | Standalone adds one class and canvas embeds rely on placement | Intentional public placement difference with shared markup. Runtime state, open/dismiss controls, project mode, radio mode, and hide behavior fan out through grouped panel bindings so embedded and standalone panels do not drift. |
|
|
70
|
+
| Timing tower standalone vs embedded | `timingTowerTemplate.js`, `raceCanvasTemplate.js`, `timingTowerRenderer.js`, `raceStatusRenderer.js` | Embedded adds reveal button/id and narrow classes in race canvas | Intentional because disclosure state belongs to the parent race canvas, not the tower. Runtime rows, lap counters, race-control status, and gap labels are shared across standalone and embedded towers through plural DOM bindings and focused readout renderers. |
|
|
71
|
+
| Drawer and race-canvas responsive logic | `telemetryDrawerTemplate.js`, `raceCanvasTemplate.js`, `layoutSupport.js`, `raceOverlayClearanceSupport.js`, `layoutScheduler.js` | Drawer toolbar and race-canvas narrow reveal have distinct CSS classes | Intentional because they solve different container constraints while sharing component children. RAF scheduling/cancellation for layout measurement is shared so support checks and overlay clearance do not drift. |
|
|
72
|
+
| Local preview route setup | `local-preview/src/main.js` | Route-specific controllers, timers, readouts, and the components-page `COMPONENT_PIECE_MOUNTS` table remain local | Intentional host-owned demo behavior. Package component logic must not move into preview. The components-page table centralizes preview wiring of public mount helpers without changing package contracts. |
|