@inventure71/paddockjs 2.0.0 → 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 +37 -1
- package/INSTALL_AND_UPDATE.md +10 -1
- package/README.md +92 -17
- package/docs/architecture.md +26 -16
- package/docs/component_inventory.md +72 -0
- package/docs/concepts.md +7 -5
- package/docs/custom_model_controller.md +2 -2
- package/docs/data_contract.md +121 -33
- package/docs/data_model.md +1 -1
- package/docs/index.md +10 -0
- package/docs/learnings.md +6 -6
- package/docs/rules.md +39 -9
- package/docs/sense_contract.md +59 -0
- package/docs/system_specs.md +76 -20
- package/docs/training.md +27 -20
- package/package.json +1 -1
- package/src/api/PaddockSimulatorController.js +75 -36
- package/src/app/BrowserExpertAdapter.js +9 -300
- package/src/app/BrowserExpertExternalRenderer.js +303 -0
- package/src/app/F1SimulatorApp.js +190 -68
- 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/restartOptions.js +34 -0
- 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 +34 -5
- package/src/environment/episode.js +14 -2
- package/src/environment/index.d.ts +17 -4
- package/src/environment/observationVector.js +200 -0
- package/src/environment/observations.js +58 -285
- package/src/environment/options.js +1 -1
- package/src/environment/recorder.js +13 -6
- package/src/environment/rewards.js +13 -2
- package/src/environment/runtime.js +65 -25
- package/src/environment/scenarios.js +22 -3
- package/src/environment/sensors/index.js +3 -3
- package/src/environment/sensors/opponentRadar.js +13 -3
- package/src/environment/sensors/sensorTargets.js +8 -2
- package/src/environment/sensors/surfaceRays.js +84 -12
- package/src/environment/sensors/trackRays.js +173 -15
- package/src/environment/specs.js +78 -40
- package/src/index.d.ts +112 -9
- package/src/index.js +26 -33
- 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/pitFlow.js +5 -0
- package/src/simulation/pit/pitKinematics.js +24 -0
- package/src/simulation/pit/pitQueue.js +5 -0
- package/src/simulation/pit/pitRouteMovement.js +7 -0
- package/src/simulation/pit/pitRouting.js +2 -4
- package/src/simulation/pit/pitService.js +2 -0
- package/src/simulation/pit/pitTireService.js +2 -0
- package/src/simulation/race/raceLifecycle.js +2 -6
- package/src/simulation/race/raceProgress.js +6 -1
- package/src/simulation/race/raceSetup.js +2 -6
- package/src/simulation/race/raceStep.js +2 -2
- package/src/simulation/race/redFlag.js +3 -3
- package/src/simulation/race/safetyCar.js +10 -4
- package/src/simulation/race/stalledDnf.js +8 -17
- package/src/simulation/raceSimulation.js +1 -0
- package/src/simulation/rules/rulesReview.js +3 -0
- package/src/simulation/snapshots/raceSnapshots.js +1 -0
- 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/trackQueryGrid.js +94 -0
- package/src/simulation/track/trackQueryIndex.js +193 -434
- package/src/simulation/track/trackQueryPitIndex.js +140 -0
- package/src/simulation/track/trackQueryScratch.js +46 -0
- package/src/simulation/track/trackQueryStats.js +28 -0
- package/src/simulation/track/trackStatePolicy.js +0 -1
- package/src/simulation/vehicle/contactResolution.js +67 -2
- package/src/simulation/vehicle/runoffResponse.js +11 -12
- package/src/simulation/vehicle/vehicleKinematics.js +37 -0
- package/src/simulation/vehicle/vehiclePhysics.js +5 -4
- package/src/simulation/vehicle/vehicleSnapshots.js +22 -0
- package/src/simulation/vehicle/vehicleState.js +36 -2
- package/src/simulation/vehicle/wheelSurface.js +3 -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/docs/data_model.md
CHANGED
|
@@ -141,7 +141,7 @@ constructorArgs: {
|
|
|
141
141
|
|
|
142
142
|
It also copies the converted driver fields and vehicle fields onto the object used by `createRaceSimulation()`.
|
|
143
143
|
|
|
144
|
-
Team metadata is copied onto the same normalized driver object. `team.color` defaults to the driver/car color when omitted, and `team.icon` defaults from the team name or timing code. Teams are
|
|
144
|
+
Team metadata is copied onto the same normalized driver object. `team.color` defaults to the driver/car color when omitted, and `team.icon` defaults from the team name or timing code. Teams are intentionally modeled separately from driver and vehicle data because current pit-lane service areas, queue colors, garage box identity, optional pit-crew stats, and future team-owned fields should not be forced into car or driver objects.
|
|
145
145
|
|
|
146
146
|
## Unit Conversion
|
|
147
147
|
|
package/docs/index.md
CHANGED
|
@@ -9,9 +9,19 @@ Use these docs as the package source of truth:
|
|
|
9
9
|
- [Concepts](concepts.md): vocabulary used by the simulator.
|
|
10
10
|
- [Data Contract](data_contract.md): shape of host-provided drivers, car pairings, ratings, assets, and callbacks.
|
|
11
11
|
- [Bring Your Own Model](training.md): environment contract, policy convention, and visual playback loop.
|
|
12
|
+
- [Model Sense Contract](sense_contract.md): executable proof boundary for policy-facing observation senses.
|
|
12
13
|
- [Custom Model Controller Guide](custom_model_controller.md): how to wrap a trained model as a batched driver controller for browser playback or headless loops.
|
|
13
14
|
- [Architecture](architecture.md): module ownership and data/control flow.
|
|
15
|
+
- [Component Inventory](component_inventory.md): package-owned UI surfaces, shared bases, derived/mixed variants, and remaining intentional duplication.
|
|
14
16
|
- [Data Model Notes](data_model.md): lower-level simulator data model notes kept with package docs.
|
|
15
17
|
- [Learnings](learnings.md): implementation lessons and layout verification rules learned from simulator integration issues.
|
|
16
18
|
|
|
19
|
+
Common paths:
|
|
20
|
+
|
|
21
|
+
- Installing PaddockJS into a browser host: read [System Specs](system_specs.md), then [Data Contract](data_contract.md), then [INSTALL_AND_UPDATE.md](../INSTALL_AND_UPDATE.md).
|
|
22
|
+
- Building a package-owned composable layout: read the public API, returned-controller, and layout support contract sections in [System Specs](system_specs.md), then the sizing and UI options in [Data Contract](data_contract.md).
|
|
23
|
+
- Enabling pits, penalties, tire requirements, safety car, or stalled off-track DNF: read [Rules](rules.md) first. Pit stops require `rules.modules.pitStops.enabled`; `tireStrategy` alone only controls compounds, tire-requirement stewarding, and pit target choices.
|
|
24
|
+
- Connecting a trained model: read [Bring Your Own Model](training.md), then [Custom Model Controller Guide](custom_model_controller.md). PaddockJS owns observations, actions, specs, scenarios, and browser playback; the host owns the model, reward, storage, and training framework.
|
|
25
|
+
- Changing internals: read [Architecture](architecture.md) before editing source so new code lands in the feature-owned module instead of a facade or compatibility barrel.
|
|
26
|
+
|
|
17
27
|
When simulator behavior changes, update the relevant doc in this folder in the same change.
|
package/docs/learnings.md
CHANGED
|
@@ -48,7 +48,7 @@ Unit tests should cover the contract, but browser geometry checks catch CSS inte
|
|
|
48
48
|
|
|
49
49
|
## Indexed Track Queries Must Reach Every Runtime Path
|
|
50
50
|
|
|
51
|
-
The track query index is internal performance infrastructure, not a separate sensor contract.
|
|
51
|
+
The track query index is internal performance infrastructure, not a separate sensor contract. Every simulation instance used by a runtime must own an index:
|
|
52
52
|
|
|
53
53
|
- Headless environments created by `createPaddockEnvironment()`.
|
|
54
54
|
- Browser expert mode through `F1SimulatorApp.createRaceSimulation()`.
|
|
@@ -56,14 +56,14 @@ The track query index is internal performance infrastructure, not a separate sen
|
|
|
56
56
|
- Local preview remount/restart paths.
|
|
57
57
|
- Any benchmark or smoke path used to validate training behavior.
|
|
58
58
|
|
|
59
|
-
Do not assume that
|
|
59
|
+
Do not assume that a high-level preview or environment config controls the query implementation. The runtime must always build a non-enumerable `track.queryIndex` for live simulations. The index must stay out of public JSON contracts: it may exist on the in-memory track object, but `Object.keys(snapshot.track)` must not expose `queryIndex`.
|
|
60
60
|
|
|
61
|
-
The failure mode to avoid: headless training uses indexed queries and looks fast, while the browser visual/expert simulation
|
|
61
|
+
The failure mode to avoid: headless training uses indexed queries and looks fast, while the browser visual/expert simulation bypasses the canonical index near barriers, recovery starts, or far-off-track positions. This can make the Policy Runner lag even when the model, heuristic controller, and ray contract are correct.
|
|
62
62
|
|
|
63
63
|
Verification for index-related changes should include both layers:
|
|
64
64
|
|
|
65
|
-
- A focused unit test proving the app or environment path
|
|
66
|
-
- A browser reproduction or smoke check against the Policy Runner or relevant visual runtime, especially near barrier/recovery geometry where
|
|
67
|
-
- A profile or timing check when the bug is performance-related. If
|
|
65
|
+
- A focused unit test proving the app or environment path owns a live `track.queryIndex`.
|
|
66
|
+
- A browser reproduction or smoke check against the Policy Runner or relevant visual runtime, especially near barrier/recovery geometry where broad nearest-sample scans are most expensive.
|
|
67
|
+
- A profile or timing check when the bug is performance-related. If broad nearest-sample scans are still hot in a browser path, another code path is bypassing the index.
|
|
68
68
|
|
|
69
69
|
Do not fix browser performance by changing model-facing sensor precision, hiding observations, or showing debug-only values. The active observation contract remains the source of truth: policies receive it, Policy Runner visualizes it, and the index only changes how the same values are computed.
|
package/docs/rules.md
CHANGED
|
@@ -25,6 +25,28 @@ Race rules are normalized before the simulation starts. Hosts can choose a packa
|
|
|
25
25
|
|
|
26
26
|
The preset only chooses defaults. Explicit `rules.modules` values override the preset.
|
|
27
27
|
|
|
28
|
+
Default module state:
|
|
29
|
+
|
|
30
|
+
| Module | Package default | Important default options |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `pitStops` | disabled | `pitLaneSpeedLimitKph: 80`, `defaultStopSeconds: 2.8`, `maxConcurrentPitLaneCars: 3`, `minimumPitLaneGapMeters: 20`, `doubleStacking: false`, tire pit thresholds `50%` request / `30%` commit |
|
|
33
|
+
| `tireStrategy` | disabled | `compounds: ['S', 'M', 'H']`, `mandatoryDistinctDryCompounds: null` |
|
|
34
|
+
| `tireDegradation` | enabled | tire energy updates from throttle, braking, and lateral load unless disabled |
|
|
35
|
+
| `stalledDnf` | disabled | `maxStoppedSeconds: 12`, `speedThresholdKph: 5` |
|
|
36
|
+
| `penalties` | disabled | `stewardStrictness: 1`; track-limit, collision, tire-requirement, and pit-lane-speeding subsections default to `strictness: 0` |
|
|
37
|
+
| `weather` | disabled | reserved; no current grip or visibility effect |
|
|
38
|
+
| `reliability` | disabled | reserved; no current failure-risk effect |
|
|
39
|
+
| `fuelLoad` | disabled | reserved; no current mass or pace effect |
|
|
40
|
+
|
|
41
|
+
Ruleset preset deltas:
|
|
42
|
+
|
|
43
|
+
| Ruleset | Module changes from package defaults |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `paddock` | no module changes; uses the package defaults above |
|
|
46
|
+
| `custom` | no module changes before explicit host overrides |
|
|
47
|
+
| `grandPrix2025` | enables `pitStops`, enables `tireStrategy` with `mandatoryDistinctDryCompounds: 2`, enables `penalties` with `stewardStrictness: 0.85`, track-limit strictness `0.85`, collision strictness `0.65`, tire-requirement strictness `1`, and pit-lane-speeding strictness `1` |
|
|
48
|
+
| `fia2025` | alias for `grandPrix2025` |
|
|
49
|
+
|
|
28
50
|
## Rule Modules
|
|
29
51
|
|
|
30
52
|
Advanced race behavior is organized under `rules.modules` so hosts can enable, disable, or tune each system independently:
|
|
@@ -71,14 +93,18 @@ rules: {
|
|
|
71
93
|
}
|
|
72
94
|
```
|
|
73
95
|
|
|
74
|
-
The current implementation normalizes and exposes all module config, records a penalty ledger, enforces collision penalties, track-limit penalties, tire-requirement penalties, pit-lane speeding penalties, and opt-in stalled off-track DNFs, creates/renders pit-lane geometry
|
|
96
|
+
The current implementation normalizes and exposes all module config, records a penalty ledger, enforces collision penalties, track-limit penalties, tire-requirement penalties, pit-lane speeding penalties, and opt-in stalled off-track DNFs, creates/renders pit-lane geometry when the resolved track generation options enable it, treats pit-lane asphalt, working-lane service areas, and garage boxes as legal drivable surfaces, applies nonlinear tire-energy degradation when `tireDegradation.enabled` is not `false`, and runs automated pit stops when `pitStops.enabled` is true. Weather effects, reliability failures, and fuel-load performance effects are reserved future modules only; the simulation does not change grip, power, retirement risk, mass, or pace from those module keys.
|
|
97
|
+
|
|
98
|
+
`tireStrategy` does not enable automatic pit stops. Enable `rules.modules.pitStops.enabled` separately, or use `ruleset: 'fia2025'` / `ruleset: 'grandPrix2025'`, when cars should use the automatic pit-entry, queue, service, and exit route. `tireStrategy.enabled` controls compounds, tire-requirement stewarding, and pit-stop tire choices.
|
|
75
99
|
|
|
76
100
|
`tireDegradation.enabled: false` freezes each car's current `tireEnergy` instead of updating wear from throttle, braking, and lateral load. This is separate from `tireStrategy.enabled`: tire strategy controls compounds, tire-requirement stewarding, and pit-stop tire choices, while tire degradation controls the physics wear curve.
|
|
77
101
|
|
|
78
|
-
`stalledDnf` defaults to `enabled: false`. When a host sets `rules.modules.stalledDnf.enabled: true`, a car that is off legal racing or pit surfaces and remains below `speedThresholdKph` for `maxStoppedSeconds` is marked out of race with `dnfReason: 'stalled-off-track'`, emits a `car-dnf` event, freezes controls/motion, and leaves active collision, sensor, pit-blocking, and race-order participation. The timer does not run during pre-start, red flag, after race completion, for already-finished cars, or while pit entry/queue/service/exit owns the car. Returning to legal surface or moving above the threshold resets the timer.
|
|
102
|
+
`stalledDnf` defaults to `enabled: false`. When a host sets `rules.modules.stalledDnf.enabled: true`, a car that is off legal racing or pit surfaces and remains below `speedThresholdKph` for `maxStoppedSeconds` is marked out of race with `dnfReason: 'stalled-off-track'`, emits a `car-dnf` event, freezes controls/motion, and leaves active collision, sensor, pit-blocking, and race-order participation. The freeze clears advanced-mode velocity, acceleration, slip, grip, and manual controls together, so a retired car cannot keep a stale motion vector in snapshots or model observations. The timer does not run during pre-start, red flag, after race completion, for already-finished cars, or while pit entry/queue/service/exit owns the car. Returning to legal surface or moving above the threshold resets the timer.
|
|
79
103
|
|
|
80
104
|
Participant interaction profiles can opt real cars out of specific interaction systems without turning them into replay ghosts. A non-colliding profile skips vehicle collision resolution and collision stewarding for that car pair, but the car still uses normal steering/throttle/brake physics. A non-blocking pit profile is ignored by pit service occupancy and queue-blocking checks. A car with `affectsRaceOrder: false` remains in `snapshot.cars` but is excluded from ranking, DRS references, finish order, and final classification. Replay ghosts are separate trajectory overlays in `snapshot.replayGhosts`; they never participate in rules, collisions, timing, pit stops, or penalties.
|
|
81
105
|
|
|
106
|
+
Automatic pit routing owns car motion while a car is entering, queued, servicing, or exiting. During those phases the route/service logic keeps scalar speed, throttle/brake, `appliedControls`, and advanced telemetry synchronized so advanced-mode observations and the next physics-owned step do not inherit stale pre-pit motion, grip, slip, or instability state. `velocityX/Y` is synchronized in advanced mode and cleared from arcade snapshots because arcade physics does not own a world-velocity state.
|
|
107
|
+
|
|
82
108
|
## Steward Strictness
|
|
83
109
|
|
|
84
110
|
Penalty subsections use `strictness` from `0` to `1` instead of a boolean:
|
|
@@ -104,6 +130,8 @@ Pit-lane surfaces are legal road for track-limit purposes. `pit-entry`, `pit-lan
|
|
|
104
130
|
|
|
105
131
|
Penalty decisions are recorded in `snapshot.penalties` and emitted as `penalty` events in the same step. Each entry includes the penalty type, driver id, strictness, status, penalty seconds, pending service conversion seconds, lap, timestamp, and rule-specific context. Multiple time penalties for the same driver are additive: two separate +5s entries produce `penaltySeconds: 10` on that car's snapshot and classification adjustment.
|
|
106
132
|
|
|
133
|
+
Collision response and collision-steward impact speed use simulator `velocityX/Y` when available. In advanced mode this keeps slip, sideways recovery, pit-adjacent motion, and post-contact damping aligned with actual physical motion instead of assuming every car moves exactly in its heading direction. If a collision pushes against a pit-route-owned car, the pit-owned car stays fixed and the movable car's scalar speed and velocity vector are damped together so the next physics step and model-facing speed senses remain consistent.
|
|
134
|
+
|
|
107
135
|
Each penalty subsection can define `consequences`. Supported consequences are:
|
|
108
136
|
|
|
109
137
|
- `{ type: 'warning' }`
|
|
@@ -166,7 +194,7 @@ The race engine uses simulator units internally. `src/simulation/units.js` conve
|
|
|
166
194
|
|
|
167
195
|
The default `physicsMode` is `'arcade'`, which preserves the existing forgiving vehicle behavior for current browser hosts and demos.
|
|
168
196
|
|
|
169
|
-
`physicsMode: '
|
|
197
|
+
`physicsMode: 'advanced'` is opt-in for more believable F1-style limits. It still accepts only normal steering, throttle, brake, and pit-intent inputs, but the vehicle integrator keeps a real 2D world velocity, derives speed from that velocity, and applies a traction budget between longitudinal and lateral demand. Steering changes yaw through a rate-limited response, while body slip is derived from the difference between the car heading and actual velocity direction instead of being an artificial movement offset. External state placement through simulator APIs rejects non-finite numeric values and resynchronizes `velocityX/Y` whenever speed or heading is changed without an explicit velocity vector, so scenario/reset placement cannot leave stale advanced-mode velocity behind. Kerbs remain legal racing surface, but high-load kerb use is less stable and slower than track asphalt. Gravel and grass are averaged per wheel so one wheel over the edge creates partial grip/drag loss, while all-wheel runoff still costs major time. The rendered barrier wall is the hard outer runoff boundary in both physics modes; cars whose footprint reaches the wall's inner face are marked `destroyed`, stopped, removed from active collision and sensor participation, and placed in DNF timing order. Barrier destruction, stalled DNF, pre-start grid hold, red-flag hold, and pit-queue hold freeze scalar speed and advanced-mode velocity together; clearing a red flag resynchronizes active cars to the release speed and heading before the next observation while DNF/out-of-race cars remain frozen. `physicsMode` changes vehicle integration and grip behavior, not barrier consequences. Advanced mode does not teleport cars, magnetize them to the track, or bypass physics with hidden pose correction.
|
|
170
198
|
|
|
171
199
|
Simulator snapshots and environment observations expose additional vehicle telemetry: `lateralG`, `longitudinalG`, `gripUsage`, `slipAngleRadians`, `tractionLimited`, and `stabilityState`. These fields are optional public telemetry; existing `speedKph`, wheel-surface, steering, throttle, and brake fields remain unchanged.
|
|
172
200
|
|
|
@@ -194,7 +222,7 @@ DRS behavior:
|
|
|
194
222
|
|
|
195
223
|
Physics effect:
|
|
196
224
|
|
|
197
|
-
- Active DRS reduces drag through `src/simulation/vehiclePhysics.js
|
|
225
|
+
- Active DRS reduces drag through `src/simulation/vehicle/vehiclePhysics.js`; the root `src/simulation/vehiclePhysics.js` path is only a compatibility re-export.
|
|
198
226
|
- The current drag multiplier is `0.42`.
|
|
199
227
|
|
|
200
228
|
Visual effect:
|
|
@@ -246,17 +274,19 @@ Green-flag behavior uses:
|
|
|
246
274
|
|
|
247
275
|
The built-in racing controller still drives through normal vehicle inputs only. It chooses a forward racing target, blends steering from the target angle, upcoming track heading, lateral offset, edge recovery pressure, outward edge motion, and a corner-apex racing-line offset, then sends clamped steering/throttle/brake values into the vehicle physics integrator. It is expected to use the available track width and kerbs on generated circuits, including sharp generated turns. When heading alignment, steering load, or outward edge motion shows the car cannot yet carry throttle through a corner, throttle is cut before braking is requested so the controller does not fight itself. It does not snap car position, override heading, or bypass steering-rate/yaw-rate limits.
|
|
248
276
|
|
|
249
|
-
In
|
|
277
|
+
In advanced mode, the built-in controller uses separate tuning: earlier lookahead for braking, smaller high-speed steering requests, stronger edge recovery before the legal limit, throttle reduction when grip and slip telemetry show the car is overloaded, a higher legal-road speed floor so the baseline AI does not crawl through generated bends, and lower rejoin targets from kerb, grass, or gravel states. Extra stability braking is applied only when upcoming corner load, edge pressure, outward drift, high straight speed, or near-target speed makes it useful; ordinary below-target straight-line correction should not trigger brake pulses. This is intentionally a baseline driver, not a trained policy.
|
|
250
278
|
|
|
251
279
|
Traffic decisions use real meter-scaled gaps and lateral spacing, not raw simulator-size constants. A trailing car inside the attack window commits toward a visible passing lane instead of sitting on the centerline. A leading car with a close rear threat can defend the threatened side before the rival is fully alongside. Side-by-side overlap still creates spacing pressure so defensive and attacking moves are expressed as target-line choices and normal steering/brake/throttle inputs, not as collision avoidance teleports or artificial position overrides.
|
|
252
280
|
|
|
253
|
-
Rejoin behavior also remains physics-driven. When a car leaves the legal racing surface, the controller enters a short recovery hold and keeps using rejoin controls after the center point first reaches track or kerb until heading and edge margin are stable. That prevents the car from immediately returning to full racing throttle while still pointed across the track. Recovery targets look forward along the racing surface, bias inward from the edge, and request bounded surface-specific speeds for track, kerb, grass, and gravel states. In
|
|
281
|
+
Rejoin behavior also remains physics-driven. When a car leaves the legal racing surface, the controller enters a short recovery hold and keeps using rejoin controls after the center point first reaches track or kerb until heading and edge margin are stable. That prevents the car from immediately returning to full racing throttle while still pointed across the track. Recovery targets look forward along the racing surface, bias inward from the edge, and request bounded surface-specific speeds for track, kerb, grass, and gravel states. In advanced mode, rejoin controls also watch signed outward velocity relative to the track normal: a car sliding farther into runoff cuts throttle or brakes before trying to accelerate out, while very low-speed gravel/grass recovery limits steering lock and caps throttle when the heading is still outward so steering scrub does not pin the car in place. Barrier contact is not a recovery state; it destroys the car.
|
|
282
|
+
|
|
283
|
+
Advanced-mode steering scrub is dissipative in both forward and reverse motion. It opposes the signed forward velocity instead of always applying negative forward acceleration, so a car sliding backward with zero throttle cannot gain speed from steering scrub.
|
|
254
284
|
|
|
255
285
|
Tire energy can degrade to 1%. The vehicle physics layer converts tire energy into a nonlinear grip factor, so degradation has a visible performance cost across the full 100% to 1% range while still leaving a damaged car controllable enough to return to the pits.
|
|
256
286
|
|
|
257
287
|
## Vehicle Physics
|
|
258
288
|
|
|
259
|
-
Vehicle physics live in `src/simulation/vehiclePhysics.js`.
|
|
289
|
+
Vehicle physics live in `src/simulation/vehicle/vehiclePhysics.js`.
|
|
260
290
|
|
|
261
291
|
The model includes:
|
|
262
292
|
|
|
@@ -299,7 +329,7 @@ When pit stops are enabled, each team is assigned one shared service area in the
|
|
|
299
329
|
|
|
300
330
|
Collision handling uses:
|
|
301
331
|
|
|
302
|
-
- Shared vehicle geometry from `src/simulation/vehicleGeometry.js`.
|
|
332
|
+
- Shared vehicle geometry from `src/simulation/vehicle/vehicleGeometry.js`.
|
|
303
333
|
- A body collision hull for car-vs-car contact.
|
|
304
334
|
- Four wheel/contact-patch shapes for surface and track-limit state, not for car-vs-car contact.
|
|
305
335
|
- Track-progress candidate pruning before narrow-phase checks.
|
|
@@ -322,7 +352,7 @@ These are not currently implemented:
|
|
|
322
352
|
|
|
323
353
|
- Strategic pit-call timing beyond the current first automatic stop.
|
|
324
354
|
- Double stacking and pit-crew conflicts.
|
|
325
|
-
-
|
|
355
|
+
- Full race-strategy planning beyond the current automatic pit schedule and host/model pit intents.
|
|
326
356
|
- Fuel load strategy.
|
|
327
357
|
- Weather.
|
|
328
358
|
- Mechanical failures.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Model Sense Contract
|
|
2
|
+
|
|
3
|
+
This document defines the proof boundary for model-facing PaddockJS senses. The
|
|
4
|
+
active environment observation contract is the source of truth for policies,
|
|
5
|
+
Policy Runner inputs, expert visualization, and rollout recording.
|
|
6
|
+
|
|
7
|
+
## What Is Proven In Code
|
|
8
|
+
|
|
9
|
+
`src/__tests__/environmentSenseContract.test.js` is the executable contract for
|
|
10
|
+
core model-facing senses. It checks three layers:
|
|
11
|
+
|
|
12
|
+
1. Object observations are compared against independent formulas from the
|
|
13
|
+
returned simulator snapshot. This covers body telemetry, local track
|
|
14
|
+
relation, race state, lookahead samples, wheel contact patches, nearby-car
|
|
15
|
+
geometry, and opponent radar fields.
|
|
16
|
+
2. Vector observations are decoded by schema name and compared against the
|
|
17
|
+
object observation oracle. A vector entry is not accepted unless a matching
|
|
18
|
+
schema oracle exists.
|
|
19
|
+
3. Compact vector-only output is compared against full output for the same
|
|
20
|
+
deterministic state, and public per-driver observation specs are compared
|
|
21
|
+
against the schemas produced in observations.
|
|
22
|
+
|
|
23
|
+
This is intentionally stronger than testing current implementation against
|
|
24
|
+
itself: the object oracle uses simulator snapshot facts and unit conversions,
|
|
25
|
+
while the vector oracle uses schema names and object fields rather than calling
|
|
26
|
+
`buildObservationVector()`.
|
|
27
|
+
|
|
28
|
+
## Model-Facing Sense Rules
|
|
29
|
+
|
|
30
|
+
- Public distance fields with a `Meters` suffix are meters, not simulator
|
|
31
|
+
units.
|
|
32
|
+
- Public angle fields with a `Radians` suffix are radians.
|
|
33
|
+
- Speed is exposed both as `speedKph` and `speedMetersPerSecond`; the latter is
|
|
34
|
+
derived from the simulator speed unit conversion.
|
|
35
|
+
- `self.onTrack` and `trackRelation.onLegalSurface` follow wheel-level legal
|
|
36
|
+
surface rules when wheel contact patches exist.
|
|
37
|
+
- `contactPatches` always uses the stable order `front-left`, `front-right`,
|
|
38
|
+
`rear-left`, `rear-right`.
|
|
39
|
+
- `nearbyCars` are sorted by car-relative distance, with deterministic ordering
|
|
40
|
+
for equal distances.
|
|
41
|
+
- Opponent radar fields use actual advanced-mode velocity vectors when present.
|
|
42
|
+
- Ray object values, vector values, schemas, and visualizations must describe
|
|
43
|
+
the same active observation values. Debug-only precision may exist only when
|
|
44
|
+
clearly labeled and must not be presented as model input.
|
|
45
|
+
- `getObservationSpec()` is the canonical schema for compact loops, including
|
|
46
|
+
per-driver sensor overrides.
|
|
47
|
+
|
|
48
|
+
## Remaining Formal Limits
|
|
49
|
+
|
|
50
|
+
The test suite proves conformance to the executable contract above for the
|
|
51
|
+
deterministic scenarios it enumerates. It is not a mathematical proof over every
|
|
52
|
+
possible JavaScript object a caller could construct or every physically possible
|
|
53
|
+
track state. Extending the proof boundary requires adding more oracle cases for
|
|
54
|
+
the specific state family, for example replay ghosts, pit-service movement, or a
|
|
55
|
+
new sensor channel.
|
|
56
|
+
|
|
57
|
+
Any new model-facing sense must add or extend an oracle in
|
|
58
|
+
`environmentSenseContract.test.js`. Adding a vector schema field without an
|
|
59
|
+
oracle must fail the test.
|
package/docs/system_specs.md
CHANGED
|
@@ -35,11 +35,35 @@ import {
|
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
The environment subpath is the only public headless training import. It must stay free of DOM, PixiJS, CSS, and browser app dependencies.
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
|
|
39
|
+
Root package exports:
|
|
40
|
+
|
|
41
|
+
| Export group | Public exports | Use |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| Browser mounts | `mountF1Simulator`, `createPaddockSimulator` | all-in-one shell or composable package-owned UI surfaces |
|
|
44
|
+
| Composable helper mounts | `mountRaceControls`, `mountCameraControls`, `mountSafetyCarControl`, `mountTimingTower`, `mountRaceCanvas`, `mountTelemetryPanel`, `mountTelemetryCore`, `mountTelemetrySectors`, `mountTelemetrySectorBanner`, `mountTelemetryLapTimes`, `mountTelemetrySectorTimes`, `mountRaceTelemetryDrawer`, `mountCarDriverOverview`, `mountRaceDataPanel` | function-call wrappers around a composable simulator controller |
|
|
45
|
+
| Data helpers | `DriverData`, `VehicleData`, `CHAMPIONSHIP_ENTRY_BLUEPRINTS`, `DEMO_PROJECT_DRIVERS`, `buildChampionshipDriverGrid`, `formatDriverNumber`, `normalizeSimulatorDrivers` | demo data, host entry normalization, and rating helper construction |
|
|
46
|
+
| Package constants | `DEFAULT_F1_SIMULATOR_ASSETS`, `PADDOCK_SIMULATOR_PRESETS` | inspect bundled asset mapping and UI preset defaults |
|
|
47
|
+
| Track and units | `createProceduralTrack`, `REAL_F1_CAR_LENGTH_METERS`, `VISUAL_CAR_LENGTH_METERS`, `SIM_UNITS_PER_METER`, `TARGET_F1_TOP_SPEED_KPH`, `metersToSimUnits`, `simUnitsToMeters`, `kphToSimSpeed`, `simSpeedToKph`, `simSpeedToMetersPerSecond` | generated track definitions and physical/display unit conversion |
|
|
48
|
+
| Controller loop | `createPaddockDriverControllerLoop` | shared browser/headless batched model-controller orchestration |
|
|
49
|
+
|
|
50
|
+
Environment subpath exports:
|
|
51
|
+
|
|
52
|
+
| Export | Use |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `createPaddockEnvironment` | browser-free step/reset runtime for JavaScript training and evaluation loops |
|
|
55
|
+
| `createPaddockDriverControllerLoop` | shared controller-loop helper for browser expert mode or headless environments |
|
|
56
|
+
| `createProgressReward` | optional demo reward used by examples; not the package objective |
|
|
57
|
+
| `createRolloutRecorder`, `createRolloutTransition` | neutral rollout transition recording helpers |
|
|
58
|
+
| `DEFAULT_EVALUATION_CASES`, `createEvaluationTracker`, `runEnvironmentEvaluation` | deterministic evaluation cases and metric aggregation |
|
|
59
|
+
| `ENVIRONMENT_SCENARIO_PRESETS` | reset-only scenario placement presets |
|
|
60
|
+
| `createEnvironmentWorkerProtocol`, `handleEnvironmentMessage` | JSON-serializable worker bridge helpers |
|
|
61
|
+
|
|
62
|
+
`createProgressReward()` is published from the same subpath only as non-canonical demo reward code for examples and smoke tests. It must remain optional and replaceable; environment stepping must continue to work with a custom `reward(context)` callback or no reward callback. Reward callbacks are user-owned formulas over package-owned facts, including neutral per-driver `metrics` and `info.drivers[driverId]` episode state. They run only for `step(actions)` transition results; reset/read paths are reward-neutral. Non-finite or missing callback results are normalized to a neutral `0` reward for that controlled driver.
|
|
63
|
+
Ray observations expose track-transition distance and car distance per ray. Track transitions use `kind: 'exit'` when the ray starts on track and reaches the border, `kind: 'entry'` when the ray starts off track and reaches the road again, and `kind: null` with max distance when no transition is visible. Track-position, surface, pit-lane, and ray fallback queries use an internal non-enumerable track query index built with the track model. Browser/expert mounts, headless environments, direct race-simulation construction, and compact training-style environment runs all use the indexed path. This changes runtime cost, not public observation shapes.
|
|
40
64
|
The package must not own model training, model persistence, model registries, or trained policy behavior. The supported contract is: external code reads observations, returns normalized actions, and advances either the headless environment or browser expert mode.
|
|
41
65
|
The shared expert runtime exposes `getActionSpec()` and `getObservationSpec()` so external code can inspect controlled drivers, action ranges, sensor layout, nearby-car limits, track lookahead fields, and the versioned vector schema before connecting a model. Environment reset may apply deterministic scenario placement presets or explicit placement/traffic layouts; model steps may not mutate position and still use normalized actions only. Neutral rollout recording, deterministic evaluation metrics, and the JSON worker protocol are environment utilities, not training algorithms.
|
|
42
|
-
`createPaddockDriverControllerLoop({ runtime, controller, actionRepeat })` is the shared public controller orchestrator for browser playback and JavaScript training-style loops. `runtime` may be browser expert mode or a headless environment, and `controller.decideBatch(context)` is called once per policy step for all controlled drivers in stable spec order. The loop lazily resets when scheduled playback starts from a fresh instance, caches `actionSpec` and `observationSpec`, reuses the active observation payload, supports async controllers, forwards actions through the normal `step(actions)` API, and repeats each decision for `actionRepeat` runtime frames. Scheduled playback stops and records `stats.lastError` when controller initialization, decision, stepping, or `onStep` hooks fail. It does not load checkpoints, run neural networks, parse model formats, compute rewards, or mutate vehicle state.
|
|
66
|
+
`createPaddockDriverControllerLoop({ runtime, controller, actionRepeat })` is the shared public controller orchestrator for browser playback and JavaScript training-style loops. `runtime` may be browser expert mode or a headless environment, and `controller.decideBatch(context)` is called once per policy step for all controlled drivers in stable spec order. The loop lazily resets when scheduled playback starts from a fresh instance, caches `actionSpec` and `observationSpec`, reuses the active observation payload, supports async controllers, forwards actions through the normal `step(actions)` API, and repeats each decision for `actionRepeat` runtime frames. Scheduled playback stops and records `stats.lastError` when controller initialization, decision, stepping, or `onStep` hooks fail. `stop()` cancels pending package-owned scheduler handles and custom scheduler handles that either expose `cancel()` or return browser-style numeric timeout/animation-frame ids. It does not load checkpoints, run neural networks, parse model formats, compute rewards, or mutate vehicle state.
|
|
43
67
|
|
|
44
68
|
All-in-one mount call:
|
|
45
69
|
|
|
@@ -50,9 +74,13 @@ const simulator = await mountF1Simulator(root, {
|
|
|
50
74
|
onDriverOpen,
|
|
51
75
|
seed,
|
|
52
76
|
trackSeed,
|
|
77
|
+
trackGeneration,
|
|
53
78
|
warmup,
|
|
54
79
|
totalLaps,
|
|
55
80
|
physicsMode,
|
|
81
|
+
rules,
|
|
82
|
+
participantInteractions,
|
|
83
|
+
replayGhosts,
|
|
56
84
|
initialCameraMode,
|
|
57
85
|
preset,
|
|
58
86
|
title,
|
|
@@ -62,6 +90,7 @@ const simulator = await mountF1Simulator(root, {
|
|
|
62
90
|
showBackLink,
|
|
63
91
|
ui,
|
|
64
92
|
theme,
|
|
93
|
+
debug,
|
|
65
94
|
assets,
|
|
66
95
|
onLoadingChange,
|
|
67
96
|
onReady,
|
|
@@ -74,6 +103,8 @@ const simulator = await mountF1Simulator(root, {
|
|
|
74
103
|
});
|
|
75
104
|
```
|
|
76
105
|
|
|
106
|
+
The all-in-one shell normalizes `backLinkHref` before rendering it. Package-owned race controls accept relative URLs, hash URLs, and absolute `http:` / `https:` URLs; unsafe or malformed values fall back to the default `projects.html` link instead of being rendered into the anchor. Runtime readouts also validate snapshot-derived color values before writing CSS custom properties, and package-owned asset URLs are escaped before becoming CSS `url(...)` values.
|
|
107
|
+
|
|
77
108
|
Composable mount call:
|
|
78
109
|
|
|
79
110
|
```js
|
|
@@ -83,8 +114,21 @@ const simulator = createPaddockSimulator({
|
|
|
83
114
|
onDriverOpen,
|
|
84
115
|
seed,
|
|
85
116
|
trackSeed,
|
|
117
|
+
trackGeneration,
|
|
118
|
+
warmup,
|
|
86
119
|
totalLaps,
|
|
120
|
+
physicsMode,
|
|
87
121
|
initialCameraMode,
|
|
122
|
+
participantInteractions,
|
|
123
|
+
replayGhosts,
|
|
124
|
+
rules: {
|
|
125
|
+
ruleset: 'custom',
|
|
126
|
+
modules: {
|
|
127
|
+
pitStops: { enabled: true },
|
|
128
|
+
stalledDnf: { enabled: true },
|
|
129
|
+
tireStrategy: { enabled: true, mandatoryDistinctDryCompounds: 2 },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
88
132
|
});
|
|
89
133
|
|
|
90
134
|
simulator.mountRaceControls(controlsRoot);
|
|
@@ -115,6 +159,8 @@ simulator.setPitLaneOpen(false);
|
|
|
115
159
|
simulator.setRedFlagDeployed(true);
|
|
116
160
|
```
|
|
117
161
|
|
|
162
|
+
Pit APIs are rule-gated. `setPitIntent()` returns `false` until the active rules enable `rules.modules.pitStops.enabled` and the active track has pit-lane geometry. `tireStrategy` does not enable automatic pit routing by itself; it only controls available compounds, tire requirement stewarding, and pit target choices.
|
|
163
|
+
|
|
118
164
|
Standalone helper functions are also exported for host code that prefers function calls:
|
|
119
165
|
|
|
120
166
|
```js
|
|
@@ -157,7 +203,10 @@ Returned controller:
|
|
|
157
203
|
mountTelemetryLapTimes(root),
|
|
158
204
|
mountTelemetrySectorTimes(root),
|
|
159
205
|
mountRaceTelemetryDrawer(root, { timingTowerVerticalFit, drawerInitiallyOpen, raceDataTelemetryDetail }),
|
|
206
|
+
mountCarDriverOverview(root),
|
|
160
207
|
mountRaceDataPanel(root),
|
|
208
|
+
querySelector(selector),
|
|
209
|
+
querySelectorAll(selector),
|
|
161
210
|
start(),
|
|
162
211
|
|
|
163
212
|
// Included on both APIs:
|
|
@@ -189,36 +238,41 @@ Returned controller:
|
|
|
189
238
|
- Composable mounting can embed the timing tower directly inside the race canvas with `mountRaceCanvas(root, { includeTimingTower: true })`.
|
|
190
239
|
- The race canvas is required before `start()` because PixiJS needs a canvas host.
|
|
191
240
|
- Timing tower, telemetry, controls, and race-data panels are optional from a runtime safety perspective; omitted panels simply do not render their readouts.
|
|
192
|
-
- Timing tower entries are
|
|
193
|
-
- Timing tower entries display team icons.
|
|
241
|
+
- Timing tower entries are compact content-sized rows stacked from the top of the timing list. Row vertical position must be based on rank/index, never distributed by available height or total entry count. `ui.timingEntryVerticalPadding` defaults to `5` and applies that pixel value above and below every row entry.
|
|
242
|
+
- Timing tower entries display team icons. `ui.timingGapMode` sets the initial display to interval-to-car-ahead timing or direct gap-to-leader timing, and the controller can change it at runtime with `setTimingGapMode()`, `getTimingGapMode()`, or `toggleTimingGapMode()`. The tower shows one compact `Int`/`Gap` header toggle by default. That toggle must stay aligned with the other timing header labels, preserve a practical `44px` target, draw active/focus treatment inside the timing header rather than outside the column, and never change the timing board width. `ui.timingGapModeToggle: false` hides only the manual header affordance; it must not disable the controller API. Seconds gaps are calculated from hidden timing-line crossings, and whole-lap deficits display as `+N`.
|
|
194
243
|
- Snapshots expose calibrated display units through `speedKph`, `distanceMeters`, and `gapMeters`; internal physics remains in simulator units. Public timer, lap-time, sector-time, gap-time, penalty-time, and service-countdown values are seconds.
|
|
195
244
|
- Snapshots expose automatic three-sector track metadata through `track.sectors` and per-car lap/sector timing through `car.lapTelemetry`, including completed current-lap sector splits before the active sector, live active-sector elapsed time, per-sector progress, and sector performance classes for overall-best, personal-best, and slower completed sector times. Future-sector timing/progress entries are cleared so banner and sidebar telemetry cannot display stale split values from another lap or state.
|
|
196
245
|
- Entries can include optional `team` metadata. Team color defaults to car color when omitted.
|
|
197
246
|
- Mounted package surfaces show a package-owned red start-light loading overlay until `start()` finishes PixiJS, asset, control, and initial readout initialization.
|
|
198
247
|
- `preset` is a preset-first API. Presets are resolved before explicit host overrides so hosts can use `dashboard`, `timing-overlay`, `compact-race`, or `full-dashboard` as a starting point and still override specific `ui` or `theme` fields.
|
|
199
|
-
- `theme` is the public sizing/color contract. It
|
|
200
|
-
- `initialCameraMode` accepts `'overview'`, `'leader'`, `'selected'`, `'show-all'`, or `'pit'`; invalid values fall back to `'leader'`. The overview camera frames the active generated track bounds with package-owned padding and pit-lane extent. The pit camera frames the operational `track.pitLane` lane, boxes, service areas, and queue areas instead of the longer entry/exit access roads, zooms out when needed to keep that pit-lane work area inside the active race-view safe area, and its control is hidden/disabled if the active track has no pit lane. Zoom buttons and wheel zoom work in every camera mode, including overview, show-all, and pit, but cannot zoom farther out than the active track frame.
|
|
201
|
-
- Camera controls default to external placement so they do not cover the race view. They can still be embedded in the race canvas by setting `ui.cameraControls: 'embedded'`, externally mounted, or omitted with `false`. Generated camera controls include a `Mute banners` toggle that is off by default and temporarily suppresses project/radio lower-thirds while active.
|
|
248
|
+
- `theme` is the public sizing/color contract. It resolves semantic package-owned tokens into complete light/dark themes, supports named derivative theme packages through `theme.themes`, selects the active package with `theme.use`, applies component-specific packages through `theme.componentThemes`, and accepts team selectors through `theme.teamThemes` or `entries[*].team.theme`. Component theme keys accept package component ids such as `timing-tower` and camelCase aliases such as `timingTower`; `selectedDriverPanel` targets both selected-driver package surfaces. Selected-driver surfaces use the selected team's theme by default when one is available while structural controls keep the active global theme unless explicitly overridden. 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` remain migration aliases for the new semantic tokens.
|
|
249
|
+
- `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 with package-owned padding and pit-lane extent. The driver camera follows the selected car from a lower screen anchor and rotates the world so the selected car points upward; its control is opt-in through `ui.driverCamera: true`, and `initialCameraMode: 'driver'` enables that control automatically. The pit camera frames the operational `track.pitLane` lane, boxes, service areas, and queue areas instead of the longer entry/exit access roads, zooms out when needed to keep that pit-lane work area inside the active race-view safe area, and its control is hidden/disabled if the active track has no pit lane. Zoom buttons and wheel zoom work in every camera mode, including overview, show-all, driver, and pit, but cannot zoom farther out than the active track frame.
|
|
250
|
+
- Camera controls default to external placement so they do not cover the race view. They can still be embedded in the race canvas by setting `ui.cameraControls: 'embedded'`, externally mounted, or omitted with `false`. Generated camera controls include a `Mute banners` toggle that is off by default and temporarily suppresses project/radio lower-thirds while active. The driver camera button is omitted unless `ui.driverCamera` is active.
|
|
202
251
|
- Telemetry surfaces are detached package components: core scalar readouts, sector graph, broadcast sector banner, lap-time table, and sector-time table. The broadcast sector banner shows the selected car identity, uses the selected car color for its frame/label, and keeps sector performance colors inside the sector bars. It is an explicitly mounted independent surface, not the default telemetry-drawer lower-third. `mountTelemetryPanel()` is the stack template around those detached pieces, owns vertical scrolling when constrained, and `ui.telemetryModules` controls which pieces appear in stack/drawer templates.
|
|
203
|
-
- `mountRaceTelemetryDrawer()` creates a package-owned race workbench: external top controls, race canvas, embedded timing tower, lower-third banner, top steward message, safety-car control, and a right-side telemetry drawer. The top row holds camera controls, banner mute, safety car, and telemetry toggle outside the canvas. Pass `{ raceDataTelemetryDetail: true }` when the drawer lower-third should include compact project telemetry detail. The drawer opens smoothly, takes width from the race view instead of overlaying it, is inert/hidden to interaction when closed, and embeds the same `telemetry-stack` component used by standalone telemetry panels.
|
|
252
|
+
- `mountRaceTelemetryDrawer()` creates a package-owned race workbench: external top controls, race canvas, embedded timing tower, lower-third banner, top steward message, safety-car control, and a right-side telemetry drawer. The top row holds camera controls, banner mute, safety car, and telemetry toggle outside the canvas. On narrow responsive containers, that toolbar stacks into readable full-width rows and keeps safety car plus telemetry as a two-column action row instead of squeezing them beside the camera controls. Pass `{ raceDataTelemetryDetail: true }` when the drawer lower-third should include compact project telemetry detail. The drawer opens smoothly, takes width from the race view instead of overlaying it, is inert/hidden to interaction when closed, and embeds the same `telemetry-stack` component used by standalone telemetry panels.
|
|
204
253
|
- The FPS readout can be shown or hidden with `ui.showFps`.
|
|
205
254
|
- `ui.layoutPreset: 'left-tower-overlay'` is a package-owned preset that creates a left broadcast gutter inside the race view, places the timing tower there at the same width as the default timing-board column, frames the PixiJS camera around the remaining usable race area, and keeps camera controls and start lights out of the tower area. In the combined shell, project and radio lower-thirds stay inside the race window while being allowed to cover the timing sidebar.
|
|
206
255
|
- `ui.raceDataBanners.initial` controls the starting lower-third (`'project'`, `'radio'`, or `'hidden'`), and `ui.raceDataBanners.enabled` controls which lower-third types can appear. The runtime banner mute state temporarily disables project/radio lower-thirds without changing these configured allow-lists.
|
|
207
256
|
- Project and radio lower-third pills include a package-owned top-right close button that dismisses the current pill early. The next pill appears only through normal driver selection or radio scheduling.
|
|
257
|
+
- When a composable host mounts more than one package-owned race-data panel, such as an embedded race-canvas lower-third plus `mountRaceDataPanel()`, the panels share the same runtime project/radio/hide state and each panel's open/dismiss controls remain wired.
|
|
208
258
|
- `ui.raceDataBannerSize` controls lower-third sizing: `'custom'` keeps the default CSS-variable-driven banner size for host tuning, while `'auto'` uses the race space to the right of the timing board when wide enough and overlaps the timing board only when space is constrained.
|
|
209
259
|
- `ui.raceDataTelemetryDetail` adds compact S1/S2/S3 sector detail to the project lower-third while keeping radio mode unchanged.
|
|
210
260
|
- `ui.timingTowerVerticalFit` controls vertical tower behavior in the combined overlay preset: `'expand-race-view'` grows the race window to fit the tower, while `'scroll'` crops the tower area and scrolls timing rows inside it. The same values are accepted as `mountRaceCanvas()` options when `includeTimingTower` embeds the tower in the race canvas.
|
|
211
|
-
-
|
|
261
|
+
- `ui.timingGapMode` controls whether the timing tower starts in `'interval'` or `'leader'` mode. `ui.timingGapModeToggle` defaults to `true`; setting it to `false` hides the manual header toggle while preserving runtime control through the mounted simulator/controller methods. Hosts that need a different control placement should hide the package toggle and call those methods from host-owned UI instead of restyling package internals.
|
|
262
|
+
- Hosts may scale the whole mounted simulator through the container. The horizontal proportions inside package-owned presets are not public API and should not be configurable through raw width or ratio options. The supported inline-size envelope starts at `320px` for the package-owned mount root or individual composable surface. Hosts should provide responsive outer containers with `min-width: 0` and avoid targeting package internals; PaddockJS owns timing-board width, camera safe-area math, lower-third containment, drawer sizing, and internal density tiers. The camera reads the current canvas dimensions so wider or taller host windows reveal more of the race view without needing host-owned camera math, and the renderer keeps grass coverage beyond the simulated world so deep zoom-out does not show host background. The timing tower has a package-owned max width because overly wide timing boards degrade readability; standalone hosts can constrain vertical height through the mount container and let the timing entries scroll internally. Mobile and narrow embeds are handled by package CSS plus a package-owned resize observer: embedded timing towers become left reveal panels when they no longer work as side gutters, camera controls stay in external control rows when mounted, narrow telemetry drawers reveal over the race view instead of forcing permanent canvas shrinkage, and closed reveal panels stay inert/hidden while the renderer and camera resize continuously without simulator remounts. A narrow lower-third may cover empty timing-tower chrome, but if the measured lower-third would cover any timing entry, PaddockJS adds measured lower-third clearance to the race view's minimum height; when timing entries remain clear, the height remains governed by the timing tower/race-view minimum. Template responsive narrow variants are enabled by default through `ui.responsiveNarrowLayout`; hosts can set `ui.responsiveNarrowLayout: false`, or pass `responsiveNarrowLayout: false` to composable race-canvas/drawer mounts, to opt out.
|
|
263
|
+
- Package layout density tiers are container-owned: `320px`-`519px` is mobile density, `520px`-`759px` is narrow tablet density, `760px`-`1119px` is tablet density, and `1120px` and wider is desktop density. Component behavior follows the actual package container width, not only the browser viewport, so embedded timing towers and composable pieces remain safe inside split tablet layouts.
|
|
264
|
+
- If the shell/mount root is narrower than `320px`, or if an individual package component is constrained below its component-specific inline/block minimum, the component sets `data-paddock-size-unsupported="true"` and displays a package-owned accessible unsupported-size placeholder instead of broken UI. The broadcast timing tower is one intentional exception to a blanket `320px` subcomponent rule: it remains valid at its package-owned narrow timing-board width. The placeholder uses `role="status"` and `aria-live="polite"`. Hosts should fix the outer container size rather than hiding package content or patching package internals.
|
|
265
|
+
- Package controls, drawer toggles, lower-third actions, and dismiss buttons maintain practical `44px` touch targets, shared visible focus states, and `touch-action: manipulation`. Timing rows are compact selectable broadcast entries controlled by `ui.timingEntryVerticalPadding`. Touch driving or virtual joystick controls are intentionally out of scope.
|
|
212
266
|
- The host does not need to provide simulator assets.
|
|
213
267
|
- The host passes data, not internal DOM.
|
|
214
268
|
- Host driver IDs and entry `driverId` values must be unique. Entries may omit `driverNumber`; provided numbers must be unique.
|
|
215
269
|
- `totalLaps` is normalized to a finite positive integer before simulation so invalid input cannot produce zero-lap, negative-lap, or non-finite snapshots.
|
|
216
|
-
- `physicsMode` accepts `'arcade'` and `'
|
|
270
|
+
- `physicsMode` accepts `'arcade'` and `'advanced'`. The default is `'arcade'` to preserve existing hosts. `'advanced'` is opt-in and keeps cars controlled only through steering, throttle, brake, and pit intent while enabling traction-budget limits, steering scrub, velocity-heading slip, surface-specific grip/drag, and advanced telemetry. The old `'simulator'` mode name is removed and falls back to `'arcade'` like any other invalid value. Snapshots expose `physicsMode` plus per-car `lateralG`, `longitudinalG`, `gripUsage`, `slipAngleRadians`, `tractionLimited`, `stabilityState`, and the latest `appliedControls`.
|
|
217
271
|
- `restart(nextOptions)` can change race data and deterministic seeds such as `trackSeed`, but it does not support changing asset URLs. Asset changes require `destroy()` and a fresh mount because PixiJS texture loading is an initialization boundary.
|
|
218
272
|
- `onDriverOpen(driver)` is the navigation boundary.
|
|
219
|
-
- Lifecycle callbacks are optional: `onLoadingChange`, `onReady`, `onError`, `onDriverSelect`, `onRaceEvent`, `onLapChange`, and `onRaceFinish`.
|
|
273
|
+
- Lifecycle callbacks are optional: `onLoadingChange`, `onReady`, `onError`, `onDriverSelect`, `onRaceEvent`, `onLapChange`, and `onRaceFinish`. Lifecycle callback failures are routed to `onError` when possible and must not stop the simulator loop. `onDriverOpen(driver)` is host-owned navigation code and is not wrapped as a lifecycle callback.
|
|
220
274
|
- Race completion is part of the simulation snapshot. Cars receive individual `finished`, `finishTime`, `finishRank`, `status: 'waved-flag'`, `raceStatus: 'waved-flag'`, `wavedFlag`, `penaltySeconds`, `adjustedFinishTime`, and `classifiedRank` values as they cross the finish distance. Destroyed/out-of-race cars expose `dnf`, `dnfReason`, `dnfAt`, and `dnfOrder`, appear below active cars in timing, render faded/gray in the race canvas, and do not block race completion while they remain DNF. The first finisher sets a provisional `raceControl.winner`; already-finished cars remain frozen in provisional finish order while the remaining cars complete the distance. Final `raceControl.classification` and `raceControl.finished` are set only after every race participant has finished or is DNF. DNF entries are included after finishers with no finish time. A DNF car restored before final classification re-enters live timing and must finish; after final classification, resurrection does not reopen the race. Final classification converts unserved drive-through and stop-go penalties into configured time, sorts finishers by `finishTime + penaltySeconds`, applies position-drop and disqualification consequences, then race control switches to `safety-car`, the final order freezes to the classified result, and the field keeps circulating under safety-car behavior.
|
|
221
|
-
- Race rules support package presets and custom module config. Supported rulesets are `paddock`, `grandPrix2025`, `fia2025`, and `custom`.
|
|
275
|
+
- Race rules support package presets and custom module config. Supported rulesets are `paddock`, `grandPrix2025`, `fia2025`, and `custom`. Active advanced modules include pit stops, tire strategy, tire degradation, stalled off-track DNF, and penalties. Weather, reliability, and fuel load are reserved module placeholders only; they do not currently change grip, power, retirement risk, mass, or pace. The current simulator normalizes all module config, creates/renders track-owned pit-lane geometry on tracks whose resolved generation options enable it, treats pit-lane asphalt, working-lane service areas, and garage boxes as legal drivable surfaces, runs automatic bounded pit-train entry/service/exit through the main fast lane and shared team service areas when pit stops are enabled, lets host/expert pit calls choose a target tire compound, uses the team queue point as a rolling gate when the service area is free, queues a second team car behind the active service area and moves it forward through a queue-release route only after the active service area is physically clear, keeps pit-route approach speed bounded until the final queue/service capture instead of crawling through open route, optionally varies pit-service time from team pit-crew stats with a perfect-training override, supports pit-lane open/closed state plus red flags, requests pit stops from configurable tire-energy thresholds, applies nonlinear tire-grip degradation down to 1% unless `rules.modules.tireDegradation.enabled` is `false`, can retire cars that remain off legal racing/pit surfaces below the configured stalled-DNF speed threshold for the configured duration when `rules.modules.stalledDnf.enabled` is `true`, applies the pit speed limiter only on the main pit lane/working lane, enforces pit-lane speeding on speed-limited pit-lane parts but not on entry/exit connectors, keeps the safety car about `55m` ahead of the leader with compact `22m` frozen-order queue slots, lets the built-in driver AI ride kerbs, attack, defend, and recover through normal steering/throttle/brake decisions with short controller-state commitments for rejoin and attack decisions, and enforces stewarded penalties for collisions, track limits, pit-lane speeding, and tire requirements. `tireStrategy` does not enable automatic pit stops; `pitStops.enabled` is required for pit routing and non-clear pit intent requests.
|
|
222
276
|
- Penalty subsections use `strictness` from `0` to `1`, not only boolean enablement. `strictness: 0` means the subsection is not enforced; `strictness: 1` applies the configured rule margin. Steward decisions are exposed as `penalty` events and as top-level `snapshot.penalties`. Penalty entries include normalized consequences and lifecycle status. Immediate consequences apply time, grid, position, or disqualification effects directly; drive-through and stop-go consequences remain issued until served, cancelled, or converted at final classification.
|
|
223
277
|
- The browser UI can opt into top steward messages with `ui.penaltyBanners` and timing-row penalty badges with `ui.timingPenaltyBadges`. Steward messages render track-limit warning events and penalty decisions from the simulation; time-penalty messages put the penalty seconds in a large left chip and use penalty-colored backgrounds, while warnings use warning-colored backgrounds. Timing-row `!` badges are rendered only from `snapshot.penalties`; warning events do not count. UI code must not recalculate steward decisions.
|
|
224
278
|
- The simulator must stay interactive after being installed through `npm install @inventure71/paddockjs`.
|
|
@@ -227,15 +281,15 @@ Returned controller:
|
|
|
227
281
|
- Warmup is enabled by default for browser mounts, headless environments, and direct simulation creation. It runs on a disposable runtime and must not mutate the visible initial race state; under the default `warmup.policy: 'config-change'`, identical configuration fingerprints reuse cached warmup while seed/config changes rerun warmup automatically.
|
|
228
282
|
- When `trackSeed` is omitted in a browser mount, the simulator creates a fresh procedural circuit for that mount. Explicit `trackSeed` values are deterministic and cached by seed plus resolved generation options for repeated mounts. Cached procedural definitions are immutable; callers that import `createProceduralTrack(seed, options)` should treat the returned definition as read-only and clone it before custom mutation. `trackGeneration` forwards the same procedural options used by `createProceduralTrack(seed, options)`: `profile`, `length`, `startStraight`, `pitLane`, `shape`, `validation`, and `attempts`. The `race` profile preserves the default full circuit with pit lane; `training-short`, `training-medium`, and `training-technical` are smaller pitless presets intended for training or demos. Explicit option fields override profile defaults after the profile is resolved. Procedural generation traces seeded connected region boundaries, smooths and warps them into centerline controls, then rejects circuits with excessive local heading jumps, turn accumulation, self-intersections, poor clearance, invalid length, or weak shape variation.
|
|
229
283
|
- The renderer should target a paced 60 FPS simulation/render loop.
|
|
230
|
-
- The render loop should pause while the race canvas is offscreen or the document is hidden, then resume without catching up the elapsed hidden time. Layout measurements needed for overlay camera safe areas should be cached between resize/layout invalidations. Runtime DOM updates should skip unchanged text/markup so visible embeds do not rewrite stable readouts every frame.
|
|
284
|
+
- The render loop should pause while the race canvas is offscreen or the document is hidden, then resume without catching up the elapsed hidden time. Runtime visibility must not rely on a single observer state: it should resync from canvas geometry after observer, scroll, resize, focus, and page-show signals, and ticker sync should reconcile the intended runtime state with PixiJS' actual started/stopped state. Layout measurements needed for overlay camera safe areas should be cached between resize/layout invalidations. Runtime DOM updates should skip unchanged text/markup so visible embeds do not rewrite stable readouts every frame.
|
|
231
285
|
- Restart and rerender paths must destroy replaced PixiJS display children while preserving shared loaded textures.
|
|
232
286
|
- Restart supports race/data/seed changes but does not support asset URL changes or browser expert mode changes. Texture loading and ticker ownership are mount-time boundaries; hosts must destroy and mount again to change either boundary.
|
|
233
|
-
- Expert environment code can create a headless `createPaddockEnvironment()` from the `@inventure71/paddockjs/environment` subpath. It requires explicit `controlledDrivers`, accepts normalized actions `{ steering, throttle, brake }` plus optional `pitIntent` and `pitCompound`, advances only through `step(actions)`, and returns environment-loop JavaScript results with `observation`, `reward`, `metrics`, `terminated`, `truncated`, `done`, `events`, `state`, and `info`. `steering` is an absolute normalized steering target: `-1` points at maximum left, `0` points at center, `1` points at maximum right, and intermediate values are percentages of the maximum steering angle; the vehicle integrator rate-limits motion toward that target instead of snapping the wheel. `steering`, `throttle`, and `brake` are required for each controlled-driver action. Strict action policy throws on missing or non-finite vehicle controls, while report policy records the error and releases stale manual controls so the built-in driver owns that physics step. `info.drivers[driverId]` exposes per-driver `terminated`, `truncated`, `endReason`, `episodeStep`, and `episodeId` for batched loops; selected-driver resets clear only selected driver truncation/termination and do not rewind global `info.step`. Controlled drivers do not receive tire-threshold automatic pit calls; they request automatic pit service through `pitIntent`, may choose the target tire through `pitCompound`, and observe pit-lane/service/race-control state in `observation[driverId].object.self` and `.race`. `pitIntent: 0` is a no-op clear request and does not fail when pit stops are disabled. `self.onTrack` follows wheel-level legality, so track, kerb, and legal pit-lane/box surfaces are on-track while gravel/grass/barrier are off-track. Barrier-wall contact in both physics modes is measured against the rendered wall's inner face; contact marks the car destroyed/DNF, removes it from active collision/sensor participation, emits a `car-destroyed` event, and terminates that driver's episode with `endReason: 'destroyed'`. Opt-in stalled off-track DNF removes the car from the same active participation systems while keeping `destroyed: false` and terminates controlled-driver episodes with `endReason: 'stalled-off-track'` when `rules.modules.stalledDnf.enabled` is `true`. Reset is still an episode-boundary `resetDrivers()` call. Environment scenarios may set reset positions with `preset`, `placements`, and relative `traffic`; `resetDrivers(placements)` may reset selected controlled drivers between episodes without recreating the whole simulation. Partial `reset(options)` calls preserve omitted nested option groups while replacing arrays and explicit scenario placement maps. After placement, selected cars are classified by the same runoff/barrier rules before observations are returned, so cars already inside terminal barrier space report destroyed metrics and stable miss-valued rays instead of doing alive-car far-out ray scans. Both scenario placement and `resetDrivers()` are setup data, not policy actions. `participantInteractions` may make real physics cars non-colliding, sensor-hidden, non-blocking for pit occupancy, or excluded from race order, but those cars still live in `snapshot.cars` and move only through normal physics/control APIs. The `batch-training` profile is non-colliding, sensor-hidden, pit-non-blocking, excluded from race order, and still rendered. `replayGhosts` are separate trajectory-driven entities in `snapshot.replayGhosts`; they are visual/reference overlays and never enter car physics, timing, pit, order, or penalty systems. Replay ghosts are sensor-hidden unless their own sensor flags explicitly opt them into ray or nearby observations.
|
|
287
|
+
- Expert environment code can create a headless `createPaddockEnvironment()` from the `@inventure71/paddockjs/environment` subpath. It requires explicit `controlledDrivers`, accepts normalized actions `{ steering, throttle, brake }` plus optional `pitIntent` and `pitCompound`, advances only through `step(actions)`, and returns environment-loop JavaScript results with `observation`, `reward`, `metrics`, `terminated`, `truncated`, `done`, `events`, `state`, and `info`. `steering` is an absolute normalized steering target: `-1` points at maximum left, `0` points at center, `1` points at maximum right, and intermediate values are percentages of the maximum steering angle; the vehicle integrator rate-limits motion toward that target instead of snapping the wheel. `steering`, `throttle`, and `brake` are required for each controlled-driver action. Strict action policy throws on missing or non-finite vehicle controls, while report policy records the error and releases stale manual controls so the built-in driver owns that physics step. `info.drivers[driverId]` exposes per-driver `terminated`, `truncated`, `endReason`, `episodeStep`, and `episodeId` for batched loops; selected-driver resets clear only selected driver truncation/termination and do not rewind global `info.step`. For scoped reset results, `info.controlledDrivers` matches the drivers present in `observation` and `metrics`, while `info.drivers` preserves episode state for all configured controlled drivers. Controlled drivers do not receive tire-threshold automatic pit calls; they request automatic pit service through `pitIntent`, may choose the target tire through `pitCompound`, and observe pit-lane/service/race-control state in `observation[driverId].object.self` and `.race`. `pitIntent: 0` is a no-op clear request and does not fail when pit stops are disabled. `self.onTrack` follows wheel-level legality, so track, kerb, and legal pit-lane/box surfaces are on-track while gravel/grass/barrier are off-track. Barrier-wall contact in both physics modes is measured against the rendered wall's inner face; contact marks the car destroyed/DNF, removes it from active collision/sensor participation, emits a `car-destroyed` event, and terminates that driver's episode with `endReason: 'destroyed'`. Opt-in stalled off-track DNF removes the car from the same active participation systems while keeping `destroyed: false` and terminates controlled-driver episodes with `endReason: 'stalled-off-track'` when `rules.modules.stalledDnf.enabled` is `true`. Reset is still an episode-boundary `resetDrivers()` call. Environment scenarios may set reset positions with `preset`, `placements`, and relative `traffic`; `resetDrivers(placements)` may reset selected controlled drivers between episodes without recreating the whole simulation. Partial `reset(options)` calls preserve omitted nested option groups while replacing arrays and explicit scenario placement maps. After placement, selected cars are classified by the same runoff/barrier rules before observations are returned, so cars already inside terminal barrier space report destroyed metrics and stable miss-valued rays instead of doing alive-car far-out ray scans. Both scenario placement and `resetDrivers()` are setup data, not policy actions. `participantInteractions` may make real physics cars non-colliding, sensor-hidden, non-blocking for pit occupancy, or excluded from race order, but those cars still live in `snapshot.cars` and move only through normal physics/control APIs. The `batch-training` profile is non-colliding, sensor-hidden, pit-non-blocking, excluded from race order, and still rendered. `replayGhosts` are separate trajectory-driven entities in `snapshot.replayGhosts`; they are visual/reference overlays and never enter car physics, timing, pit, order, or penalty systems. Replay ghosts are sensor-hidden unless their own sensor flags explicitly opt them into ray or nearby observations.
|
|
234
288
|
- Headless environments also accept an optional `externalRenderer` observer hook. It receives `{ snapshot, observation, meta }` on `reset`, `step`, and `resetDrivers`, so external tooling can stream authoritative frames to visualizers without letting that transport mutate simulation state.
|
|
235
289
|
- Controller loop code should use `createPaddockDriverControllerLoop()` instead of duplicating browser/headless stepping. Its controller context groups observations by controlled driver id, includes `orderedObservations` for direct batch tensor assembly, exposes metrics, events, cached specs, and reset-driver ids, and keeps per-driver resets as controller reset hooks when the runtime supports `resetDrivers()`. `previousActions` is the last action map actually applied before the current `actions`; on the first controlled frame after reset it is empty, and after repeated frames it reflects the prior physics step rather than pre-copying the current decision.
|
|
236
|
-
- Expert ray sensors originate from the controlled car center. The default compact set is `[-135, -60, -20, 0, 20, 60, 135, 180]`, giving forward, side, and rear awareness while staying small. Rays detect track edges against the actual track geometry and detect car hits by ray-to-car-footprint intersection. Hosts may opt into per-ray lengths, predefined layouts such as `driver-front-heavy`, and surface-aware channels for kerb and illegal surface detection; those channels are computed only when requested
|
|
290
|
+
- Expert ray sensors originate from the controlled car center. The default compact set is `[-135, -60, -20, 0, 20, 60, 135, 180]`, giving forward, side, and rear awareness while staying small. Rays detect track edges against the actual track geometry and detect car hits by ray-to-car-footprint intersection. Hosts may opt into per-ray lengths, predefined layouts such as `driver-front-heavy`, and surface-aware channels for kerb and illegal surface detection; those channels are computed only when requested. `precision: 'driver'` road-edge, kerb, and illegal-surface channels use the canonical indexed track projection path with driver-precision validation, preserving model-facing distances without substituting debug-only refinement. Barrier walls are rendered/physical terminal boundaries, not model-facing ray targets; active ray objects, vectors, schemas, and visualizations must not expose a `barrier` ray channel. Ray `precision` defaults to `driver`, which is the active model-facing sensor contract; `debug` precision is only for explicitly labeled diagnostics. `observation.profile: 'physical-driver'` exposes local driver-like yaw, contact-patch, boundary, richer ray, and opponent-radar senses while omitting default lookahead samples unless explicitly configured. `observation.output` can be `full`, `vector`, or `object`, `includeSchema: false` omits repeated schema payloads, and `vectorType: 'float32'` returns typed vector buffers for high-throughput JavaScript loops. `result.stateOutput` can be `full`, `minimal`, or `none`; `none` returns `state: null` and is intended for loops that rely on observations, metrics, and info instead of full snapshots.
|
|
237
291
|
- Track surface bands are single-source geometry. Rendering derives visible gravel, runoff, and barrier offsets from the same track widths used by wheel surfaces and ray surface hits: road edge, kerb outer edge, gravel outer edge, runoff outer edge, and barrier wall width. New code must not add independent magic offsets for these bands.
|
|
238
|
-
- Browser expert mode is opt-in with `expert: { enabled: true, controlledDrivers, frameSkip }`. When enabled, the returned controller exposes `expert.reset()`, `expert.step(actions)`, `expert.getObservation()`, and `expert.getState()`.
|
|
292
|
+
- Browser expert mode is opt-in with `expert: { enabled: true, controlledDrivers, frameSkip }`. It shares the environment episode lifecycle. 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 browser sessions should end by race finish instead of max-step truncation. Bounded training, evaluation, and smoke loops should pass an explicit smaller `episode.maxSteps`; `episode.endOnRaceFinish` controls whether race finish is terminal. When enabled, the returned controller exposes `expert.reset()`, `expert.step(actions)`, `expert.getObservation()`, and `expert.getState()`.
|
|
239
293
|
- Browser expert mode also exposes `attachExternalRenderer(source)`, `detachExternalRenderer()`, and `getExternalRendererState()` for render-only external-frame playback. The `source` callback contract is `subscribe(onFrame) => unsubscribe`, where each frame is `{ snapshot, observation, meta? }`.
|
|
240
294
|
- Browser expert mode wraps the same `RaceSimulation` instance that the visual canvas renders. It must not create a parallel simulation for the same mount.
|
|
241
295
|
- Browser expert mode disables automatic ticker-driven simulation advancement. The visual canvas updates only after explicit expert `reset()` or `step(actions)` calls.
|
|
@@ -249,8 +303,8 @@ Returned controller:
|
|
|
249
303
|
- Race canvas rendered with PixiJS.
|
|
250
304
|
- Procedural track rendering with asphalt texture and DRS overlays.
|
|
251
305
|
- Driver selection from cars and timing tower rows.
|
|
252
|
-
- Camera modes: overview, leader, selected, show all.
|
|
253
|
-
Overview
|
|
306
|
+
- Camera modes: overview, leader, selected, driver, show all, and pit.
|
|
307
|
+
Overview frames the active generated track bounds with package-owned padding and pit-lane extent; driver rotates around the selected car for user-driving views; show all dynamically fits the active pack; pit frames the operational pit-lane work area when the track has one.
|
|
254
308
|
- Zoom controls.
|
|
255
309
|
- FPS readout.
|
|
256
310
|
- Start lights.
|
|
@@ -264,6 +318,8 @@ Returned controller:
|
|
|
264
318
|
- Race-finish winner banner and final top-three classification.
|
|
265
319
|
- `Open project` button driven by `onDriverOpen(driver)`.
|
|
266
320
|
|
|
321
|
+
Full public race snapshots include `car.trackState` as the serialized car-center track classification. The stable fields are `distance`, `signedOffset`, `crossTrackError`, `surface`, `inPitLane`, `pitLanePart`, `pitBoxId`, `curvature`, and `heading`; pit-specific fields are `null` when not active. Runtime readouts normalize missing optional snapshot arrays and race-control groups before rendering, so partial startup or external-renderer frames do not break the package-owned UI.
|
|
322
|
+
|
|
267
323
|
## Runtime Requirements
|
|
268
324
|
|
|
269
325
|
- Browser environment with DOM APIs.
|
|
@@ -287,7 +343,7 @@ Expected:
|
|
|
287
343
|
- `npm pack --dry-run` succeeds and includes source files plus bundled assets.
|
|
288
344
|
- A packed tarball installs and builds inside a fresh temporary Vite consumer app.
|
|
289
345
|
- The tracked showcase host builds.
|
|
290
|
-
- The quick Chromium browser smoke verifies showcase canvas rendering, overflow constraints,
|
|
346
|
+
- The quick Chromium browser smoke verifies showcase canvas rendering, overflow constraints, one public API action, and live customization theme switching. The release browser smoke verifies the desktop/mobile matrix, package-panel overflow constraints, customization route interactions, public API buttons, and visual policy-runner stepping across generation and race configurations.
|
|
291
347
|
|
|
292
348
|
Run from a browser host that consumes the published package:
|
|
293
349
|
|