@inventure71/paddockjs 1.0.0 → 2.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 +52 -0
- package/INSTALL_AND_UPDATE.md +4 -3
- package/README.md +194 -12
- package/docs/architecture.md +119 -44
- package/docs/concepts.md +4 -2
- package/docs/custom_model_controller.md +329 -0
- package/docs/data_contract.md +299 -17
- package/docs/index.md +1 -0
- package/docs/learnings.md +22 -0
- package/docs/rules.md +37 -10
- package/docs/system_specs.md +23 -10
- package/docs/training.md +272 -30
- package/examples/python/README.md +62 -0
- package/examples/python/base_policy_server.py +191 -0
- package/examples/python/requirements.txt +2 -0
- package/package.json +12 -4
- package/src/api/PaddockSimulatorController.js +4 -0
- package/src/app/BrowserExpertAdapter.js +367 -9
- package/src/app/F1SimulatorApp.js +305 -1603
- package/src/app/banners/bannerFormatters.js +55 -0
- package/src/app/banners/raceDataBannerController.js +141 -0
- package/src/app/banners/stewardMessageController.js +99 -0
- package/src/app/camera/cameraConstants.js +21 -0
- package/src/app/camera/cameraController.js +265 -0
- package/src/app/camera/cameraGeometry.js +176 -0
- package/src/app/domBindings.js +4 -0
- package/src/app/readouts/carOverviewRenderer.js +109 -0
- package/src/app/readouts/controlStateRenderer.js +37 -0
- package/src/app/readouts/raceStatusRenderer.js +133 -0
- package/src/app/readouts/readoutFormatters.js +58 -0
- package/src/app/readouts/telemetryRenderer.js +113 -0
- package/src/app/readouts/timingTowerRenderer.js +177 -0
- package/src/app/rendering/appAssets.js +38 -0
- package/src/app/rendering/carRenderer.js +286 -0
- package/src/app/rendering/displayUtils.js +22 -0
- package/src/app/rendering/drsTrailRenderer.js +90 -0
- package/src/app/rendering/expertSensorRenderer.js +126 -0
- package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
- package/src/app/rendering/replayGhostRenderer.js +124 -0
- package/src/app/rendering/trackRenderer.js +25 -0
- package/src/app/runtime/frameLoop.js +66 -0
- package/src/app/runtime/runtimeTiming.js +34 -0
- package/src/app/runtime/runtimeVisibility.js +45 -0
- package/src/config/defaultOptions.js +20 -0
- package/src/environment/actions.js +13 -5
- package/src/environment/controllerLoop.js +246 -0
- package/src/environment/episode.js +86 -2
- package/src/environment/evaluation.js +54 -32
- package/src/environment/index.d.ts +361 -9
- package/src/environment/index.js +1 -0
- package/src/environment/metrics.js +159 -0
- package/src/environment/observationOptions.js +22 -1
- package/src/environment/observations.js +325 -40
- package/src/environment/options.js +37 -5
- package/src/environment/runtime.js +222 -21
- package/src/environment/scenarios.js +37 -8
- package/src/environment/sensors/bodySenses.js +23 -0
- package/src/environment/sensors/boundarySenses.js +17 -0
- package/src/environment/sensors/carRays.js +75 -0
- package/src/environment/sensors/contactSenses.js +32 -0
- package/src/environment/sensors/index.js +157 -0
- package/src/environment/sensors/indexedRayBands.js +103 -0
- package/src/environment/sensors/nearbyCars.js +52 -0
- package/src/environment/sensors/opponentRadar.js +65 -0
- package/src/environment/sensors/rayConfig.js +110 -0
- package/src/environment/sensors/rayDefaults.js +6 -0
- package/src/environment/sensors/rayGeometry.js +60 -0
- package/src/environment/sensors/rayGuards.js +19 -0
- package/src/environment/sensors/sensorTargets.js +60 -0
- package/src/environment/sensors/surfaceRays.js +296 -0
- package/src/environment/sensors/trackRays.js +261 -0
- package/src/environment/sensors.js +1 -367
- package/src/environment/specs.js +100 -5
- package/src/environment/workerProtocol.js +13 -1
- package/src/index.d.ts +169 -6
- package/src/index.js +5 -0
- package/src/rendering/proceduralTrackAsset.js +20 -823
- package/src/rendering/renderSnapshot.js +38 -12
- package/src/rendering/track/finishLineRenderer.js +34 -0
- package/src/rendering/track/gridRenderer.js +47 -0
- package/src/rendering/track/offsetStrokeSafety.js +136 -0
- package/src/rendering/track/pitLaneRenderer.js +248 -0
- package/src/rendering/track/trackMaterialRenderer.js +326 -0
- package/src/rendering/track/trackRenderConstants.js +38 -0
- package/src/rendering/track/trackRenderGeometry.js +94 -0
- package/src/simulation/collisionGeometry.js +1 -1
- package/src/simulation/driver/driverControlConstants.js +28 -0
- package/src/simulation/driver/driverController.js +47 -0
- package/src/simulation/driver/driverInput.js +28 -0
- package/src/simulation/driver/driverMath.js +6 -0
- package/src/simulation/driver/driverPersonality.js +25 -0
- package/src/simulation/driver/edgeRecovery.js +48 -0
- package/src/simulation/driver/racingControls.js +399 -0
- package/src/simulation/driver/racingLinePlan.js +166 -0
- package/src/simulation/driver/recoveryDynamics.js +38 -0
- package/src/simulation/driver/rejoinControls.js +213 -0
- package/src/simulation/driver/safetyCarControls.js +35 -0
- package/src/simulation/driver/trafficScan.js +51 -0
- package/src/simulation/driverController.js +4 -619
- package/src/simulation/participants/participantInteractions.js +120 -0
- package/src/simulation/pit/pitFlow.js +234 -0
- package/src/simulation/pit/pitIntent.js +92 -0
- package/src/simulation/pit/pitOccupancy.js +47 -0
- package/src/simulation/pit/pitPenaltyService.js +71 -0
- package/src/simulation/pit/pitQueue.js +55 -0
- package/src/simulation/pit/pitRouteMovement.js +160 -0
- package/src/simulation/pit/pitRouting.js +319 -0
- package/src/simulation/pit/pitService.js +41 -0
- package/src/simulation/pit/pitServiceConstants.js +21 -0
- package/src/simulation/pit/pitServiceProfile.js +45 -0
- package/src/simulation/pit/pitSnapshots.js +61 -0
- package/src/simulation/pit/pitState.js +113 -0
- package/src/simulation/pit/pitTireService.js +92 -0
- package/src/simulation/race/classification.js +104 -0
- package/src/simulation/race/gridPenalties.js +37 -0
- package/src/simulation/race/raceControlState.js +38 -0
- package/src/simulation/race/raceDistance.js +32 -0
- package/src/simulation/race/raceFinish.js +65 -0
- package/src/simulation/race/raceLifecycle.js +71 -0
- package/src/simulation/race/raceOrder.js +113 -0
- package/src/simulation/race/raceProgress.js +124 -0
- package/src/simulation/race/raceSetup.js +93 -0
- package/src/simulation/race/raceStep.js +80 -0
- package/src/simulation/race/redFlag.js +15 -0
- package/src/simulation/race/retirements.js +44 -0
- package/src/simulation/race/safetyCar.js +86 -0
- package/src/simulation/race/stalledDnf.js +86 -0
- package/src/simulation/raceSimulation.js +230 -2817
- package/src/simulation/replay/replayGhosts.js +128 -0
- package/src/simulation/rules/moduleConfig.js +72 -0
- package/src/simulation/rules/penaltyConfig.js +99 -0
- package/src/simulation/rules/penaltyEvents.js +18 -0
- package/src/simulation/rules/penaltyStats.js +26 -0
- package/src/simulation/rules/ruleConfigMerge.js +41 -0
- package/src/simulation/rules/ruleDefaults.js +85 -0
- package/src/simulation/rules/rulesReview.js +141 -0
- package/src/simulation/rules/rulesetPresets.js +47 -0
- package/src/simulation/rules/trackLimitsSteward.js +1 -1
- package/src/simulation/rulesConfig.js +5 -323
- package/src/simulation/snapshots/raceSnapshots.js +192 -0
- package/src/simulation/timing/drsTiming.js +87 -0
- package/src/simulation/timing/gapEstimation.js +38 -0
- package/src/simulation/timing/lapTelemetry.js +182 -0
- package/src/simulation/timing/raceTiming.js +15 -0
- package/src/simulation/timing/sectorPerformance.js +60 -0
- package/src/simulation/timing/timingConstants.js +5 -0
- package/src/simulation/timing/timingHistory.js +46 -0
- package/src/simulation/timing/timingLines.js +79 -0
- package/src/simulation/track/centerlineSpline.js +59 -0
- package/src/simulation/track/drsZones.js +67 -0
- package/src/simulation/track/pitLaneAccess.js +103 -0
- package/src/simulation/track/pitLaneLayout.js +395 -0
- package/src/simulation/track/pitLaneState.js +160 -0
- package/src/simulation/track/proceduralCenterline.js +237 -0
- package/src/simulation/track/sampleGeometry.js +49 -0
- package/src/simulation/track/spatialQueries.js +154 -0
- package/src/simulation/track/startStraight.js +141 -0
- package/src/simulation/track/trackConstants.js +109 -0
- package/src/simulation/track/trackGenerationOptions.js +283 -0
- package/src/simulation/track/trackMath.js +241 -0
- package/src/simulation/track/trackModel.js +240 -0
- package/src/simulation/track/trackQueryIndex.js +945 -0
- package/src/simulation/track/trackSectors.js +19 -0
- package/src/simulation/track/trackStatePolicy.js +24 -0
- package/src/simulation/track/trackValidation.js +98 -0
- package/src/simulation/trackModel.js +1 -1706
- package/src/simulation/vehicle/contactResolution.js +116 -0
- package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
- package/src/simulation/vehicle/pitWheelSurface.js +69 -0
- package/src/simulation/vehicle/runoffResponse.js +88 -0
- package/src/simulation/vehicle/surfacePriority.js +28 -0
- package/src/simulation/vehicle/vehicleGeometry.js +247 -0
- package/src/simulation/vehicle/vehiclePhysics.js +363 -0
- package/src/simulation/vehicle/vehicleSnapshots.js +316 -0
- package/src/simulation/vehicle/vehicleState.js +186 -0
- package/src/simulation/vehicle/wheelSurface.js +150 -0
- package/src/simulation/vehicleGeometry.js +1 -247
- package/src/simulation/vehiclePhysics.js +1 -136
- package/src/simulation/warmup/runtimeWarmup.js +224 -0
- package/src/simulation/wheelSurface.js +1 -338
- package/src/styles.css +42 -0
- package/src/ui/cameraControlsTemplate.js +20 -0
- package/src/ui/carOverviewTemplate.js +33 -0
- package/src/ui/componentTemplates.js +14 -360
- package/src/ui/raceCanvasTemplate.js +69 -0
- package/src/ui/raceControlsTemplate.js +36 -0
- package/src/ui/shellTemplate.js +5 -4
- package/src/ui/telemetryDrawerTemplate.js +55 -0
- package/src/ui/telemetryTemplates.js +136 -0
- package/src/ui/timingTowerTemplate.js +34 -0
package/docs/data_contract.md
CHANGED
|
@@ -14,8 +14,13 @@ mountF1Simulator(root, {
|
|
|
14
14
|
onDriverOpen,
|
|
15
15
|
seed,
|
|
16
16
|
trackSeed,
|
|
17
|
+
trackQueryIndex,
|
|
18
|
+
warmup,
|
|
17
19
|
totalLaps,
|
|
20
|
+
physicsMode,
|
|
18
21
|
rules,
|
|
22
|
+
participantInteractions,
|
|
23
|
+
replayGhosts,
|
|
19
24
|
initialCameraMode,
|
|
20
25
|
theme,
|
|
21
26
|
title,
|
|
@@ -45,8 +50,13 @@ const simulator = createPaddockSimulator({
|
|
|
45
50
|
onDriverOpen,
|
|
46
51
|
seed,
|
|
47
52
|
trackSeed,
|
|
53
|
+
trackQueryIndex,
|
|
54
|
+
warmup,
|
|
48
55
|
totalLaps,
|
|
56
|
+
physicsMode,
|
|
49
57
|
rules,
|
|
58
|
+
participantInteractions,
|
|
59
|
+
replayGhosts,
|
|
50
60
|
initialCameraMode,
|
|
51
61
|
preset,
|
|
52
62
|
theme,
|
|
@@ -121,6 +131,9 @@ result = env.step({
|
|
|
121
131
|
```
|
|
122
132
|
|
|
123
133
|
`controlledDrivers` is required. It supports one or many externally controlled cars. Non-controlled participants use the built-in driver AI in the stable 1.0 environment API.
|
|
134
|
+
`externalRenderer` is optional and observer-only. It can be a function or `{ onFrame(frame) }`, and receives `{ snapshot, observation, meta }` on `reset`, `step`, and `resetDrivers`. The hook does not mutate simulation state and hook failures are isolated so stepping continues.
|
|
135
|
+
`warmup` is optional and enabled by default. It primes a disposable runtime during load/reset creation and caches by configuration fingerprint so repeated identical resets skip warmup. Use `warmup: { enabled, policy: 'config-change' | 'always' | 'never', steps }` or `warmup: false` to disable.
|
|
136
|
+
`physicsMode` is optional and accepts `'arcade'` or `'simulator'`. Invalid values fall back to `'arcade'`, which is the default compatibility mode. `'simulator'` keeps the same public action contract but uses stricter 2D velocity/yaw dynamics and exposes additional telemetry fields on snapshots and observations: `lateralG`, `longitudinalG`, `gripUsage`, `slipAngleRadians`, `tractionLimited`, and `stabilityState`. In simulator mode, `slipAngleRadians` is derived from the car heading versus actual velocity direction, and mixed wheel surfaces are averaged for physics while wheel snapshots still report each contact patch.
|
|
124
137
|
`rules` is an optional override object for the race rules documented in [rules.md](rules.md). Flat keys such as `standingStart: false` still work for existing behavior. Advanced systems live under `rules.modules` so hosts can choose a preset and then override individual modules:
|
|
125
138
|
|
|
126
139
|
```js
|
|
@@ -135,6 +148,14 @@ rules: {
|
|
|
135
148
|
tirePitRequestThresholdPercent: 50,
|
|
136
149
|
tirePitCommitThresholdPercent: 30,
|
|
137
150
|
},
|
|
151
|
+
tireDegradation: {
|
|
152
|
+
enabled: false,
|
|
153
|
+
},
|
|
154
|
+
stalledDnf: {
|
|
155
|
+
enabled: true,
|
|
156
|
+
maxStoppedSeconds: 12,
|
|
157
|
+
speedThresholdKph: 5,
|
|
158
|
+
},
|
|
138
159
|
penalties: {
|
|
139
160
|
trackLimits: { strictness: 0.8 },
|
|
140
161
|
collision: { strictness: 0.5, consequences: [{ type: 'time', seconds: 5 }] },
|
|
@@ -145,7 +166,7 @@ rules: {
|
|
|
145
166
|
}
|
|
146
167
|
```
|
|
147
168
|
|
|
148
|
-
Supported rulesets are `paddock`, `grandPrix2025`, `fia2025`, and `custom`. The `fia2025` name is a 2024-2025-era grand-prix-style package preset; explicit module config always wins over preset defaults. Penalty strictness is clamped from `0` to `1`, where `0` disables enforcement for that subsection and `1` uses the configured rule margin. `rules` is not a direct state-mutation API.
|
|
169
|
+
Supported rulesets are `paddock`, `grandPrix2025`, `fia2025`, and `custom`. The `fia2025` name is a 2024-2025-era grand-prix-style package preset; explicit module config always wins over preset defaults. `rules.modules.tireDegradation.enabled: false` freezes tyre energy for deterministic training or visual comparison without changing tire compounds or pit rules. `rules.modules.stalledDnf` defaults to `{ enabled: false, maxStoppedSeconds: 12, speedThresholdKph: 5 }`; set `enabled: true` to retire stuck off-track cars with `dnfReason: 'stalled-off-track'`. Pre-start, red flag, legal pit surfaces, active pit handling, and already-finished cars are excluded. Penalty strictness is clamped from `0` to `1`, where `0` disables enforcement for that subsection and `1` uses the configured rule margin. `rules` is not a direct state-mutation API.
|
|
149
170
|
|
|
150
171
|
Scenario support:
|
|
151
172
|
|
|
@@ -175,7 +196,65 @@ scenario: {
|
|
|
175
196
|
}
|
|
176
197
|
```
|
|
177
198
|
|
|
178
|
-
Scenario placement is applied only during environment creation/reset through the simulator state API. It does not give policies a state-mutation path during `step(actions)`. Explicit `placements` override preset placement for the same driver, and `traffic` places another participant relative to a placed or existing car.
|
|
199
|
+
Scenario placement is applied only during environment creation/reset through the simulator state API. It does not give policies a state-mutation path during `step(actions)`. Explicit `placements` override preset placement for the same driver, and `traffic` places another participant relative to a placed or existing car. `env.reset(partialOptions)` preserves omitted nested option groups, but an explicitly supplied `scenario.placements` object replaces the old placement map so callers can clear stale reset placements with `{ scenario: { placements: {} } }`. Static obstacles, debug mutation, assisted controls, and Python Gymnasium wrappers are intentionally deferred. Replay ghosts and participant interaction profiles are supported separately below. The supported package boundary today is JavaScript Gym-style control plus a JSON worker protocol, not a Python Gym package and not a scenario editor.
|
|
200
|
+
|
|
201
|
+
`env.resetDrivers(placements)` applies the same placement shape to selected controlled drivers without recreating the whole environment. It is an episode-boundary API for batched training: selected drivers get a new `episodeId`, `episodeStep: 0`, cleared manual controls, and cleared pit intent. Non-selected controlled drivers keep their current car state and episode counters. Max-step truncation is evaluated from each reported driver's own `episodeStep`, so resetting one truncated driver clears that driver's done state without rewinding the environment `info.step` or changing other drivers' episode state. Reset placement is still validated against the normal track/runoff/barrier model before observations are returned. If a reset placement puts a car into the barrier destruction boundary, the returned result reports that driver as `destroyed` with `endReason: 'destroyed'` and stable miss-valued rays instead of doing an expensive far-out sensor scan. This API is not accepted inside `step(actions)`.
|
|
202
|
+
|
|
203
|
+
Participant interaction profiles are simulator-owned environment setup, not RL logic. They let hosts run multiple real cars in one environment without forcing every car to collide with or appear in every other car's sensors:
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
participantInteractions: {
|
|
207
|
+
defaultProfile: 'normal',
|
|
208
|
+
drivers: {
|
|
209
|
+
'model-a': { profile: 'normal' },
|
|
210
|
+
'model-b': { profile: 'isolated-training' },
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Supported profiles are:
|
|
216
|
+
|
|
217
|
+
- `normal`: collidable, ray-detectable, nearby-detectable, pit-lane-blocking, and race-order-affecting.
|
|
218
|
+
- `isolated-training`: non-colliding, hidden from rays and nearby-car observations, non-blocking in pit-lane occupancy, but still included in race order.
|
|
219
|
+
- `batch-training`: non-colliding, hidden from rays and nearby-car observations, non-blocking in pit-lane occupancy, and excluded from race order/classification while still being a rendered physics-driven car in `snapshot.cars`.
|
|
220
|
+
- `phantom-race`: non-colliding and non-blocking in pit-lane occupancy, but visible to rays and nearby-car observations and included in race order.
|
|
221
|
+
- `time-trial-overlay`: non-colliding, sensor-hidden, non-blocking, and excluded from race order/classification while still being a physics-driven car in `snapshot.cars`.
|
|
222
|
+
|
|
223
|
+
Every profile flag can be overridden per driver. This does not add teleport, assisted steering, or trainer behavior: cars still advance through the normal vehicle physics and public action/state APIs.
|
|
224
|
+
|
|
225
|
+
For multi-car learner batches, `batch-training` is the default no-collision profile. It hides each learner from other cars' ray sensors and `nearbyCars` observations, excludes learner cars from race order, and still keeps them visible as normal physics cars in snapshots/rendering. Use `isolated-training` when a non-colliding car should still affect order. If a non-colliding car should remain sensor-visible, use `phantom-race` or explicitly override `detectableByRays` / `detectableAsNearby`.
|
|
226
|
+
|
|
227
|
+
Browser render snapshots include each car's resolved `interaction` object. The default browser renderer uses that to draw a blue outline marker around non-colliding physics participants while keeping them solid and clickable. That visual marker does not change collision, sensor, timing, pit, or control behavior.
|
|
228
|
+
|
|
229
|
+
Replay ghosts are separate trajectory-driven reference entities:
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
replayGhosts: [
|
|
233
|
+
{
|
|
234
|
+
id: 'best-lap',
|
|
235
|
+
label: 'Best Lap',
|
|
236
|
+
color: '#00ff84',
|
|
237
|
+
opacity: 0.35,
|
|
238
|
+
visible: true,
|
|
239
|
+
trajectory: [
|
|
240
|
+
{
|
|
241
|
+
timeSeconds: 0,
|
|
242
|
+
x: 100,
|
|
243
|
+
y: 200,
|
|
244
|
+
headingRadians: 1.2,
|
|
245
|
+
speedKph: 180,
|
|
246
|
+
progressMeters: 0,
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
sensors: {
|
|
250
|
+
detectableByRays: false,
|
|
251
|
+
detectableAsNearby: false,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
]
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Replay ghosts appear in `snapshot.replayGhosts`, interpolate by `timeSeconds`, render as translucent browser overlays, and never enter `snapshot.cars`, timing rows, race order, pit logic, collision resolution, or steward penalties. They are hidden from rays and nearby-car observations by default. If `sensors.detectableByRays` or `sensors.detectableAsNearby` is explicitly enabled, observations may report that replay ghost as a sensor target with `targetType` / `entityType: 'replayGhost'`; it still remains a non-physics reference entity.
|
|
179
258
|
|
|
180
259
|
Actions use normalized low-level controls:
|
|
181
260
|
|
|
@@ -189,13 +268,69 @@ Actions use normalized low-level controls:
|
|
|
189
268
|
}
|
|
190
269
|
```
|
|
191
270
|
|
|
192
|
-
|
|
271
|
+
Browser expert mode also exposes an external render hook for authoritative node-frame visualization:
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
simulator.expert.attachExternalRenderer(source);
|
|
275
|
+
simulator.expert.detachExternalRenderer();
|
|
276
|
+
const state = simulator.expert.getExternalRendererState();
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`source` contract:
|
|
280
|
+
|
|
281
|
+
```js
|
|
282
|
+
{
|
|
283
|
+
subscribe(onFrame) {
|
|
284
|
+
// call onFrame({ snapshot, observation, meta? })
|
|
285
|
+
return () => { /* unsubscribe */ };
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
While attached, expert runtime is strict render-only and rejects local `step()`, `resetDrivers()`, and `reset()`. Detaching restores the current local simulation track surface before hosts resume local expert stepping, even if the last external frame used a different track. Package core stays transport-agnostic: WebSocket connection/discovery belongs to host integration code, not to `src/`.
|
|
291
|
+
|
|
292
|
+
Operationally this is one architecture with two modes:
|
|
293
|
+
|
|
294
|
+
- local expert mode: browser owns stepping
|
|
295
|
+
- external render mode: browser only renders authoritative external frames
|
|
296
|
+
|
|
297
|
+
Policy Runner maps this directly through supported controller modes: `Distilled policy` and `Policy server` for browser-owned stepping, and `Live preview stream` for external render-only frames.
|
|
298
|
+
|
|
299
|
+
`steering` is an absolute steering-wheel target: `-1` points at the maximum left steering limit, `0` points at center, `1` points at the maximum right steering limit, and intermediate values are percentages of that limit. The vehicle physics moves the steering angle toward that target through the configured steering-rate limit, so centering is physical rather than an instantaneous snap. `throttle` and `brake` are clamped from `0` to `1`. `steering`, `throttle`, and `brake` are required on every controlled-driver action; missing or non-finite values fail action validation instead of defaulting to zero.
|
|
193
300
|
|
|
194
301
|
Actions may also include `pitIntent`. `0` clears a pending pit request and is accepted as a no-op even when pit stops are disabled, so fixed-shape policies can always send the full action object. `1` keeps trying until the next free-enough pit-entry window, and `2` commits to entering at the next pit-entry window even when pit-lane capacity or gap checks would block an opportunistic stop. Expert-controlled drivers start with `pitIntent: 0`, and tire-threshold automatic pit calls are disabled for those drivers so models do not manually steer into pit-lane geometry or get surprise pit calls from the built-in strategy. If pit stops are disabled, if the car has no pit assignment, or if the car is already entering, queued, servicing, or exiting, the environment rejects non-zero pit requests through the configured `actionPolicy`.
|
|
195
302
|
|
|
196
303
|
Pit actions may also include `pitCompound` or `pitTargetCompound`, for example `{ pitIntent: 2, pitCompound: 'H' }`. The value must be one of `rules.modules.tireStrategy.compounds`. If omitted, the simulator keeps the existing pending target or picks the first configured compound different from the current tire. The model never steers down pit-lane geometry directly; once a request is accepted, the simulator owns pit entry, queueing, penalty hold, tire service, and pit exit.
|
|
197
304
|
|
|
198
|
-
The recommended
|
|
305
|
+
The recommended runtime convention is a user-owned driver controller with `decideBatch(context) -> { [driverId]: { steering, throttle, brake, pitIntent? } }`. Controllers may load any model format or call any inference backend. PaddockJS only provides the simulator runtime context, stable controlled-driver ordering, cached specs, compact observations, and normalized action validation.
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
import { createPaddockDriverControllerLoop } from '@inventure71/paddockjs';
|
|
309
|
+
|
|
310
|
+
const controller = {
|
|
311
|
+
async init(ctx) {
|
|
312
|
+
this.model = await loadUserModel(ctx.observationSpec);
|
|
313
|
+
},
|
|
314
|
+
async reset(ctx) {
|
|
315
|
+
this.hidden = resetHiddenState(ctx.controlledDrivers.length);
|
|
316
|
+
},
|
|
317
|
+
async decideBatch(ctx) {
|
|
318
|
+
const vectors = ctx.orderedObservations.map((item) => item.vector);
|
|
319
|
+
return this.model.predictBatch(vectors, this.hidden);
|
|
320
|
+
},
|
|
321
|
+
onStep(ctx) {
|
|
322
|
+
recordUserOwnedStats(ctx.metrics, ctx.actions);
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const loop = createPaddockDriverControllerLoop({
|
|
327
|
+
runtime: env,
|
|
328
|
+
controller,
|
|
329
|
+
actionRepeat: 4,
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
The older one-car `policy.predict(driverObservation)` style remains easy to wrap inside a controller, but new browser playback and training-style loops should use `decideBatch()` so one inference call can cover every controlled car.
|
|
199
334
|
|
|
200
335
|
The environment exposes specs for external training code:
|
|
201
336
|
|
|
@@ -204,7 +339,51 @@ const actionSpec = env.getActionSpec();
|
|
|
204
339
|
const observationSpec = env.getObservationSpec();
|
|
205
340
|
```
|
|
206
341
|
|
|
207
|
-
`actionSpec` describes controlled drivers, normalized action ranges, and the optional pit intent values. `observationSpec` describes object observation fields, ray layout, nearby-car limits, track lookahead fields, and the versioned vector schema. `observation.lookaheadMeters` is sanitized to a finite numeric array; invalid or empty values fall back to the default `[20, 50, 100, 150]`.
|
|
342
|
+
`actionSpec` describes controlled drivers, normalized action ranges, and the optional pit intent values. `observationSpec` describes object observation fields, ray layout, nearby-car limits, track lookahead fields, and the versioned vector schema. `observation.lookaheadMeters` is sanitized to a finite numeric array; invalid or empty values fall back to the default `[20, 50, 100, 150]`. The opt-in `observation.profile: 'physical-driver'` profile defaults lookahead to `[]` so policies can use local driver-like senses without receiving privileged future track curvature. Realistic training runs should pair this profile with `physicsMode: 'simulator'`; that keeps the model's observed yaw, grip, contact-patch, kerb, and runoff behavior aligned with the physics it is learning to control.
|
|
343
|
+
|
|
344
|
+
Observation output can be compacted for training throughput:
|
|
345
|
+
|
|
346
|
+
```js
|
|
347
|
+
observation: {
|
|
348
|
+
profile: 'physical-driver',
|
|
349
|
+
output: 'vector', // 'full' | 'vector' | 'object'
|
|
350
|
+
includeSchema: false,
|
|
351
|
+
vectorType: 'float32', // 'array' | 'float32'
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The default remains `output: 'full'`, `includeSchema: true`, and `vectorType: 'array'`, which returns `{ object, vector, schema, events }` for backward compatibility. `output: 'vector'` returns `{ vector, events }` unless schema inclusion is requested. `output: 'object'` returns `{ object, events }` unless schema inclusion is requested. `getObservationSpec()` remains the canonical schema source for compact loops. `vectorType: 'float32'` returns a `Float32Array` for JavaScript consumers that want typed numeric buffers; JSON worker users should keep the default array output unless their bridge explicitly handles typed arrays. Destroyed or out-of-race cars keep the same ray/vector schema and return miss-valued ray channels so model code does not need a separate terminal tensor shape.
|
|
356
|
+
|
|
357
|
+
Result state output can also be compacted:
|
|
358
|
+
|
|
359
|
+
```js
|
|
360
|
+
result: {
|
|
361
|
+
stateOutput: 'none', // 'full' | 'minimal' | 'none'
|
|
362
|
+
resetDriversObservationScope: 'reset', // 'all' | 'reset'
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
The default `stateOutput: 'full'` preserves the public `{ state: { snapshot } }` payload. `minimal` returns the lean observation snapshot used by the environment. `none` returns `state: null`, so external loops should rely on `observation`, `metrics`, and `info` instead. `resetDriversObservationScope: 'reset'` makes `resetDrivers()` return observations and metrics only for reset drivers by default; callers can override per call with `env.resetDrivers(placements, { observationScope: 'all' | 'reset', stateOutput })`. Partial `env.reset(options)` calls merge plain nested option groups such as `rules.modules`, `trackGeneration`, `sensors`, `observation`, `result`, and `episode`; arrays and explicit placement maps replace the old value.
|
|
367
|
+
|
|
368
|
+
The JSON worker protocol exposes the same reset-result controls through `resultOptions`:
|
|
369
|
+
|
|
370
|
+
```js
|
|
371
|
+
protocol.handle({
|
|
372
|
+
id: 'reset-agent-3',
|
|
373
|
+
type: 'resetDrivers',
|
|
374
|
+
placements: {
|
|
375
|
+
'agent-3': { distanceMeters: 1200, offsetMeters: 0, speedKph: 80 },
|
|
376
|
+
},
|
|
377
|
+
resultOptions: {
|
|
378
|
+
observationScope: 'reset',
|
|
379
|
+
stateOutput: 'none',
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
`getState` accepts `stateOptions: { output: 'full' | 'minimal' | 'none' }`. For compatibility with reset-style messages, `resultOptions.stateOutput` is also mapped to the same state output selector on `getState` messages.
|
|
385
|
+
|
|
386
|
+
This is still an environment-control message, not an action. A policy cannot submit `resetDrivers` inside `step(actions)`.
|
|
208
387
|
|
|
209
388
|
`createProgressReward()` is non-canonical demo reward code for examples and quick smoke tests, not the official reward. It returns a callback compatible with `reward(context)` and combines:
|
|
210
389
|
|
|
@@ -229,15 +408,18 @@ const reward = createProgressReward({
|
|
|
229
408
|
});
|
|
230
409
|
```
|
|
231
410
|
|
|
232
|
-
Custom reward functions receive the same context:
|
|
411
|
+
Custom reward functions receive the same context plus neutral environment facts:
|
|
233
412
|
|
|
234
413
|
```js
|
|
235
|
-
reward({ driverId, previous, current, action, events, state }) {
|
|
236
|
-
|
|
414
|
+
reward({ driverId, previous, current, action, events, state, metrics, episode }) {
|
|
415
|
+
if (metrics.destroyed) return -200;
|
|
416
|
+
if (metrics.offTrack) return -12;
|
|
417
|
+
if (episode.terminated) return 0;
|
|
418
|
+
return metrics.legalProgressDeltaMeters;
|
|
237
419
|
}
|
|
238
420
|
```
|
|
239
421
|
|
|
240
|
-
If no reward callback is provided, `result.reward` is `null`. PaddockJS does not infer, select, or tune rewards.
|
|
422
|
+
`metrics` is the same neutral per-driver metrics object returned in `result.metrics[driverId]`, and `episode` is the same per-driver runtime state returned in `result.info.drivers[driverId]`. These are package-owned facts, not reward policy. If no reward callback is provided, `result.reward` is `null`. If a callback returns `undefined`, `null`, `NaN`, or an infinite value, that driver's reward is normalized to `0`. PaddockJS does not infer, select, or tune rewards.
|
|
241
423
|
|
|
242
424
|
Neutral rollout recording is available for external training loops:
|
|
243
425
|
|
|
@@ -266,7 +448,7 @@ const report = runEnvironmentEvaluation({
|
|
|
266
448
|
});
|
|
267
449
|
```
|
|
268
450
|
|
|
269
|
-
Evaluation reports include distance, lap progress, off-track step count, contact count, recovery success, pass count, and first lap time when available. These metrics are not rewards.
|
|
451
|
+
Evaluation reports include distance, lap progress, off-track step count, contact count, recovery success, pass count, and first lap time when available. They follow the same wheel-level legal-surface and contact-event definitions as environment metrics. `runEnvironmentEvaluation()` can accept compact base options with `result.stateOutput: 'none'`; it internally requests the minimal snapshot needed for evaluation bookkeeping. Direct `createEvaluationTracker(result)` usage requires `state.snapshot`, so use `stateOutput: 'minimal'` or `full` when wiring the tracker manually. These metrics are not rewards.
|
|
270
452
|
|
|
271
453
|
`step(actions)` returns a JavaScript object designed for environment loops:
|
|
272
454
|
|
|
@@ -300,6 +482,16 @@ Evaluation reports include distance, lap progress, off-track step count, contact
|
|
|
300
482
|
pitStopPenaltyServiceRemainingSeconds,
|
|
301
483
|
pitStopsCompleted,
|
|
302
484
|
},
|
|
485
|
+
trackRelation: {
|
|
486
|
+
lateralOffsetMeters,
|
|
487
|
+
headingErrorRadians,
|
|
488
|
+
legalWidthMeters,
|
|
489
|
+
leftBoundaryMeters,
|
|
490
|
+
rightBoundaryMeters,
|
|
491
|
+
onLegalSurface,
|
|
492
|
+
surface,
|
|
493
|
+
},
|
|
494
|
+
contactPatches,
|
|
303
495
|
race: {
|
|
304
496
|
position,
|
|
305
497
|
totalCars,
|
|
@@ -316,6 +508,23 @@ Evaluation reports include distance, lap progress, off-track step count, contact
|
|
|
316
508
|
},
|
|
317
509
|
},
|
|
318
510
|
reward,
|
|
511
|
+
metrics: {
|
|
512
|
+
budget: {
|
|
513
|
+
progressDeltaMeters,
|
|
514
|
+
legalProgressDeltaMeters,
|
|
515
|
+
offTrack,
|
|
516
|
+
kerb,
|
|
517
|
+
fullyOutsideWhiteLine,
|
|
518
|
+
severeCut,
|
|
519
|
+
destroyed,
|
|
520
|
+
destroyReason,
|
|
521
|
+
under30kph,
|
|
522
|
+
spinOrBackwards,
|
|
523
|
+
completedLap,
|
|
524
|
+
lapTimeSeconds,
|
|
525
|
+
contactCount,
|
|
526
|
+
},
|
|
527
|
+
},
|
|
319
528
|
terminated,
|
|
320
529
|
truncated,
|
|
321
530
|
done,
|
|
@@ -331,11 +540,29 @@ Evaluation reports include distance, lap progress, off-track step count, contact
|
|
|
331
540
|
controlledDrivers,
|
|
332
541
|
actionErrors,
|
|
333
542
|
endReason,
|
|
543
|
+
drivers: {
|
|
544
|
+
budget: {
|
|
545
|
+
terminated,
|
|
546
|
+
truncated,
|
|
547
|
+
endReason,
|
|
548
|
+
episodeStep,
|
|
549
|
+
episodeId,
|
|
550
|
+
},
|
|
551
|
+
},
|
|
334
552
|
},
|
|
335
553
|
}
|
|
336
554
|
```
|
|
337
555
|
|
|
338
|
-
|
|
556
|
+
`state.snapshot.cars` contains physics-driven participants only. Each car includes `interaction` with the resolved profile flags. `state.snapshot.replayGhosts` contains trajectory-driven replay/reference entities only. This invariant is shared by browser and headless snapshots:
|
|
557
|
+
|
|
558
|
+
```txt
|
|
559
|
+
snapshot.cars = physics-driven race participants
|
|
560
|
+
snapshot.replayGhosts = trajectory-driven replay/reference entities
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Observation objects use physical units such as kph, meters/second, meters, and radians. Optional `vector` values use fixed documented scaling from `schema`; they do not use hidden per-car normalization. Full simulator truth remains available under `state.snapshot`. Internally, the environment avoids rebuilding full snapshots during each `frameSkip` substep and defaults to the non-public track query index unless `trackQueryIndex: false` is set, but the returned `state.snapshot`, reward callback `previous` snapshot, and reward callback `state.snapshot` keep the same public shape.
|
|
564
|
+
|
|
565
|
+
The environment observation now exposes local physical driver senses separately from full snapshot truth. `self.yawRateRadiansPerSecond` is the car's current yaw rate. `self.appliedControls` reports the normalized controls that actually drove the latest physics step: `steering` in `-1..1`, `steeringRadians` in simulator radians, `throttle` in `0..1`, and `brake` in `0..1`. For controlled cars this mirrors the accepted environment action; in `actionPolicy: 'report'` mode, a missing or invalid vehicle action releases stale manual controls, lets the built-in AI drive that step, and exposes its exact controls for auditing or clean local imitation datasets. `trackRelation` gives immediate local road relationship: lateral offset, heading error, legal width, left/right boundary distance, legal-surface state, and current surface. `contactPatches` exposes the four wheel/contact-patch surface readings as stable public observation data, with `surfaceCode` intended only as a compact vector encoding.
|
|
339
566
|
|
|
340
567
|
Default rays use a compact center-origin set with forward, side, and rear awareness:
|
|
341
568
|
|
|
@@ -350,11 +577,18 @@ Track distances are sampled against the actual track model. When the ray starts
|
|
|
350
577
|
angleDegrees,
|
|
351
578
|
angleRadians,
|
|
352
579
|
lengthMeters,
|
|
580
|
+
roadEdge: {
|
|
581
|
+
hit,
|
|
582
|
+
distanceMeters,
|
|
583
|
+
kind, // 'exit' | 'entry' | null
|
|
584
|
+
},
|
|
353
585
|
track: {
|
|
354
586
|
hit,
|
|
355
587
|
distanceMeters,
|
|
356
588
|
kind, // 'exit' | 'entry' | null
|
|
357
589
|
},
|
|
590
|
+
kerb: { hit, distanceMeters, surface },
|
|
591
|
+
illegalSurface: { hit, distanceMeters, surface },
|
|
358
592
|
car: {
|
|
359
593
|
hit,
|
|
360
594
|
distanceMeters,
|
|
@@ -364,6 +598,26 @@ Track distances are sampled against the actual track model. When the ray starts
|
|
|
364
598
|
}
|
|
365
599
|
```
|
|
366
600
|
|
|
601
|
+
`track` is kept as the compatibility alias for `roadEdge`. New ray configs may use a per-ray layout and opt into additional channels only when needed:
|
|
602
|
+
|
|
603
|
+
```js
|
|
604
|
+
sensors: {
|
|
605
|
+
rays: {
|
|
606
|
+
layout: 'driver-front-heavy',
|
|
607
|
+
channels: ['roadEdge', 'kerb', 'illegalSurface', 'car'],
|
|
608
|
+
precision: 'driver', // 'driver' | 'debug'
|
|
609
|
+
rays: [
|
|
610
|
+
{ id: 'front', angleDegrees: 0, lengthMeters: 260 },
|
|
611
|
+
{ id: 'right', angleDegrees: 90, lengthMeters: 80 },
|
|
612
|
+
],
|
|
613
|
+
},
|
|
614
|
+
}
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
Ray precision defaults to `driver`. Driver precision is the active model-facing sensor contract and uses the normal sampled ray step without extra refinement. `precision: 'debug'` is available for clearly labeled diagnostics with additional edge refinement, but debug precision must not be displayed as model senses unless the policy is also running with that exact sensor config.
|
|
618
|
+
|
|
619
|
+
Surface channels are computed only when requested. `kerb` is legal racing surface. `illegalSurface` reports the first grass or gravel surface crossed by the ray. The same model-facing ray contract is accelerated for both normal on-track driving and nearby off-track recovery starts using indexed ray-boundary intersections. Ambiguous pit-connector and unusual geometry cases may use indexed sampled fallback internally, but the returned ray object shape is unchanged. Barrier walls are not exposed as a model-facing ray target; active ray objects, vectors, schemas, and visualizations must not expose a `barrier` ray channel. Barrier contact is enforced as a shared destruction boundary in both physics modes at the rendered wall's inner face and reported through events, snapshots, metrics, and per-driver episode state.
|
|
620
|
+
|
|
367
621
|
Nearby-car observations are car-relative:
|
|
368
622
|
|
|
369
623
|
```js
|
|
@@ -375,11 +629,16 @@ Nearby-car observations are car-relative:
|
|
|
375
629
|
relativeSpeedKph,
|
|
376
630
|
relativeHeadingRadians,
|
|
377
631
|
ahead,
|
|
632
|
+
behind,
|
|
378
633
|
sameLap,
|
|
634
|
+
closingRateMetersPerSecond,
|
|
635
|
+
timeToContactSeconds,
|
|
636
|
+
leftOverlap,
|
|
637
|
+
rightOverlap,
|
|
379
638
|
}
|
|
380
639
|
```
|
|
381
640
|
|
|
382
|
-
If no `reward` callback is provided, `result.reward` is `null`. If provided, rewards are returned by controlled driver ID.
|
|
641
|
+
If no `reward` callback is provided, `result.reward` is `null`. If provided, rewards are returned by controlled driver ID. The callback receives `driverId`, `previous`, `current`, `action`, `events`, `state.snapshot`, `metrics`, and `episode`.
|
|
383
642
|
|
|
384
643
|
Browser expert mode is opt-in through the browser mount API:
|
|
385
644
|
|
|
@@ -405,7 +664,7 @@ simulator.expert.step({
|
|
|
405
664
|
|
|
406
665
|
When browser expert mode is enabled, automatic ticker simulation advancement is disabled. The visual canvas advances only when host code calls `simulator.expert.reset()` or `simulator.expert.step(actions)`. Browser expert mode is a mount-time option; `restart(nextOptions)` rejects `expert` changes. Destroy and mount a new simulator to switch between built-in ticker control and expert stepping.
|
|
407
666
|
|
|
408
|
-
`expert.visualizeSensors` is a browser-only visual debugging option. When `visualizeSensors: true` or `visualizeSensors: { rays: true }` is set, the simulator draws the controlled
|
|
667
|
+
`expert.visualizeSensors` is a browser-only visual debugging option. When `visualizeSensors: true` or `visualizeSensors: { rays: true }` is set, the simulator draws the selected controlled driver's ray sensors in the Pixi world layer from the car's current position. Ray lengths and hit markers are based on the same observation data returned from the expert environment result. Every detected ray channel gets its own colored marker so road-edge, kerb, illegal surface, and car hits can be debugged independently. Barrier walls are rendered as track geometry, not model-facing ray hits. In multi-driver expert runs, use `visualizeSensors: { rays: true, drivers: 'all' }` only when every controlled car's overlay is intentionally needed; hosts may also pass `drivers: ['driver-a']` to pin the overlay to a fixed controlled subset.
|
|
409
668
|
|
|
410
669
|
## Lap Telemetry Snapshot
|
|
411
670
|
|
|
@@ -442,7 +701,25 @@ Each `car.lapTelemetry` snapshot includes current/last/best lap and sector timin
|
|
|
442
701
|
|
|
443
702
|
`totalLaps` is optional. Values are normalized to a finite positive integer, with invalid or non-positive input falling back to a one-lap race.
|
|
444
703
|
|
|
445
|
-
`trackSeed` is optional. If omitted, each mounted browser simulator creates a fresh procedural circuit. Passing a `trackSeed` makes the track deterministic; repeated procedural seeds are cached so multiple mounts can reuse the same generated track definition
|
|
704
|
+
`trackSeed` is optional. If omitted, each mounted browser simulator creates a fresh procedural circuit. Passing a `trackSeed` makes the track deterministic; repeated procedural seeds are cached so multiple mounts can reuse the same immutable generated track definition when the resolved generation options also match. Callers should treat `createProceduralTrack(seed, options)` results as read-only. Calling `restart({ trackSeed })` rebuilds the simulation on the deterministic circuit for that seed.
|
|
705
|
+
|
|
706
|
+
`trackGeneration` is optional and applies only when a procedural `trackSeed` is used. It accepts profile presets plus explicit overrides:
|
|
707
|
+
|
|
708
|
+
```js
|
|
709
|
+
{
|
|
710
|
+
profile: 'training-short',
|
|
711
|
+
length: { minMeters: 900, maxMeters: 1800 },
|
|
712
|
+
startStraight: { gridMeters: 0, exitMeters: 80, blendMeters: 80 },
|
|
713
|
+
pitLane: { enabled: false },
|
|
714
|
+
shape: { scale: 0.2, cornerDensity: 1.3, variation: 0.22 },
|
|
715
|
+
validation: { minClearanceMultiplier: 1, maxLocalTurnRadians: 1.85 },
|
|
716
|
+
attempts: { primary: 80, fallback: 200 },
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
Profiles are presets, not separate generators. `race` preserves the default full-length circuit with pit lane; `training-short`, `training-medium`, and `training-technical` generate smaller pitless circuits for training or demos. Resolution is `race` defaults, selected profile defaults, then explicit overrides. Procedural tracks are generated by tracing, smoothing, and warping package-owned seeded region masks, then validating the resulting centerline instead of falling back to pure ovals. Invalid candidates are rejected for length, world bounds, non-adjacent clearance, turn sharpness, self-intersections, and weak shape variation. The start/finish area is normalized into an explicit straight window so the starting grid, pit-entry approach, and immediate pit-exit merge area are straight even when the rest of the circuit is generated from curved controls.
|
|
721
|
+
|
|
722
|
+
`trackQueryIndex` controls indexed track/surface query acceleration. Browser/expert simulator mounts, headless environments, and direct race-simulation construction default this option to `true`, so visual race simulation, expert controls, Policy Runner playback, headless training/evaluation runs, and model-sense visualization use the indexed path by default. Set `trackQueryIndex: false` to force legacy non-indexed queries for comparison or debugging. It does not change public snapshot or observation shapes.
|
|
446
723
|
|
|
447
724
|
`initialCameraMode` is optional and accepts `'overview'`, `'leader'`, `'selected'`, `'show-all'`, or `'pit'`. Invalid values fall back to `'leader'`. The `overview` camera frames the generated track bounds with package-owned padding and pit-lane extent. The `pit` camera frames the active track's `pitLane` geometry, zooms out when needed to keep the full pit lane inside the active race-view safe area, and falls back to `leader` when no pit lane is available. Camera zoom controls and wheel zoom apply to every mode, with zoom-out bounded by the active track frame.
|
|
448
725
|
|
|
@@ -577,7 +854,7 @@ Optional lifecycle callbacks:
|
|
|
577
854
|
}
|
|
578
855
|
```
|
|
579
856
|
|
|
580
|
-
`onRaceEvent` receives simulation events such as `contact`, `penalty`, `track-limits`, `pit-lane-speeding`, `safety-car`, `green-flag`, `start-lights-out`, and `race-finish`. `contact` events include metadata from the production body collision solver: `firstShapeId`, `secondShapeId`, `contactType`, `depth`, and `timeOfImpact`. Host callback errors are caught; if `onError` exists, it receives `{ callback: name }` context for callback failures.
|
|
857
|
+
`onRaceEvent` receives simulation events such as `contact`, `penalty`, `track-limits`, `pit-lane-speeding`, `car-dnf`, `safety-car`, `green-flag`, `start-lights-out`, and `race-finish`. `car-dnf` currently reports stalled off-track retirements with `reason: 'stalled-off-track'`. `contact` events include metadata from the production body collision solver: `firstShapeId`, `secondShapeId`, `contactType`, `depth`, and `timeOfImpact`. Host callback errors are caught; if `onError` exists, it receives `{ callback: name }` context for callback failures.
|
|
581
858
|
|
|
582
859
|
Race snapshots include a top-level `penalties` array. Each penalty entry includes `id`, `type`, `driverId`, `strictness`, `status`, `penaltySeconds`, `pendingPenaltySeconds`, `serviceType`, `serviceRequired`, `serviceServedAt`, `appliedAt`, `cancelledAt`, `unserved`, `positionDrop`, `gridDrop`, `disqualified`, `consequences`, `lap`, `at`, and rule-specific context such as `otherCarId`, `aheadDriverId`, `atFaultDriverId`, `sharedFault`, and `impactSpeedKph` for collision penalties or `speedKph`, `speedLimitKph`, `excessKph`, and `pitLanePart` for pit-lane speeding penalties. Clear rear contact has one at-fault driver; unclear meaningful contact records one shared-fault penalty per involved driver. Multiple time penalties for the same driver are summed into the car snapshot's `penaltySeconds` and adjusted finish/classification time.
|
|
583
860
|
|
|
@@ -646,6 +923,9 @@ ui: {
|
|
|
646
923
|
raceDataBannerSize: 'custom',
|
|
647
924
|
raceDataTelemetryDetail: false,
|
|
648
925
|
timingTowerVerticalFit: 'expand-race-view',
|
|
926
|
+
},
|
|
927
|
+
debug: {
|
|
928
|
+
physicsModeIndicator: false,
|
|
649
929
|
}
|
|
650
930
|
```
|
|
651
931
|
|
|
@@ -654,6 +934,7 @@ ui: {
|
|
|
654
934
|
- `showFps`: controls whether the race canvas renders the FPS readout.
|
|
655
935
|
- `showRaceDataPanel`: controls whether the precombined shell includes the project/radio lower-third inside the race window.
|
|
656
936
|
- `showTimingTower`, `showTelemetry`: reserved component visibility flags for host layout decisions.
|
|
937
|
+
- `debug.physicsModeIndicator`: when `true`, renders a small top-left square in the race canvas. Blue means `physicsMode: 'arcade'`; red means `physicsMode: 'simulator'`. It defaults to `false` and is intended only for debug/development use.
|
|
657
938
|
- `telemetryIncludesOverview`: controls whether the telemetry stack template embeds the car/driver overview. Composable hosts can also pass `mountTelemetryPanel(root, { includeOverview: false })`.
|
|
658
939
|
- `telemetryModules`: controls optional telemetry surfaces inside stack/drawer templates. The default object enables `core` scalar readouts, `sectors` progress bars, `lapTimes`, and `sectorTimes`. It can also be `false` to disable all telemetry modules, or an array such as `['sectors', 'lapTimes']` to render only named modules. These modules are also individually mountable with `mountTelemetryCore`, `mountTelemetrySectors`, `mountTelemetryLapTimes`, and `mountTelemetrySectorTimes`.
|
|
659
940
|
- `raceDataBanners.initial`: `'project'`, `'radio'`, or `'hidden'`. This controls which lower-third appears first in the precombined shell.
|
|
@@ -701,13 +982,13 @@ After every car completes `totalLaps`, `getSnapshot()` returns:
|
|
|
701
982
|
finishedAt: 123.4,
|
|
702
983
|
winner: { id, code, name, rank, finished },
|
|
703
984
|
classification: [
|
|
704
|
-
{ id, code, timingCode, name, rank, lap, lapsCompleted, distanceMeters, gapMeters, gapSeconds, intervalSeconds, gapLaps, intervalLaps, finished, finishTime, penaltySeconds, adjustedFinishTime, positionDrop, disqualified },
|
|
985
|
+
{ id, code, timingCode, name, rank, lap, lapsCompleted, distanceMeters, gapMeters, gapSeconds, intervalSeconds, gapLaps, intervalLaps, finished, finishTime, dnf, dnfReason, dnfAt, dnfOrder, penaltySeconds, adjustedFinishTime, positionDrop, disqualified },
|
|
705
986
|
],
|
|
706
987
|
},
|
|
707
988
|
}
|
|
708
989
|
```
|
|
709
990
|
|
|
710
|
-
Cars also include `team`, `speedKph`, `distanceMeters`, `gapAheadMeters`, `gapAheadSeconds`, `intervalAheadSeconds`, `leaderGapSeconds`, `gapAheadLaps`, `intervalAheadLaps`, `leaderGapLaps`, `finished`, `finishTime`, `finishRank`, `status`, `raceStatus`, `wavedFlag`, `penaltySeconds`, `adjustedFinishTime`, `classifiedRank`, and `lapTelemetry`. A car that has crossed the finish before the whole race is complete exposes `status: 'waved-flag'`, `raceStatus: 'waved-flag'`, and `wavedFlag: true`; the timing order keeps those cars frozen by provisional `finishRank` until final classification is available. Classification entries also expose `gapLaps`, `intervalLaps`, `positionDrop`,
|
|
991
|
+
Cars also include `team`, `speedKph`, `distanceMeters`, `gapAheadMeters`, `gapAheadSeconds`, `intervalAheadSeconds`, `leaderGapSeconds`, `gapAheadLaps`, `intervalAheadLaps`, `leaderGapLaps`, `finished`, `finishTime`, `finishRank`, `status`, `raceStatus`, `wavedFlag`, `destroyed`, `destroyReason`, `destroyedAt`, `dnf`, `dnfReason`, `dnfAt`, `dnfOrder`, `penaltySeconds`, `adjustedFinishTime`, `classifiedRank`, and `lapTelemetry`. A car that has crossed the finish before the whole race is complete exposes `status: 'waved-flag'`, `raceStatus: 'waved-flag'`, and `wavedFlag: true`; the timing order keeps those cars frozen by provisional `finishRank` until final classification is available, and later barrier contact or stalled-off-track detection does not convert that result into DNF. A destroyed/out-of-race car exposes `dnf: true`, keeps compatibility `raceStatus: 'destroyed'`, shows `DNF` in the timing tower, renders faded/gray in the race canvas, and is ordered below running cars by `dnfOrder`. Stalled off-track DNFs use `dnfReason: 'stalled-off-track'` and `destroyed: false`; barrier DNFs keep `destroyed: true`. Classification entries also expose `gapLaps`, `intervalLaps`, `positionDrop`, `disqualified`, and DNF metadata. `gapAheadSeconds` and `intervalAheadSeconds` are the interval to the car directly ahead when both cars are on the same lead-lap cycle; `gapAheadLaps` and `intervalAheadLaps` are the whole-lap deficit to that car when it is at least one lap ahead. `leaderGapSeconds` is the same-lead-lap gap to P1. `leaderGapLaps` is the whole-lap deficit to P1. Seconds gaps are measured from the timestamp difference when both cars crossed the same hidden timing line, with fallback interpolation only when no shared timing line exists yet. The timing tower prefers lap labels such as `+1` over seconds when the relevant lap gap is positive, shows `WAVED` for a finished car before full race classification, and shows `DNF` for retired cars. The first car to finish sets a provisional `raceControl.winner` and receives a `car-finish` event, but the race keeps running until every race participant has finished or is DNF. A DNF car restored before final classification re-enters live timing and must finish; after final classification, resurrection does not reopen the race. Final `raceControl.classification` sorts finishers by `finishTime + penaltySeconds` after converting unserved drive-through and stop-go penalties, applies position-drop and disqualification consequences, then appends DNF entries after finishers.
|
|
711
992
|
|
|
712
993
|
When `rules.modules.pitStops.enabled` is true, cars also expose `pitIntent`, `pitStop`, and `usedTireCompounds`. `pitIntent` and `pitStop.intent` use `0` for no request, `1` for an opportunistic request that stays active until a free-enough entry window appears, and `2` for a committed request that enters at the next pit-entry window even if pit capacity or gap checks would block mode `1`. `pitStop.status` is one of `pending`, `entering`, `queued`, `servicing`, `exiting`, or `completed`; `pitStop.phase` may be `entry`, `queue`, `queue-release`, `penalty`, `service`, `exit`, or `null`. It also includes the assigned shared service `boxId`/`boxIndex`, the driver's `garageBoxId`/`garageBoxIndex`, optional team id/color, planned pit-call race distance, physical pit-entry race distance, service seconds remaining, `penaltyServiceRemainingSeconds`, `penaltyServiceTotalSeconds`, `servingPenaltyIds`, target tire, optional `serviceProfile`, queue flag, and completed stop count. Host APIs and expert actions can choose the target tire compound with `setPitIntent(driverId, intent, targetCompound)` or `pitCompound`; invalid compounds are rejected. Automatic pit calls can share the same entry lap, but a pending mode `1` car only joins when the active pit-lane population is below `maxConcurrentPitLaneCars` and the nearest active pit-lane car is at least `minimumPitLaneGapMeters` ahead; mode `2` bypasses that opportunistic-capacity gate and commits to the automatic pit-entry route. Team-mates share one service area; every car enters through the working-lane queue point first. If the service area is free, the queue point behaves as a rolling gate and the car immediately follows a short `queue-release` route into service without exposing `queued`; if the area is occupied, the car exposes `status: queued` until servicing, queue-release, and just-exiting cars have physically cleared the active spot. Completed stops can be re-armed by later tire condition or host intent, so `stopsCompleted` can increase beyond one during longer races. `pitLaneSpeedLimitKph` applies only while the automatic route is inside the straight main pit lane/working lane; entry and exit connector roads remain legal pit surfaces but are not limiter zones. The automatic route brakes before the main lane start, travels along the main fast lane, and crosses into the team service area only near the assigned stop. If `phase` is `penalty`, the car is stationary before tire service and the browser renderer shows a red `+Ns` countdown above the car; if `phase` is `service`, it shows a yellow `Ns` countdown for the normal pit-service time. Optional pit-stop variability records the resolved `serviceProfile`; `pitStops.variability.perfect: true` forces the configured default service time for deterministic training. `usedTireCompounds` starts with the initial tire and receives the tire selected by completed automatic pit stops.
|
|
713
994
|
|
|
@@ -810,6 +1091,7 @@ Controller methods:
|
|
|
810
1091
|
- `setPitIntent(driverId, intent, targetCompound?)`: requests, clears, or updates a pending automatic pit stop and optional target tire.
|
|
811
1092
|
- `getPitIntent(driverId)`: reads the current pit intent.
|
|
812
1093
|
- `getPitTargetCompound(driverId)`: reads the current pit target tire.
|
|
1094
|
+
- `getSimulationSpeed()`: returns the active browser playback multiplier from the package-owned simulation-speed control, defaulting to `1`.
|
|
813
1095
|
- `servePenalty(penaltyId)`: marks an issued drive-through or stop-go penalty as served.
|
|
814
1096
|
- `cancelPenalty(penaltyId)`: cancels a penalty so it no longer affects service, timing, grid, or classification.
|
|
815
1097
|
- `getSnapshot()`: returns the latest simulation snapshot.
|
package/docs/index.md
CHANGED
|
@@ -9,6 +9,7 @@ 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
|
+
- [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.
|
|
12
13
|
- [Architecture](architecture.md): module ownership and data/control flow.
|
|
13
14
|
- [Data Model Notes](data_model.md): lower-level simulator data model notes kept with package docs.
|
|
14
15
|
- [Learnings](learnings.md): implementation lessons and layout verification rules learned from simulator integration issues.
|
package/docs/learnings.md
CHANGED
|
@@ -45,3 +45,25 @@ For visual layout changes, verify more than the happy path screenshot:
|
|
|
45
45
|
- Browser smoke against an installed/bundled host, not only string-based unit tests.
|
|
46
46
|
|
|
47
47
|
Unit tests should cover the contract, but browser geometry checks catch CSS interactions that tests can miss.
|
|
48
|
+
|
|
49
|
+
## Indexed Track Queries Must Reach Every Runtime Path
|
|
50
|
+
|
|
51
|
+
The track query index is internal performance infrastructure, not a separate sensor contract. When a runtime opts into it, every simulation instance used by that runtime must receive the same option:
|
|
52
|
+
|
|
53
|
+
- Headless environments created by `createPaddockEnvironment()`.
|
|
54
|
+
- Browser expert mode through `F1SimulatorApp.createRaceSimulation()`.
|
|
55
|
+
- Policy Runner controller-debug configurations.
|
|
56
|
+
- Local preview remount/restart paths.
|
|
57
|
+
- Any benchmark or smoke path used to validate training behavior.
|
|
58
|
+
|
|
59
|
+
Do not assume that setting `trackQueryIndex: true` on a high-level preview or environment config is enough. Trace the option all the way into `createRaceSimulation()` and verify that the live simulation owns a non-enumerable `track.queryIndex`. 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
|
+
|
|
61
|
+
The failure mode to avoid: headless training uses indexed queries and looks fast, while the browser visual/expert simulation silently drops the option and falls back to legacy `nearestSampleInRange()` scans 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
|
+
|
|
63
|
+
Verification for index-related changes should include both layers:
|
|
64
|
+
|
|
65
|
+
- A focused unit test proving the app or environment path forwards `trackQueryIndex` into the created simulation.
|
|
66
|
+
- A browser reproduction or smoke check against the Policy Runner or relevant visual runtime, especially near barrier/recovery geometry where legacy nearest-sample scans are most expensive.
|
|
67
|
+
- A profile or timing check when the bug is performance-related. If `nearestSampleInRange()` is still hot in an indexed browser path, the option did not reach the live simulation or another code path is bypassing the index.
|
|
68
|
+
|
|
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.
|