@inventure71/paddockjs 2.0.0 → 2.0.1
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 +6 -0
- package/INSTALL_AND_UPDATE.md +1 -1
- package/README.md +30 -5
- package/docs/architecture.md +15 -9
- package/docs/concepts.md +6 -4
- package/docs/data_contract.md +28 -15
- package/docs/data_model.md +1 -1
- package/docs/index.md +9 -0
- package/docs/rules.md +37 -7
- package/docs/sense_contract.md +59 -0
- package/docs/system_specs.md +56 -7
- package/docs/training.md +16 -10
- package/package.json +1 -1
- package/src/api/PaddockSimulatorController.js +2 -33
- package/src/app/BrowserExpertAdapter.js +9 -300
- package/src/app/BrowserExpertExternalRenderer.js +303 -0
- package/src/app/F1SimulatorApp.js +1 -42
- package/src/config/restartOptions.js +34 -0
- package/src/environment/controllerLoop.js +17 -2
- package/src/environment/episode.js +14 -2
- package/src/environment/index.d.ts +16 -2
- package/src/environment/observationVector.js +200 -0
- package/src/environment/observations.js +58 -285
- package/src/environment/recorder.js +13 -6
- package/src/environment/rewards.js +13 -2
- package/src/environment/runtime.js +62 -14
- 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 +90 -26
- package/src/environment/sensors/trackRays.js +185 -22
- package/src/environment/specs.js +78 -40
- package/src/index.d.ts +2 -0
- package/src/index.js +1 -32
- 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/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/raceStep.js +1 -1
- 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/track/trackQueryGrid.js +94 -0
- package/src/simulation/track/trackQueryIndex.js +16 -302
- 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/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 +2 -1
- package/src/simulation/vehicle/vehicleSnapshots.js +11 -0
- package/src/simulation/vehicle/vehicleState.js +36 -2
- package/src/simulation/vehicle/wheelSurface.js +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Harden the simulator training environment and model-facing sense contract. This patch keeps vector-only observations aligned with full object observations, keeps indexed ray acceleration equivalent to the legacy ray contract for driver-facing senses, preserves stable terminal-car and pit-route state during training loops, and documents the stricter model-sense boundary.
|
|
8
|
+
|
|
3
9
|
## 2.0.0
|
|
4
10
|
|
|
5
11
|
### Major Changes
|
package/INSTALL_AND_UPDATE.md
CHANGED
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install @inventure71/paddockjs
|
|
|
12
12
|
|
|
13
13
|
## Documentation
|
|
14
14
|
|
|
15
|
-
Start with [docs/index.md](docs/index.md) for
|
|
15
|
+
Start with [docs/index.md](docs/index.md) for the full package map: what to install, which API to use, what data to pass, how rules modules work, how the browser and headless runtimes differ, and which verification commands prove a change. If you are integrating the package into a host site, read [System Specs](docs/system_specs.md), [Data Contract](docs/data_contract.md), and [Rules](docs/rules.md) first. If you already trained a driver model and want to run it in PaddockJS, use the [Custom Model Controller Guide](docs/custom_model_controller.md).
|
|
16
16
|
|
|
17
17
|
## Package Workflow
|
|
18
18
|
|
|
@@ -40,9 +40,9 @@ npm run changeset
|
|
|
40
40
|
|
|
41
41
|
Local development and showcase builds require Node `20.19.0` or newer. CI currently runs the package check on Node 22 and releases on Node 24.
|
|
42
42
|
|
|
43
|
-
## 2.0
|
|
43
|
+
## 2.0 Release Highlights
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
The `2.0.x` release line adds the stricter opt-in simulator physics mode, richer procedural track generation, indexed track/ray queries, barrier destruction and stalled-DNF behavior, batch-training participant profiles, replay ghosts, compact/vectorized environment output, policy-server/live-preview Policy Runner paths, Python policy-server examples, the custom model controller guide, the Rules showcase page, and the full release-gated local preview coverage.
|
|
46
46
|
|
|
47
47
|
The package still does not ship trained model weights, model storage, a Python Gymnasium/PettingZoo package, static obstacles, weather, reliability failures, fuel-load effects, or debug mutation APIs. Those remain host-owned or future scope.
|
|
48
48
|
|
|
@@ -239,6 +239,8 @@ When multiple drivers are controlled, sensor visualization renders the selected
|
|
|
239
239
|
|
|
240
240
|
## API
|
|
241
241
|
|
|
242
|
+
The root package exports the browser mounts, composable helper mounts, data/rating helpers, bundled asset and preset constants, procedural-track helper, simulator unit converters, and `createPaddockDriverControllerLoop`. The browser-free training runtime lives under `@inventure71/paddockjs/environment`; see [docs/system_specs.md](docs/system_specs.md) for the full export catalog.
|
|
243
|
+
|
|
242
244
|
All-in-one mount:
|
|
243
245
|
|
|
244
246
|
```js
|
|
@@ -324,6 +326,23 @@ simulator.mountRaceTelemetryDrawer(document.getElementById('sim-race-workbench')
|
|
|
324
326
|
});
|
|
325
327
|
```
|
|
326
328
|
|
|
329
|
+
When a composable host intends to use pit APIs or automatic pit behavior, include the pit rules in the simulator options before mounting components:
|
|
330
|
+
|
|
331
|
+
```js
|
|
332
|
+
const simulator = createPaddockSimulator({
|
|
333
|
+
drivers,
|
|
334
|
+
entries,
|
|
335
|
+
rules: {
|
|
336
|
+
ruleset: 'custom',
|
|
337
|
+
modules: {
|
|
338
|
+
pitStops: { enabled: true },
|
|
339
|
+
stalledDnf: { enabled: true },
|
|
340
|
+
tireStrategy: { enabled: true, mandatoryDistinctDryCompounds: 2 },
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
327
346
|
`drivers` is the host-owned project/pilot list. `entries` is the optional driver/car/team pairing sheet. The car/driver overview uses the existing driver and vehicle rating components from each entry. Driver and vehicle entries can also include `customFields` as extra label/value metadata. Entries can include `team: { id, name, color, icon, pitCrew }`; the timing tower uses the team icon and defaults team color to the car color when omitted. `team.pitCrew` accepts `speed`, `consistency`, and `reliability` values from `0` to `1` for optional pit-service variability. Assets, including the default car image and generic driver helmet, are bundled by default, so the host website does not need to provide simulator images or textures.
|
|
328
347
|
|
|
329
348
|
The returned object supports:
|
|
@@ -439,6 +458,8 @@ Pit-lane asphalt, working-lane service areas, and garage boxes are legal drivabl
|
|
|
439
458
|
|
|
440
459
|
When `rules.modules.pitStops.enabled` is true, cars automatically form bounded pit trains when lane space is available. They brake to the limiter by the main pit-lane start, drive along the main fast lane, pass through the team queue spot as a rolling gate, roll into the team-colored working-lane service area when it is clear, stop, serve eligible penalties before tire work, show the remaining stationary service time above the car, change to the requested configured tire compound or the default alternate compound, and exit back to the race track.
|
|
441
460
|
|
|
461
|
+
`tireStrategy` does not enable automatic pit stops. It controls available compounds, pit-stop target compound choices, and tire-requirement stewarding. Use `rules.modules.pitStops.enabled: true` for automatic pit routing and pit APIs, or choose a preset such as `ruleset: 'fia2025'` / `ruleset: 'grandPrix2025'` that enables both pit stops and the two-compound tire strategy by default.
|
|
462
|
+
|
|
442
463
|
Team-mates share one service area. Every car enters through the queue spot first, but it only stops there when the active service spot is blocked. A second team car waits in the queue spot until the active service spot is physically clear, including the previous car's first movement out of the box, without blocking the main fast lane.
|
|
443
464
|
|
|
444
465
|
By default, built-in AI cars request an opportunistic `pitIntent: 1` below 50% tire energy and commit `pitIntent: 2` below 30%. Hosts can change those thresholds with `tirePitRequestThresholdPercent` and `tirePitCommitThresholdPercent`. Expert/headless controlled drivers do not receive those automatic tire-threshold calls and must request service with `pitIntent`.
|
|
@@ -473,6 +494,10 @@ const simulator = await mountF1Simulator(root, {
|
|
|
473
494
|
perfect: false,
|
|
474
495
|
},
|
|
475
496
|
},
|
|
497
|
+
tireStrategy: {
|
|
498
|
+
enabled: true,
|
|
499
|
+
mandatoryDistinctDryCompounds: 2,
|
|
500
|
+
},
|
|
476
501
|
tireDegradation: {
|
|
477
502
|
enabled: true,
|
|
478
503
|
},
|
|
@@ -498,7 +523,7 @@ const simulator = await mountF1Simulator(root, {
|
|
|
498
523
|
|
|
499
524
|
For deterministic single-skill training or visual checkpoint comparison, set `rules.modules.tireDegradation.enabled: false` so tyre energy remains fixed while the car still drives through normal steering, throttle, brake, and surface physics. `rules.modules.stalledDnf` defaults to `enabled: false` so base simulator and training environments keep stuck off-track cars live unless a host opts in. Set `enabled: true` to retire cars that are off legal racing or pit surfaces while below `speedThresholdKph` for `maxStoppedSeconds`.
|
|
500
525
|
|
|
501
|
-
Supported rulesets are `paddock`, `grandPrix2025`, `fia2025`, and `custom`. Presets only choose defaults; explicit module config wins. Weather, reliability, and fuel-load performance effects are reserved future modules and are not active 2.0.
|
|
526
|
+
Supported rulesets are `paddock`, `grandPrix2025`, `fia2025`, and `custom`. Presets only choose defaults; explicit module config wins. Weather, reliability, and fuel-load performance effects are reserved future modules and are not active `2.0.x` behavior. Penalty subsections use `strictness` from `0` to `1` instead of plain booleans. Track limits use the white line as the legal edge and require all four wheel contact patches to be fully outside the same side of the line before recording a violation, so normal kerb riding is not punished. Per-car `surface` is resolved from the worst wheel surface, snapshots include `car.wheels` for per-wheel surface and white-line state, and asymmetric left/right wheel resistance adds a small capped yaw tug toward the slower side when only one side is on a worse surface. Collision stewarding is driven by a body collision hull, not transparent sprite bounds or wheel-only overlap, and contact events include shape ids, contact type, depth, and time of impact. It considers impact severity, closing speed, and whether one car clearly hit another from behind; clear rear contact penalizes only the physically trailing car, including lapped traffic cases, while unclear meaningful contact records shared-fault penalties for both cars. Stalled off-track DNFs emit `car-dnf` with `reason: 'stalled-off-track'`, freeze the car, remove it from active control/collision/sensor/pit participation, and classify it with the existing DNF metadata. Pit-lane speeding is enforced on the main fast lane, working lane, service areas, and garage boxes, but not on pit-entry or pit-exit connector roads. Track-limit warnings are emitted as `track-limits` events, while penalty decisions are exposed through `snapshot.penalties` plus `penalty` events. Penalty consequences support warning, time, drive-through, stop-go, position-drop, grid-drop, and disqualification payloads. Time consequences are additive, drive-through and stop-go penalties are service obligations, and unserved service penalties convert to configured time at final classification.
|
|
502
527
|
|
|
503
528
|
`initialCameraMode` accepts `'overview'`, `'leader'`, `'selected'`, `'show-all'`, or `'pit'`; invalid values fall back to `'leader'`. The overview camera frames the active generated track bounds, including package-owned track padding and pit-lane extent, instead of using a fixed world-center zoom. Camera mode changes ease from the current camera target to the next target after the initial frame, so switching between leader, selected, overview, show-all, and pit views does not snap the world view.
|
|
504
529
|
|
|
@@ -553,7 +578,7 @@ Mounted package surfaces include a lightweight red start-light loading overlay.
|
|
|
553
578
|
|
|
554
579
|
The runtime also pauses its render ticker when the race canvas is offscreen or the browser tab is hidden. This keeps pages with multiple PaddockJS embeds responsive without requiring host code to manually start and stop each simulator. At `5x` and `10x` browser playback, noncritical timing/readout DOM refreshes run at a lower cadence while fixed-step simulation and race events keep using authoritative race state.
|
|
555
580
|
|
|
556
|
-
Lifecycle callbacks are optional and host-owned. PaddockJS emits `onLoadingChange`, `onReady`, `onError`, `onDriverSelect`, `onRaceEvent`, `onLapChange`, and `onRaceFinish`; callback errors are routed to `onError` when provided and do not stop the simulator loop. Race snapshots include per-car interval timing, leader-gap timing, whole-lap gap counts, calibrated `speedKph`, automatic `track.sectors`, hidden `track.timingLines`, per-car `lapTelemetry`, finish state, `raceControl.winner` after the first finisher, and final `raceControl.classification` only after the whole field is finished or DNF. Sector telemetry clears future-sector values so banners and sidebars show completed splits before the active sector plus the active live timer, not stale later-sector entries. Cars that have crossed the line before full race completion expose `raceStatus: 'waved-flag'` / `wavedFlag: true` and stay frozen in provisional finish order; later barrier contact or stalled-off-track detection does not turn that result into DNF. Destroyed/out-of-race cars expose DNF metadata, show `DNF` at the bottom of the timing tower, render faded/gray in the race canvas, and appear after finishers in final classification with no finish time. If a DNF car is restored before final classification, it re-enters live timing and the race waits for it again; once `raceControl.finished` is true, classification stays final. Final classification applies time penalties and unserved service conversions through `adjustedFinishTime`, then applies position-drop and disqualification consequences to finishers before DNF entries. The field then circulates in safety-car mode and the race canvas shows a package-owned winner banner.
|
|
581
|
+
Lifecycle callbacks are optional and host-owned. PaddockJS emits `onLoadingChange`, `onReady`, `onError`, `onDriverSelect`, `onRaceEvent`, `onLapChange`, and `onRaceFinish`; lifecycle callback errors are routed to `onError` when provided and do not stop the simulator loop. `onDriverOpen(driver)` is host-owned navigation code and is not wrapped as a lifecycle callback. Race snapshots include per-car interval timing, leader-gap timing, whole-lap gap counts, calibrated `speedKph`, automatic `track.sectors`, hidden `track.timingLines`, per-car `lapTelemetry`, finish state, `raceControl.winner` after the first finisher, and final `raceControl.classification` only after the whole field is finished or DNF. Sector telemetry clears future-sector values so banners and sidebars show completed splits before the active sector plus the active live timer, not stale later-sector entries. Cars that have crossed the line before full race completion expose `raceStatus: 'waved-flag'` / `wavedFlag: true` and stay frozen in provisional finish order; later barrier contact or stalled-off-track detection does not turn that result into DNF. Destroyed/out-of-race cars expose DNF metadata, show `DNF` at the bottom of the timing tower, render faded/gray in the race canvas, and appear after finishers in final classification with no finish time. If a DNF car is restored before final classification, it re-enters live timing and the race waits for it again; once `raceControl.finished` is true, classification stays final. Final classification applies time penalties and unserved service conversions through `adjustedFinishTime`, then applies position-drop and disqualification consequences to finishers before DNF entries. The field then circulates in safety-car mode and the race canvas shows a package-owned winner banner.
|
|
557
582
|
|
|
558
583
|
## License
|
|
559
584
|
|
package/docs/architecture.md
CHANGED
|
@@ -103,15 +103,15 @@ Responsibilities:
|
|
|
103
103
|
`src/environment/` owns the browser-free expert environment:
|
|
104
104
|
|
|
105
105
|
- `index.js` and `index.d.ts` expose the public subpath API.
|
|
106
|
-
- `runtime.js` owns environment-loop `reset()`, `step(actions)`, `resetDrivers(placements)`, `getObservation()`, and `getState()` around an injected race-simulation host. It also owns partial reset option merging, preserving omitted nested plain-object groups while replacing arrays and explicit scenario placement maps. It owns per-driver episode bookkeeping for batched environments and formats result state as `full`, `minimal`, or `none` without changing simulator stepping. `resetDrivers()` reapplies normal runoff/barrier classification to the selected cars after placement and before result construction, clears only selected driver episode terminal state, and does not rewind global `info.step`.
|
|
106
|
+
- `runtime.js` owns environment-loop `reset()`, `step(actions)`, `resetDrivers(placements)`, `getObservation()`, and `getState()` around an injected race-simulation host. It also owns partial reset option merging, preserving omitted nested plain-object groups while replacing arrays and explicit scenario placement maps. It owns per-driver episode bookkeeping for batched environments and formats result state as `full`, `minimal`, or `none` without changing simulator stepping. `resetDrivers()` reapplies normal runoff/barrier classification to the selected cars after placement and before result construction, clears only selected driver episode terminal state, and does not rewind global `info.step`. Reset-only observation scope affects the returned observation and metrics maps, while full `info.drivers` remains scoped to each controlled driver's own episode state. Reward callbacks are transition-only and run from `step(actions)`, not reset/read result construction.
|
|
107
107
|
- `race/stalledDnf.js` owns the optional stalled off-track retirement rule. It consumes current wheel/track surface state after vehicle physics, ignores pre-start/red-flag/finished/pit-controlled states, tracks per-car stopped time below the configured speed threshold, and converts eligible stuck cars into the existing DNF path with `reason: 'stalled-off-track'`. Other modules should consume the resulting `outOfRace`/`dnf` state instead of reimplementing stalled detection.
|
|
108
108
|
- `options.js` validates `controlledDrivers`, scenario participants, reset placement config, frame skip, action policy, sensor config, and episode limits.
|
|
109
109
|
- `actions.js` maps normalized public controls onto simulator steering/throttle/brake controls plus pit intent and optional target compound requests. It treats steering, throttle, and brake as required vehicle-control fields so broken model output is reported instead of silently becoming zeroed controls.
|
|
110
110
|
- `scenarios.js` owns environment reset placement presets, absolute placements, and relative traffic layouts. It applies them through simulator state APIs during environment creation/reset only; it must not create policy-time teleport or assisted-control paths.
|
|
111
|
-
- `observations.js`, `sensors.js`, and `events.js` build sensor-style observations, versioned fixed-schema vectors, track lookahead/curvature, ray/nearby-car readings, and global/per-driver events. Observation output formatting is separate from object/vector construction so compact training loops can request vector-only results while `getObservationSpec()` remains the schema source. Vector-only mode uses a direct vector path and can return `Float32Array` buffers without building the full public object observation. When a loop requests vector-only, schema-free, no-state output, `runtime.js` uses the internal `snapshotTraining()` path instead of the heavier public observation snapshot;
|
|
111
|
+
- `observations.js`, `observationVector.js`, `sensors.js`, and `events.js` build sensor-style observations, versioned fixed-schema vectors, track lookahead/curvature, ray/nearby-car readings, and global/per-driver events. `observations.js` owns object observation assembly and direct compact-source preparation, while `observationVector.js` is the single owner of model-facing vector order, schema entries, and typed-array formatting for both object-backed and direct vector paths. Observation output formatting is separate from object/vector construction so compact training loops can request vector-only results while `getObservationSpec()` remains the schema source. Vector-only mode uses a direct vector path and can return `Float32Array` buffers without building the full public object observation. When a loop requests vector-only, schema-free, no-state output, `runtime.js` uses the internal `snapshotTraining()` path instead of the heavier public observation snapshot; returned state still uses public snapshot shapes. Environment-owned simulations default to the internal track query index unless `trackQueryIndex: false` is set, because repeated track/surface/ray fallback queries otherwise dominate step cost. `sensors.js` is a compatibility barrel; nearby-car scans, ray defaults, ray config normalization, track rays, surface rays, indexed ray-band intersections, car rays, body senses, contact-patch senses, boundary senses, opponent radar, and ray geometry live under `src/environment/sensors/`. Expensive surface-ray channels are opt-in and appear in the vector schema whenever requested. Replay-ghost sensor type flags are also part of compact ray/nearby-car vectors so vector-only consumers can distinguish real cars from opt-in references. Driver rays use a precision-aware cached origin state and shared per-observation ray target context. Normal driver `precision: 'driver'` road-edge, kerb, and illegal-surface channels keep the sampled values that policies receive with or without the track query index. The `batch-training` participant profile keeps the indexed recovery-ray path as its high-throughput training contract, and other indexed ray-boundary shortcuts are reserved for non-driver/debug precision paths. Destroyed/out-of-race cars keep the active ray schema but short-circuit to miss-valued rays before creating track ray contexts, which prevents terminal cars in batch training from repeatedly querying far-out geometry. Pit-connector and unusual cases still fall back to sampled checks. Ray precision defaults to the sampled model-facing `driver` contract; `debug` precision is diagnostic-only and must not be silently substituted into policy inputs or model-sense visualization.
|
|
112
112
|
- `metrics.js` owns reward-neutral per-driver facts such as progress delta, legal progress delta, off-track/severe-cut state, destroyed state, low-speed state, spin/backwards/instability state, lap completion, lap time, and contact count. Contact metrics dedupe the identifiers on each normalized physical contact event so one event increments each involved driver once. It must not encode or choose rewards.
|
|
113
|
-
- `recorder.js` owns neutral rollout transition export. It
|
|
114
|
-
- `controllerLoop.js` owns browser/headless controller orchestration around an injected runtime. It may lazily reset a fresh runtime before scheduled playback, but public `reset()` remains an explicit playback stop. Controller contexts report current `actions` separately from `previousActions`, where previous means the last action map actually applied before the current physics step. Scheduled playback catches controller/runtime failures, stops the loop, and exposes the thrown value through loop stats.
|
|
113
|
+
- `recorder.js` owns neutral rollout transition export. It snapshots observations, actions, rewards, next observations, terminal flags, and info at record time and does not interpret rewards or policies.
|
|
114
|
+
- `controllerLoop.js` owns browser/headless controller orchestration around an injected runtime. It may lazily reset a fresh runtime before scheduled playback, but public `reset()` remains an explicit playback stop. Controller contexts report current `actions` separately from `previousActions`, where previous means the last action map actually applied before the current physics step. It maintains an observation cache so reset-only `resetDrivers()` results can stay small without handing null observations to the next batch decision for non-reset controlled drivers. Scheduled playback catches controller/runtime failures, stops the loop, and exposes the thrown value through loop stats.
|
|
115
115
|
- `evaluation.js` owns deterministic evaluation cases and metrics. It can run a supplied policy against fixed seeds/scenarios, internally requests the minimal snapshot needed when compact no-state base options are supplied, and reuses environment metric semantics for legal surfaces and contacts. It must not update models or define reward objectives.
|
|
116
116
|
- `workerProtocol.js` owns the JSON-serializable wrapper used by external bridges. It forwards reset/step/spec/state messages to an environment instance, including `getState` output selection, and does not choose Python, Gymnasium, PettingZoo, or storage infrastructure.
|
|
117
117
|
|
|
@@ -119,7 +119,7 @@ The environment subpath must not import `src/index.js`, package CSS, PixiJS, `F1
|
|
|
119
119
|
|
|
120
120
|
`src/simulation/rulesConfig.js` is the public rules normalization facade. Defaults live in `src/simulation/rules/ruleDefaults.js`, ruleset presets in `rulesetPresets.js`, merge/value helpers in `ruleConfigMerge.js`, module normalization in `moduleConfig.js`, and penalty consequence normalization in `penaltyConfig.js`. Race modules should be added through those focused files first, then consumed by simulation logic through the normalized `race.rules` object.
|
|
121
121
|
|
|
122
|
-
`src/simulation/track/trackModel.js` is the canonical track-model facade. Procedural centerline generation, validation, sample rebuilding, start-straight normalization, sectors, DRS zones, pit-lane layout/access/state, spatial query indexing, and spatial queries live in focused `src/simulation/track/*` modules. `src/simulation/trackModel.js` remains a compatibility re-export for tests or older internal imports, but new production code should import the canonical track module. `buildTrackModel()` attaches a non-enumerable internal `track.queryIndex` after samples, DRS, sectors, and pit-lane geometry are finalized, and cached built models freeze their static geometry so consumers cannot mutate shared pit-lane data between builds. Race simulations clone the built track to own mutable timing/pit state and default to attaching a fresh runtime index fork unless `trackQueryIndex: false` is explicitly requested.
|
|
122
|
+
`src/simulation/track/trackModel.js` is the canonical track-model facade. Procedural centerline generation, validation, sample rebuilding, start-straight normalization, sectors, DRS zones, pit-lane layout/access/state, spatial query indexing, and spatial queries live in focused `src/simulation/track/*` modules. `src/simulation/trackModel.js` remains a compatibility re-export for tests or older internal imports, but new production code should import the canonical track module. `buildTrackModel()` attaches a non-enumerable internal `track.queryIndex` after samples, DRS, sectors, and pit-lane geometry are finalized, and cached built models freeze their static geometry so consumers cannot mutate shared pit-lane data between builds. Race simulations clone the built track to own mutable timing/pit state and default to attaching a fresh runtime index fork unless `trackQueryIndex: false` is explicitly requested. `trackQueryIndex.js` owns the public indexed-query orchestration over typed centerline segment data, arc-length buckets, surface band thresholds, and deterministic nearest/ray query selection. Generic uniform grid operations live in `trackQueryGrid.js`, scratch buffer/epoch reuse lives in `trackQueryScratch.js`, query diagnostics live in `trackQueryStats.js`, and pit-lane road/box grid construction plus pit candidate lookups live in `trackQueryPitIndex.js`. Public callers still use `nearestTrackState()` and the same snapshot shapes; the index is internal infrastructure and must not leak into JSON snapshots or host contracts. Indexed nearest lookup resolves equal-distance centerline segment ties deterministically inside the index; legacy nearest-sample fallback is reserved for missing indexes, invalid positions, or points outside the indexed world bounds. Ray-boundary queries walk the tight segment grid along the ray corridor instead of querying the expanded recovery grid, which keeps compact training-track profiles from producing thousands of false candidate segments per ray. The model explicitly straightens the start/finish window so the starting grid, pit-entry approach area, and immediate post-line exit area are geometrically straight instead of merely choosing the least-curved generated segment. The pit lane is generated next to that start straight for every built track and exposes entry/exit connectors, explicit procedural road centerlines that overlap the track surface, connect on the lane-facing side of the main track, are tangent to the track at merge points and to the main pit lane at pit-lane endpoints, a straight main fast lane sized from the team/box run plus bounded entry/exit buffers, a parallel working lane, one shared service area per team, queue geometry behind each service area, and two garage boxes per team. `src/rendering/proceduralTrackAsset.js` is now only the Pixi container lifecycle wrapper; main-circuit drawing, pit-lane drawing, grid rendering, finish-line rendering, material constants, render geometry, and offset-stroke safety live under `src/rendering/track/`. `F1SimulatorApp` overlays the dynamic pit-lane status light because open/closed/red-flag state belongs to race control, not the static track texture.
|
|
123
123
|
|
|
124
124
|
`src/simulation/rules/` owns focused rule calculation helpers. Each stewarded rule should have its own function and, once non-trivial, its own file. `raceSimulation.js` should orchestrate timing/state and call those helpers; rule modules should return events or penalty payloads rather than mutating the whole simulation directly.
|
|
125
125
|
|
|
@@ -178,11 +178,13 @@ Warmup ownership is split intentionally: `src/simulation/warmup/runtimeWarmup.js
|
|
|
178
178
|
|
|
179
179
|
When `options.expert.enabled` is true, `F1SimulatorApp` creates a narrow browser expert adapter around its existing `this.sim` race simulation. The adapter uses the same shared environment runtime as the headless API, but it must not create a second race simulation for the visual mount. Expert browser mounts disable automatic ticker-driven simulation advancement; the canvas updates only after `simulator.expert.reset()` or `simulator.expert.step(actions)` renders the new snapshot. External renderer mode may temporarily redraw the shared track asset from authoritative external snapshots; detach restores the local simulation track before local expert stepping resumes. The shared runtime owns controlled-driver pit intent defaults: controlled cars start with `pitIntent` set to `0`, tire-threshold automatic pit calls are disabled for them, and expert actions may request `0`, `1`, or `2` plus optional `pitCompound` through the simulation API instead of steering into the pit lane directly. Expert mode is a mount-time boundary; runtime restart rejects `expert` changes instead of attempting to rewire ticker ownership in place.
|
|
180
180
|
|
|
181
|
+
`src/app/BrowserExpertExternalRenderer.js` owns the external-renderer bridge used by browser expert mode: source subscription lifecycle, external-to-local driver-id mapping, external frame observation/event/penalty id rewriting, external track-surface redraws, local track restoration on detach, and external-renderer state reporting. `BrowserExpertAdapter.js` remains the local visual expert runtime adapter and should not grow those external-frame responsibilities again.
|
|
182
|
+
|
|
181
183
|
Expert sensor visualization belongs to the browser app layer, not the headless environment. The environment result owns the observation contract; `BrowserExpertAdapter` passes that observation into `F1SimulatorApp.renderExpertFrame()`, and `src/app/rendering/expertSensorRenderer.js` draws opt-in sensor rays in a Pixi world layer so they share the same camera transform as the track and cars. Multi-driver expert mounts visualize the selected controlled car by default, with an explicit `drivers: 'all'` opt-in for heavier all-controlled-car overlays. Ray visualization draws every detected model-facing channel as a separate marker rather than choosing only the nearest hit, which keeps road-edge, kerb, illegal-surface, and car detection debuggable. Barrier walls are track geometry and destruction boundaries, not model-facing ray hits; active ray objects, vectors, schemas, and visualizations must not expose a `barrier` ray channel. The Policy Runner senses panel follows the same boundary for all senses: it reads the active observation object/vector and may show separate diagnostics only when those are labeled as diagnostics, not as model input.
|
|
182
184
|
|
|
183
185
|
Track material rendering must use the same surface-band thresholds as simulation. `src/rendering/track/trackMaterialRenderer.js` exposes `getTrackMaterialBands(track)` as the rendering-side band contract derived from `track.width`, `track.kerbWidth`, `track.gravelWidth`, `track.runoffWidth`, and `track.barrierWidth`; gravel, runoff, visible barrier walls, ray surface hits, wheel surfaces, and barrier destruction must not invent independent offsets or hardcoded widths. Barrier contact uses the visible vehicle footprint against the rendered wall's inner face so visual contact, destruction, and the environment's terminal state agree.
|
|
184
186
|
|
|
185
|
-
Environment sensor target selection belongs to `src/environment/sensors/sensorTargets.js`. It adapts real cars and opt-in replay ghosts into a common sensor target shape while preserving the invariant that replay ghosts stay out of `snapshot.cars` and race systems.
|
|
187
|
+
Environment sensor target selection belongs to `src/environment/sensors/sensorTargets.js`. It adapts real cars and opt-in replay ghosts into a common sensor target shape while preserving the invariant that replay ghosts stay out of `snapshot.cars` and race systems. Radar enrichment keys targets by entity type plus id, so duplicate ids between real cars and replay ghosts do not cross-contaminate velocity-derived nearby-car facts.
|
|
186
188
|
|
|
187
189
|
`F1SimulatorApp.js` should stay a facade/orchestrator. New camera, rendering, readout, banner, or runtime behavior should usually land in the matching `src/app/*/` ownership module rather than expanding the app class.
|
|
188
190
|
|
|
@@ -245,13 +247,13 @@ Responsibilities:
|
|
|
245
247
|
|
|
246
248
|
`src/simulation/rules/penaltyStats.js` owns penalty-stat aggregation and lookup helpers.
|
|
247
249
|
|
|
248
|
-
`src/simulation/vehicle/contactResolution.js` owns physical collision resolution and contact velocity response. Stewarding remains separate from physical response.
|
|
250
|
+
`src/simulation/vehicle/contactResolution.js` owns physical collision resolution and contact velocity response. Stewarding remains separate from physical response. In simulator mode, every collision branch that changes speed also keeps `velocityX/Y` synchronized, including contacts where one car is fixed by pit entry, queue, service, or exit routing.
|
|
249
251
|
|
|
250
252
|
`src/simulation/vehicle/runoffResponse.js` owns non-penalty off-track vehicle correction.
|
|
251
253
|
|
|
252
254
|
`src/simulation/snapshots/raceSnapshots.js` owns public, render, and observation snapshot assembly.
|
|
253
255
|
|
|
254
|
-
Weather effects, reliability failures, and fuel-load performance effects are not active 2.0.
|
|
256
|
+
Weather effects, reliability failures, and fuel-load performance effects are not active `2.0.x` behavior. Their future ownership should stay separate from the race orchestrator: weather belongs in a future `src/simulation/weather/` module that returns track/session condition modifiers, reliability belongs in a future `src/simulation/reliability/` module that emits deterministic failure/degradation events, and fuel-load effects belong in a future vehicle-adjacent performance module that adjusts mass/pace through explicit inputs rather than hidden mutation.
|
|
255
257
|
|
|
256
258
|
`snapshot()` is the full public data contract and must remain stable for host callbacks, environment state, and external callers. Internal browser/rendering paths use lean read models such as `snapshotRender()` so the 60 FPS loop does not serialize setup, wheels, penalties, and other non-render fields every frame. Environment and sensor code may use similarly narrow internal views, cache shared per-observation ray origin state, and broadphase car-ray checks before exact footprint intersection, but those must not change the public `RaceSnapshot` shape.
|
|
257
259
|
|
|
@@ -263,7 +265,9 @@ Weather effects, reliability failures, and fuel-load performance effects are not
|
|
|
263
265
|
|
|
264
266
|
`src/simulation/vehicle/wheelSurface.js` owns per-wheel track sampling and the effective car surface. Surface priority, analytic main-track classification, and pit-lane/connector classification are split into `surfacePriority.js`, `mainTrackWheelSurface.js`, and `pitWheelSurface.js`. On normal main-track running it uses the cached wheel geometry and the car center's track normal to analytically classify each wheel patch from signed offsets. It falls back to full patch sampling near pit connectors and inside pit-lane states. `vehiclePhysics.js` consumes that effective surface through the car track state, and separately compares left-side and right-side wheel resistance to add a small capped yaw bias toward the slower side.
|
|
265
267
|
|
|
266
|
-
`src/simulation/vehiclePhysics.js` owns vehicle integration using the already-resolved wheel surfaces and wheel-state resistance imbalance. It exposes the normalized `physicsMode` contract and routes between the compatibility arcade integrator and the opt-in simulator integrator. The simulator path owns 2D velocity integration, yaw response, traction-budget calculation, speed-sensitive steering, steering scrub, aero drag/downforce balance, per-wheel surface averaging, derived slip-angle telemetry, simulator surface coefficients, and telemetry fields;
|
|
268
|
+
`src/simulation/vehicle/vehiclePhysics.js` owns vehicle integration using the already-resolved wheel surfaces and wheel-state resistance imbalance. It exposes the normalized `physicsMode` contract and routes between the compatibility arcade integrator and the opt-in simulator integrator. The simulator path owns 2D velocity integration, yaw response, traction-budget calculation, speed-sensitive steering, steering scrub, aero drag/downforce balance, per-wheel surface averaging, derived slip-angle telemetry, simulator surface coefficients, and telemetry fields; steering scrub opposes signed forward motion so reverse slides do not gain energy from steering loss. Driver AI and environment policies still provide only steering/throttle/brake/pit intent.
|
|
269
|
+
|
|
270
|
+
`src/simulation/vehicle/vehicleState.js` owns external state mutation from simulator and environment reset APIs. It rejects non-finite numeric partial state and resynchronizes simulator velocity from speed/heading when callers do not provide an explicit velocity vector, so scenario placement and `resetDrivers()` cannot leave stale physics velocity behind.
|
|
267
271
|
|
|
268
272
|
`src/simulation/rules/pitLaneSpeedingSteward.js` owns pit-speed-limit review. `raceSimulation.js` calls it after authoritative race-state recalculation so it reads the same pit-lane part classification as snapshots and track-limit stewarding. The steward only checks speed-limited pit-lane parts (`fast-lane`, `working-lane`, service areas, and garage boxes), leaving pit-entry and pit-exit connector roads legal but not speed-limited.
|
|
269
273
|
|
|
@@ -316,6 +320,8 @@ Penalty UI is display-only. `src/app/banners/stewardMessageController.js` may sh
|
|
|
316
320
|
|
|
317
321
|
`src/config/defaultOptions.js` owns preset resolution, telemetry module normalization, the public theme contract, and browser warmup option normalization defaults. Presets are merged before host options; theme fields are applied as package CSS variables by the all-in-one app and composable controller. This keeps sizing/color customization explicit without turning internal layout ratios into public API.
|
|
318
322
|
|
|
323
|
+
`src/config/restartOptions.js` owns shared restart-option merging for both `mountF1Simulator().restart()` and `createPaddockSimulator().restart()`. It preserves omitted driver/entry data, merges nested UI banner and asset texture groups, and resets prior UI/theme state when a restart supplies a new preset so the two browser mount surfaces cannot drift.
|
|
324
|
+
|
|
319
325
|
Race completion is owned by `src/simulation/raceSimulation.js`. The simulation records provisional `finishOrder` / `finishRank` as each car crosses the line and uses that order for snapshots until final classification exists, so app code never has to re-sort finished cars by live distance. Race retirement state is owned by `src/simulation/race/retirements.js`; it defines DNF detection and ordering while vehicle code only marks the physical destruction event. The app layer reads `raceControl.finished`, `winner`, `classification`, and per-car `raceStatus`/DNF fields from snapshots, renders the timing/winner states, and emits lifecycle callbacks. Penalty lifecycle, pit-stop penalty service, service conversion, time adjustment, position drops, grid drops, disqualification effects, and DNF classification placement are applied inside the simulation, not in the UI. UI code may display countdowns from `pitStop.phase`, `serviceRemainingSeconds`, and `penaltyServiceRemainingSeconds`, but it must not infer, serve, or recalculate steward outcomes from lap text, timing rows, or car position.
|
|
320
326
|
|
|
321
327
|
Composable hosts may choose where each package-owned component root is placed, but they still receive package-generated markup through the public mount functions. The controller marks each mounted root as an `f1-sim-component` styling scope so standalone pieces receive the same package variables as the all-in-one shell.
|
package/docs/concepts.md
CHANGED
|
@@ -55,7 +55,7 @@ The composable controller has a setup phase:
|
|
|
55
55
|
|
|
56
56
|
After startup, both controller styles expose runtime methods such as `restart()`, `selectDriver()`, `setSafetyCarDeployed()`, `getSnapshot()`, and `destroy()`.
|
|
57
57
|
|
|
58
|
-
Pit-stop hosts can also call `setPitIntent(driverId, 0 | 1 | 2)`. `0`
|
|
58
|
+
Pit-stop hosts can also call `setPitIntent(driverId, 0 | 1 | 2, targetCompound?)` after enabling `rules.modules.pitStops`. `0` clears a pending request, `1` keeps trying until a free-enough pit-entry window is available, and `2` commits to entering at the next pit-entry window even when opportunistic capacity or gap checks would block mode `1`. `targetCompound` must be one of `rules.modules.tireStrategy.compounds`; `tireStrategy` controls tire choices and stewarding, but does not enable pit stops by itself.
|
|
59
59
|
|
|
60
60
|
## Driver
|
|
61
61
|
|
|
@@ -87,7 +87,7 @@ Entry responsibilities:
|
|
|
87
87
|
|
|
88
88
|
## Team
|
|
89
89
|
|
|
90
|
-
A team is entry-level metadata used for race identity and
|
|
90
|
+
A team is entry-level metadata used for race identity and current pit-lane behavior. The timing tower uses the team icon in its team column. Team color defaults to the car color when omitted. When pit stops are enabled, team color marks the shared service area, queue point, and garage boxes, and optional `team.pitCrew` stats can affect service-time variability.
|
|
91
91
|
|
|
92
92
|
## Driver Ratings
|
|
93
93
|
|
|
@@ -127,7 +127,7 @@ The package supports:
|
|
|
127
127
|
|
|
128
128
|
Browser mounts that omit `trackSeed` create a fresh procedural track for that mount. Passing `trackSeed` makes the generated circuit deterministic; repeated procedural seeds are cached within the page runtime. Generated circuits start from seeded connected coarse regions, trace the region boundary, then smooth and warp that boundary into the sampled centerline. This lets tracks move inward and outward through concave sections and chicane-like bends instead of degrading into circular or oval fallback layouts. Failed candidates retry with another deterministic region-derived shape.
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
Tracks whose resolved generation options enable pit lanes expose a deterministic `pitLane` near the start/finish straight. The `race` profile includes pit-lane geometry; training profiles are pitless by default unless the host explicitly enables a pit lane. An enabled pit lane has an entry before the start line, an exit after it, explicit lane-aligned entry/exit road centerlines, a straight main fast lane, a parallel working lane, 10 shared team service areas, and 20 unused garage boxes arranged as 10 team pairs. Pit-lane asphalt, service areas, and garage boxes are legal drivable surfaces for sensors, runoff handling, track-limit stewarding, and stalled-DNF timing. When the pit-stop module is enabled, cars automatically form bounded pit trains when there is enough rolling gap, brake to the limiter by the main lane start, follow the fast lane, pass through their assigned colored team queue spot, roll into the team service area, change tire compound, and return through the exit. Team-mates share one service area; every car passes through the queue spot first, but it only waits there if the active service area is occupied. A second team car waits without blocking the fast lane before following a short queue-release route into the active service area after the previous car has physically cleared it. Tire condition can request a stop automatically: below the configured request threshold the car asks to pit if free, and below the commit threshold it keeps retrying until served. The speed limiter is active on the straight main pit lane/working lane, not on the entry and exit connector roads.
|
|
131
131
|
|
|
132
132
|
## Progress
|
|
133
133
|
|
|
@@ -161,7 +161,9 @@ Per-car timing exposes both interval to the car ahead and direct same-lead-lap g
|
|
|
161
161
|
|
|
162
162
|
A ruleset is a named preset for race-rule defaults. `paddock` is the package default, `grandPrix2025` / `fia2025` are 2024-2025-era grand-prix-style presets, and `custom` is for host-owned behavior.
|
|
163
163
|
|
|
164
|
-
A rule module is an advanced subsystem under `rules.modules
|
|
164
|
+
A rule module is an advanced subsystem under `rules.modules`. Active modules include pit stops, tire strategy, tire degradation, stalled off-track DNF, and penalties. Weather, reliability, and fuel load are reserved placeholders and do not currently change grip, power, failure risk, mass, or pace. Presets set defaults, but explicit module config wins.
|
|
165
|
+
|
|
166
|
+
`pitStops` and `tireStrategy` are intentionally separate. `pitStops.enabled` owns automatic pit routing, pit-lane status, and successful pit intent requests. `tireStrategy.enabled` owns compound lists, tire-requirement stewarding, and pit target choices. A two-compound rule does not make cars pit unless `pitStops` is enabled separately or the selected ruleset preset enables both modules.
|
|
165
167
|
|
|
166
168
|
Stalled off-track DNF is an opt-in retirement rule. When `rules.modules.stalledDnf.enabled` is `true`, it marks a car DNF when the car is off legal racing/pit surfaces and remains below the configured speed threshold for the configured time, while excluding pre-start, red flag, active pit handling, and already-finished cars.
|
|
167
169
|
|
package/docs/data_contract.md
CHANGED
|
@@ -14,6 +14,7 @@ mountF1Simulator(root, {
|
|
|
14
14
|
onDriverOpen,
|
|
15
15
|
seed,
|
|
16
16
|
trackSeed,
|
|
17
|
+
trackGeneration,
|
|
17
18
|
trackQueryIndex,
|
|
18
19
|
warmup,
|
|
19
20
|
totalLaps,
|
|
@@ -29,6 +30,7 @@ mountF1Simulator(root, {
|
|
|
29
30
|
backLinkLabel,
|
|
30
31
|
showBackLink,
|
|
31
32
|
ui,
|
|
33
|
+
debug,
|
|
32
34
|
assets,
|
|
33
35
|
expert,
|
|
34
36
|
onLoadingChange,
|
|
@@ -50,6 +52,7 @@ const simulator = createPaddockSimulator({
|
|
|
50
52
|
onDriverOpen,
|
|
51
53
|
seed,
|
|
52
54
|
trackSeed,
|
|
55
|
+
trackGeneration,
|
|
53
56
|
trackQueryIndex,
|
|
54
57
|
warmup,
|
|
55
58
|
totalLaps,
|
|
@@ -61,6 +64,7 @@ const simulator = createPaddockSimulator({
|
|
|
61
64
|
preset,
|
|
62
65
|
theme,
|
|
63
66
|
ui,
|
|
67
|
+
debug,
|
|
64
68
|
assets,
|
|
65
69
|
expert,
|
|
66
70
|
});
|
|
@@ -114,6 +118,7 @@ const env = createPaddockEnvironment({
|
|
|
114
118
|
standingStart: false,
|
|
115
119
|
modules: {
|
|
116
120
|
penalties: {
|
|
121
|
+
enabled: true,
|
|
117
122
|
trackLimits: { strictness: 0.25 },
|
|
118
123
|
},
|
|
119
124
|
},
|
|
@@ -133,7 +138,7 @@ result = env.step({
|
|
|
133
138
|
`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
139
|
`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
140
|
`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.
|
|
141
|
+
`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: `velocityX`, `velocityY`, `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. Terminal and race-control-held simulator cars publish `velocityX: 0` and `velocityY: 0` whenever scalar `speed` is zero, so training loops do not see stale pre-DNF, pre-grid, pre-red-flag, or pre-queue motion. Arcade snapshots publish `velocityX` and `velocityY` as `null`; consumers that need a heading-aligned arcade velocity should derive it from `heading` and `speed`.
|
|
137
142
|
`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:
|
|
138
143
|
|
|
139
144
|
```js
|
|
@@ -148,6 +153,10 @@ rules: {
|
|
|
148
153
|
tirePitRequestThresholdPercent: 50,
|
|
149
154
|
tirePitCommitThresholdPercent: 30,
|
|
150
155
|
},
|
|
156
|
+
tireStrategy: {
|
|
157
|
+
enabled: true,
|
|
158
|
+
mandatoryDistinctDryCompounds: 2,
|
|
159
|
+
},
|
|
151
160
|
tireDegradation: {
|
|
152
161
|
enabled: false,
|
|
153
162
|
},
|
|
@@ -166,7 +175,7 @@ rules: {
|
|
|
166
175
|
}
|
|
167
176
|
```
|
|
168
177
|
|
|
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'
|
|
178
|
+
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.pitStops.enabled` is the switch for automatic pit routing, pit-lane status, and successful `setPitIntent()` calls. `rules.modules.tireStrategy.enabled` is separate: it controls available compounds, tire-requirement stewarding, and pit target choices, but it does not enable pit stops. Use `ruleset: 'fia2025'` / `ruleset: 'grandPrix2025'` or explicitly enable both modules when a host wants a two-compound race with automatic stops. `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'` only when they are off legal racing or pit surfaces. 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.
|
|
170
179
|
|
|
171
180
|
Scenario support:
|
|
172
181
|
|
|
@@ -198,7 +207,7 @@ scenario: {
|
|
|
198
207
|
|
|
199
208
|
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
209
|
|
|
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)`.
|
|
210
|
+
`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. If all reported controlled drivers are ended, top-level `done` is true even when their terminal states are mixed, such as one destroyed driver and one max-step-truncated driver. 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. With reset-only observation scope, top-level terminal state is evaluated over the reported reset drivers but `info.drivers` still reflects each controlled driver's own state, so a destroyed reset driver does not falsely terminate healthy non-reset drivers. This API is not accepted inside `step(actions)`.
|
|
202
211
|
|
|
203
212
|
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
213
|
|
|
@@ -254,7 +263,7 @@ replayGhosts: [
|
|
|
254
263
|
]
|
|
255
264
|
```
|
|
256
265
|
|
|
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.
|
|
266
|
+
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. Sensor target filtering and enrichment treat `{ entityType, id }` as the target identity, so a replay ghost id collision cannot replace a real car's nearby-radar velocity facts and an opt-in same-id reference ghost remains visible to that car's sensors.
|
|
258
267
|
|
|
259
268
|
Actions use normalized low-level controls:
|
|
260
269
|
|
|
@@ -352,7 +361,9 @@ observation: {
|
|
|
352
361
|
}
|
|
353
362
|
```
|
|
354
363
|
|
|
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.
|
|
364
|
+
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. When `sensorsByDriver` changes ray or nearby-car shape for individual controlled drivers, `observationSpec.perDriver[driverId]` exposes that driver's exact ray, nearby-car, and vector schema shape. Full observations include the actual object ray geometry and Policy Runner object-backed encoding uses that geometry instead of reconstructing angles or lengths from a default layout. `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.
|
|
365
|
+
|
|
366
|
+
The executable model-sense contract lives in `src/__tests__/environmentSenseContract.test.js` and is summarized in [Model Sense Contract](sense_contract.md). It treats the object observation as a policy-facing contract, checks it against simulator snapshot facts through independent unit and geometry formulas, then decodes each vector schema entry back to the object contract. A new vector field is not considered contract-covered until it has an oracle there.
|
|
356
367
|
|
|
357
368
|
Result state output can also be compacted:
|
|
358
369
|
|
|
@@ -363,7 +374,7 @@ result: {
|
|
|
363
374
|
}
|
|
364
375
|
```
|
|
365
376
|
|
|
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
|
|
377
|
+
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, metrics, and `info.controlledDrivers` only for reset drivers by default; callers can override per call with `env.resetDrivers(placements, { observationScope: 'all' | 'reset', stateOutput })`. `info.drivers` still carries episode state for the configured controlled drivers so active, non-reset drivers keep their episode ids and terminal flags. `createPaddockDriverControllerLoop()` maintains an observation cache across partial resets and merges reset-driver observations into it before the next controller decision. 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
378
|
|
|
368
379
|
The JSON worker protocol exposes the same reset-result controls through `resultOptions`:
|
|
369
380
|
|
|
@@ -419,7 +430,7 @@ reward({ driverId, previous, current, action, events, state, metrics, episode })
|
|
|
419
430
|
}
|
|
420
431
|
```
|
|
421
432
|
|
|
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.
|
|
433
|
+
`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. Reward callbacks run only for `step(actions)` transition results; reset/read paths such as `reset()`, `resetDrivers()`, and cold `getObservation()` are reward-neutral and return no transition reward. 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`. The starter `createProgressReward()` reads speed/legal-surface facts from the reward snapshot and metrics when object observations are omitted, so the same transition keeps the same demo reward in compact vector-only mode. PaddockJS does not infer, select, or tune rewards.
|
|
423
434
|
|
|
424
435
|
Neutral rollout recording is available for external training loops:
|
|
425
436
|
|
|
@@ -433,7 +444,7 @@ const next = env.step(action);
|
|
|
433
444
|
recorder.recordStep(previous, action, next);
|
|
434
445
|
```
|
|
435
446
|
|
|
436
|
-
Each recorded transition has `{ observation, action, reward, nextObservation, terminated, truncated, info }`. This is data export only; it does not train or update a model.
|
|
447
|
+
Each recorded transition has `{ observation, action, reward, nextObservation, terminated, truncated, info }`. Recorded transitions are cloned at record time, including numeric arrays and typed vectors, so later mutation of a policy action object or observation buffer cannot change already-recorded samples. This is data export only; it does not train or update a model.
|
|
437
448
|
|
|
438
449
|
Deterministic evaluation helpers run fixed seeds/scenarios and report environment quality metrics:
|
|
439
450
|
|
|
@@ -562,7 +573,7 @@ snapshot.replayGhosts = trajectory-driven replay/reference entities
|
|
|
562
573
|
|
|
563
574
|
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
575
|
|
|
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
|
|
576
|
+
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. Compact physical-driver vectors include each patch's `surfaceCode`, `signedOffsetMeters`, and `crossTrackErrorMeters` so vector-only training loops do not lose a contact-patch sense that exists in object observations.
|
|
566
577
|
|
|
567
578
|
Default rays use a compact center-origin set with forward, side, and rear awareness:
|
|
568
579
|
|
|
@@ -616,9 +627,9 @@ sensors: {
|
|
|
616
627
|
|
|
617
628
|
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
629
|
|
|
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.
|
|
630
|
+
Surface channels are computed only when requested. `kerb` is legal racing surface. `illegalSurface` reports the first grass or gravel surface crossed by the ray. Requested surface-ray channels are part of the active object observation and compact vector schema for both default and physical-driver profiles. For normal driver interactions, `precision: 'driver'` road-edge, kerb, and illegal-surface channels keep the sampled driver contract whether the track query index is enabled or disabled. The `batch-training` participant profile keeps its indexed recovery-ray path as its own high-throughput training contract. Other indexed ray-boundary shortcuts are reserved for non-driver/debug precision paths where sub-sample diagnostics are explicitly requested. Requested surface channels preserve an origin hit at `distanceMeters: 0` even if another requested channel has no visible boundary. Ambiguous pit-connector and unusual geometry cases may use 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
631
|
|
|
621
|
-
Nearby-car observations are car-relative:
|
|
632
|
+
Nearby-car observations are car-relative. When simulator velocity fields are present, `closingRateMetersPerSecond` and `timeToContactSeconds` use the actual 2D velocity vector instead of assuming the car is moving in its heading direction, so slip and recovery states remain physically accurate:
|
|
622
633
|
|
|
623
634
|
```js
|
|
624
635
|
{
|
|
@@ -639,6 +650,7 @@ Nearby-car observations are car-relative:
|
|
|
639
650
|
```
|
|
640
651
|
|
|
641
652
|
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`.
|
|
653
|
+
Reward callbacks run only for `step(actions)` transition results. Reset and read paths return `reward: null`, so episode setup, `resetDrivers()`, and `getObservation()` cannot create fake replay rewards or trigger reward side effects.
|
|
642
654
|
|
|
643
655
|
Browser expert mode is opt-in through the browser mount API:
|
|
644
656
|
|
|
@@ -838,7 +850,7 @@ onDriverOpen(driver) {
|
|
|
838
850
|
}
|
|
839
851
|
```
|
|
840
852
|
|
|
841
|
-
The callback receives the normalized driver object. If the host wants modals, routing, analytics, or external tabs, it should implement that inside this callback.
|
|
853
|
+
The callback receives the normalized driver object. If the host wants modals, routing, analytics, or external tabs, it should implement that inside this callback. `onDriverOpen` is host-owned navigation code and should handle its own failures; unlike lifecycle callbacks, it is not routed through `onError`.
|
|
842
854
|
|
|
843
855
|
Optional lifecycle callbacks:
|
|
844
856
|
|
|
@@ -854,7 +866,7 @@ Optional lifecycle callbacks:
|
|
|
854
866
|
}
|
|
855
867
|
```
|
|
856
868
|
|
|
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`.
|
|
869
|
+
`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`. Lifecycle callback errors are caught; if `onError` exists, it receives `{ callback: name }` context for callback failures.
|
|
858
870
|
|
|
859
871
|
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.
|
|
860
872
|
|
|
@@ -1024,7 +1036,7 @@ Every built track is automatically divided into three equal sectors. `snapshot.t
|
|
|
1024
1036
|
|
|
1025
1037
|
Every built track also exposes hidden `snapshot.track.timingLines`. Timing lines are spaced from the track length at an F1-style mini-sector target of roughly `150m..200m`; they are simulation metadata for gap calculation and are not rendered by default.
|
|
1026
1038
|
|
|
1027
|
-
|
|
1039
|
+
Tracks whose resolved generation options enable pit lanes expose `snapshot.track.pitLane`. The `race` profile includes this geometry; training profiles are pitless by default unless explicitly overridden. When present, the pit lane is deterministic for the track seed and contains:
|
|
1028
1040
|
|
|
1029
1041
|
- `entry`: track distance before the start line, the true track `edgePoint`, an overlapping lane-facing `trackConnectPoint` on the track surface, connector points from the racing surface to the pit lane, and a procedural `roadCenterline` that is tangent to the main track at entry and tangent to the straight pit lane at the pit-lane start.
|
|
1030
1042
|
- `layout`: the model-owned pit sizing data, including the box-run length, total main-lane length, entry/exit distances relative to start/finish, and entry/exit buffers. The main lane is sized from the configured team/box count instead of using a fixed oversized straight.
|
|
@@ -1088,7 +1100,7 @@ Controller methods:
|
|
|
1088
1100
|
- `callSafetyCar()`: deploys the safety car.
|
|
1089
1101
|
- `clearSafetyCar()`: releases the safety car.
|
|
1090
1102
|
- `toggleSafetyCar()`: switches safety car deployment based on the current snapshot.
|
|
1091
|
-
- `setPitIntent(driverId, intent, targetCompound?)`: requests, clears, or updates a pending automatic pit stop and optional target tire.
|
|
1103
|
+
- `setPitIntent(driverId, intent, targetCompound?)`: requests, clears, or updates a pending automatic pit stop and optional target tire. This requires `rules.modules.pitStops.enabled: true` and track pit-lane geometry; `tireStrategy` alone only affects compound choices.
|
|
1092
1104
|
- `getPitIntent(driverId)`: reads the current pit intent.
|
|
1093
1105
|
- `getPitTargetCompound(driverId)`: reads the current pit target tire.
|
|
1094
1106
|
- `getSimulationSpeed()`: returns the active browser playback multiplier from the package-owned simulation-speed control, defaulting to `1`.
|
|
@@ -1111,6 +1123,7 @@ Composable controllers additionally expose:
|
|
|
1111
1123
|
- `mountRaceTelemetryDrawer(root, { timingTowerVerticalFit, drawerInitiallyOpen, raceDataTelemetryDetail })`: renders a template that combines an external top control row, race canvas, embedded timing tower, the project/radio lower-third, top steward message, safety-car control, and a right-side telemetry drawer. The control row contains camera controls, the `1x..10x` simulation-speed toggle, banner mute, the safety-car button, and the telemetry toggle so those controls do not cover the race view. Pass `raceDataTelemetryDetail: true` when this template should put compact S1/S2/S3 detail in the project lower-third instead of mounting a second sector popup. The drawer embeds the same package-owned telemetry stack used by `mountTelemetryPanel()` and takes layout space from the race window when opened.
|
|
1112
1124
|
- `mountCarDriverOverview(root)`: renders the package-owned car/driver overview as a separate component with a Car/Driver toggle, center visual, and linked stat cells from the existing driver/vehicle rating components.
|
|
1113
1125
|
- `mountRaceDataPanel(root)`: renders the project/race-data lower-third as a separate component for hosts that intentionally want it outside the race canvas.
|
|
1126
|
+
- `querySelector(selector)` / `querySelectorAll(selector)`: search across the mounted package-owned composable roots. These exist for integration tests and advanced host glue; ordinary hosts should prefer explicit controller methods and mounted component roots.
|
|
1114
1127
|
- `start()`: initializes PixiJS, binds mounted controls, and starts the simulation loop.
|
|
1115
1128
|
|
|
1116
1129
|
Mount component roots before calling `start()`. If a component is not mounted, the runtime skips that UI surface instead of requiring hidden placeholder DOM. Mounted surfaces render a package-owned loading overlay immediately; `start()` removes those overlays after PixiJS, assets, controls, and initial readouts have initialized.
|
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,18 @@ 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.
|
|
14
15
|
- [Data Model Notes](data_model.md): lower-level simulator data model notes kept with package docs.
|
|
15
16
|
- [Learnings](learnings.md): implementation lessons and layout verification rules learned from simulator integration issues.
|
|
16
17
|
|
|
18
|
+
Common paths:
|
|
19
|
+
|
|
20
|
+
- 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).
|
|
21
|
+
- Building a package-owned composable layout: read the public API and returned-controller sections in [System Specs](system_specs.md), then the sizing and UI options in [Data Contract](data_contract.md).
|
|
22
|
+
- 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.
|
|
23
|
+
- 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.
|
|
24
|
+
- 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.
|
|
25
|
+
|
|
17
26
|
When simulator behavior changes, update the relevant doc in this folder in the same change.
|