@inventure71/paddockjs 2.0.1 → 4.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 +176 -1
- package/INSTALL_AND_UPDATE.md +24 -4
- package/README.md +84 -512
- package/docs/architecture.md +38 -24
- package/docs/component_inventory.md +72 -0
- package/docs/composable-layouts.md +77 -0
- package/docs/concepts.md +2 -2
- package/docs/custom_model_controller.md +6 -6
- package/docs/data-helpers.md +50 -0
- package/docs/data_contract.md +104 -27
- package/docs/getting-started.md +75 -0
- package/docs/headless-environment.md +68 -0
- package/docs/index.md +18 -4
- package/docs/learnings.md +6 -6
- package/docs/rules.md +12 -8
- package/docs/sense_contract.md +1 -1
- package/docs/system_specs.md +71 -21
- package/docs/theming.md +64 -0
- package/docs/training.md +26 -19
- package/docs/troubleshooting.md +76 -0
- package/docs/upgrading-hosts-to-3.md +42 -0
- package/examples/python/README.md +6 -0
- package/examples/python/base_policy_server.py +19 -0
- package/package.json +10 -3
- package/src/api/PaddockSimulatorController.js +150 -24
- package/src/api/runtimeTheme.js +59 -0
- package/src/app/F1SimulatorApp.js +213 -29
- package/src/app/banners/raceDataBannerController.js +71 -36
- package/src/app/banners/stewardMessageController.js +5 -4
- package/src/app/camera/cameraConstants.js +2 -0
- package/src/app/camera/cameraController.js +86 -29
- package/src/app/camera/cameraGeometry.js +12 -1
- package/src/app/domBindings.js +89 -9
- package/src/app/layoutScheduler.js +27 -0
- package/src/app/layoutSupport.js +165 -0
- package/src/app/raceOverlayClearanceSupport.js +158 -0
- package/src/app/readouts/carOverviewRenderer.js +16 -8
- package/src/app/readouts/controlStateRenderer.js +1 -1
- package/src/app/readouts/raceStatusRenderer.js +30 -21
- package/src/app/readouts/readoutFormatters.js +2 -0
- package/src/app/readouts/telemetryRenderer.js +17 -8
- package/src/app/readouts/timingTowerRenderer.js +262 -49
- package/src/app/runtime/frameLoop.js +5 -1
- package/src/app/runtime/runtimeVisibility.js +66 -5
- package/src/config/cssValues.js +16 -0
- package/src/config/defaultOptions.js +58 -38
- package/src/config/themeOptions.js +830 -0
- package/src/config/timingGapMode.js +13 -0
- package/src/config/urlOptions.js +25 -0
- package/src/data/championship.js +14 -9
- package/src/data/index.d.ts +212 -0
- package/src/data/index.js +14 -0
- package/src/environment/controllerLoop.js +17 -3
- package/src/environment/episode.js +11 -1
- package/src/environment/index.d.ts +1 -2
- package/src/environment/metrics.js +59 -16
- package/src/environment/observationVector.js +50 -33
- package/src/environment/observations.js +89 -20
- package/src/environment/options.js +1 -1
- package/src/environment/runtime.js +44 -11
- package/src/environment/scenarios.js +1 -1
- package/src/environment/sensors/carRays.js +48 -37
- package/src/environment/sensors/index.js +501 -97
- package/src/environment/sensors/indexedRayBands.js +134 -46
- package/src/environment/sensors/rayBandTrace.js +1171 -0
- package/src/environment/sensors/rayChannels.js +108 -0
- package/src/environment/sensors/rayConfig.js +71 -25
- package/src/environment/sensors/rayGeometry.js +23 -17
- package/src/environment/sensors/sensorTargets.js +67 -1
- package/src/environment/sensors/surfaceRays.js +167 -118
- package/src/environment/sensors/trackRays.js +157 -48
- package/src/index.d.ts +153 -9
- package/src/index.js +52 -3
- package/src/simulation/collisionGeometry.js +260 -96
- package/src/simulation/driver/driverController.js +1 -1
- package/src/simulation/driver/edgeRecovery.js +1 -1
- package/src/simulation/driver/racingControls.js +2 -2
- package/src/simulation/driver/rejoinControls.js +2 -2
- package/src/simulation/pit/pitFlow.js +21 -9
- package/src/simulation/pit/pitKinematics.js +2 -2
- package/src/simulation/pit/pitQueue.js +5 -3
- package/src/simulation/pit/pitRouteMovement.js +9 -4
- package/src/simulation/pit/pitRouting.js +79 -46
- package/src/simulation/pit/pitState.js +1 -0
- package/src/simulation/pit/pitTireService.js +7 -3
- package/src/simulation/race/raceFinish.js +25 -8
- package/src/simulation/race/raceOrder.js +189 -32
- package/src/simulation/race/raceProgress.js +129 -37
- package/src/simulation/race/raceSetup.js +4 -6
- package/src/simulation/race/raceStep.js +26 -14
- package/src/simulation/race/safetyCar.js +1 -1
- package/src/simulation/race/stalledDnf.js +1 -1
- package/src/simulation/raceSimulation.js +20 -17
- package/src/simulation/rules/collisionSteward.js +40 -23
- package/src/simulation/rules/rulesReview.js +77 -56
- package/src/simulation/snapshots/raceSnapshots.js +37 -0
- package/src/simulation/timing/drsTiming.js +143 -30
- package/src/simulation/timing/gapEstimation.js +25 -3
- package/src/simulation/timing/lapTelemetry.js +138 -40
- package/src/simulation/timing/raceTiming.js +5 -1
- package/src/simulation/timing/sectorPerformance.js +104 -26
- package/src/simulation/timing/timingHistory.js +229 -14
- package/src/simulation/timing/timingLines.js +65 -7
- package/src/simulation/track/pitLaneLayout.js +30 -3
- package/src/simulation/track/pitLaneState.js +218 -19
- package/src/simulation/track/spatialQueries.js +66 -33
- package/src/simulation/track/trackMath.js +91 -24
- package/src/simulation/track/trackModel.js +2 -0
- package/src/simulation/track/trackQueryIndex.js +1468 -189
- package/src/simulation/track/trackQueryPitIndex.js +138 -14
- package/src/simulation/track/trackQueryScratch.js +7 -0
- package/src/simulation/track/trackQueryStats.js +25 -0
- package/src/simulation/track/trackSnapshotJson.js +139 -0
- package/src/simulation/track/trackStatePolicy.js +249 -2
- package/src/simulation/vehicle/contactResolution.js +99 -62
- package/src/simulation/vehicle/mainTrackWheelSurface.js +73 -34
- package/src/simulation/vehicle/pitWheelSurface.js +81 -28
- package/src/simulation/vehicle/runoffResponse.js +54 -29
- package/src/simulation/vehicle/vehicleGeometry.js +245 -51
- package/src/simulation/vehicle/vehicleKinematics.js +2 -2
- package/src/simulation/vehicle/vehiclePhysics.js +45 -30
- package/src/simulation/vehicle/vehicleSnapshots.js +35 -1
- package/src/simulation/vehicle/vehicleState.js +7 -1
- package/src/simulation/vehicle/wheelSurface.js +807 -92
- package/src/simulation/warmup/runtimeWarmup.js +0 -1
- package/src/styles.css +1568 -519
- package/src/ui/bannerTemplates.js +38 -28
- package/src/ui/cameraControlsTemplate.js +16 -5
- package/src/ui/carOverviewTemplate.js +12 -5
- package/src/ui/raceCanvasTemplate.js +31 -8
- package/src/ui/raceControlsTemplate.js +27 -10
- package/src/ui/shellTemplate.js +3 -1
- package/src/ui/telemetryDrawerTemplate.js +23 -10
- package/src/ui/telemetryTemplates.js +58 -32
- package/src/ui/templateUtils.js +48 -0
- package/src/ui/timingTowerTemplate.js +29 -12
package/docs/rules.md
CHANGED
|
@@ -99,11 +99,11 @@ The current implementation normalizes and exposes all module config, records a p
|
|
|
99
99
|
|
|
100
100
|
`tireDegradation.enabled: false` freezes each car's current `tireEnergy` instead of updating wear from throttle, braking, and lateral load. This is separate from `tireStrategy.enabled`: tire strategy controls compounds, tire-requirement stewarding, and pit-stop tire choices, while tire degradation controls the physics wear curve.
|
|
101
101
|
|
|
102
|
-
`stalledDnf` defaults to `enabled: false`. When a host sets `rules.modules.stalledDnf.enabled: true`, a car that is off legal racing or pit surfaces and remains below `speedThresholdKph` for `maxStoppedSeconds` is marked out of race with `dnfReason: 'stalled-off-track'`, emits a `car-dnf` event, freezes controls/motion, and leaves active collision, sensor, pit-blocking, and race-order participation. The freeze clears
|
|
102
|
+
`stalledDnf` defaults to `enabled: false`. When a host sets `rules.modules.stalledDnf.enabled: true`, a car that is off legal racing or pit surfaces and remains below `speedThresholdKph` for `maxStoppedSeconds` is marked out of race with `dnfReason: 'stalled-off-track'`, emits a `car-dnf` event, freezes controls/motion, and leaves active collision, sensor, pit-blocking, and race-order participation. The freeze clears advanced-mode velocity, acceleration, slip, grip, and manual controls together, so a retired car cannot keep a stale motion vector in snapshots or model observations. The timer does not run during pre-start, red flag, after race completion, for already-finished cars, or while pit entry/queue/service/exit owns the car. Returning to legal surface or moving above the threshold resets the timer.
|
|
103
103
|
|
|
104
104
|
Participant interaction profiles can opt real cars out of specific interaction systems without turning them into replay ghosts. A non-colliding profile skips vehicle collision resolution and collision stewarding for that car pair, but the car still uses normal steering/throttle/brake physics. A non-blocking pit profile is ignored by pit service occupancy and queue-blocking checks. A car with `affectsRaceOrder: false` remains in `snapshot.cars` but is excluded from ranking, DRS references, finish order, and final classification. Replay ghosts are separate trajectory overlays in `snapshot.replayGhosts`; they never participate in rules, collisions, timing, pit stops, or penalties.
|
|
105
105
|
|
|
106
|
-
Automatic pit routing owns car motion while a car is entering, queued, servicing, or exiting. During those phases the route/service logic keeps scalar speed, throttle/brake, `appliedControls`, and
|
|
106
|
+
Automatic pit routing owns car motion while a car is entering, queued, servicing, or exiting. During those phases the route/service logic keeps scalar speed, throttle/brake, `appliedControls`, and advanced telemetry synchronized so advanced-mode observations and the next physics-owned step do not inherit stale pre-pit motion, grip, slip, or instability state. Route-owned sampling/projection scratch preserves the same route geometry and limiter behavior without allocating sampled point/projection objects during each transition tick. `velocityX/Y` is synchronized in advanced mode and cleared from arcade snapshots because arcade physics does not own a world-velocity state.
|
|
107
107
|
|
|
108
108
|
## Steward Strictness
|
|
109
109
|
|
|
@@ -130,7 +130,7 @@ Pit-lane surfaces are legal road for track-limit purposes. `pit-entry`, `pit-lan
|
|
|
130
130
|
|
|
131
131
|
Penalty decisions are recorded in `snapshot.penalties` and emitted as `penalty` events in the same step. Each entry includes the penalty type, driver id, strictness, status, penalty seconds, pending service conversion seconds, lap, timestamp, and rule-specific context. Multiple time penalties for the same driver are additive: two separate +5s entries produce `penaltySeconds: 10` on that car's snapshot and classification adjustment.
|
|
132
132
|
|
|
133
|
-
Collision response and collision-steward impact speed use simulator `velocityX/Y` when available. In
|
|
133
|
+
Collision response and collision-steward impact speed use simulator `velocityX/Y` when available. In advanced mode this keeps slip, sideways recovery, pit-adjacent motion, and post-contact damping aligned with actual physical motion instead of assuming every car moves exactly in its heading direction. If a collision pushes against a pit-route-owned car, the pit-owned car stays fixed and the movable car's scalar speed and velocity vector are damped together so the next physics step and model-facing speed senses remain consistent.
|
|
134
134
|
|
|
135
135
|
Each penalty subsection can define `consequences`. Supported consequences are:
|
|
136
136
|
|
|
@@ -177,6 +177,10 @@ Race order is based on `raceDistance`, descending. Ties fall back to original dr
|
|
|
177
177
|
|
|
178
178
|
During safety car, order is frozen at deployment time. That prevents passing from reshuffling the timing tower while the safety car is active.
|
|
179
179
|
|
|
180
|
+
During normal green-flag stepping, driver decisions read the previously committed order, gap, DRS, and rank state. The current step then moves cars, applies local physics/pit/collision/rule phases, and commits the next broad race state once at the end of the active step. This means AI reactions can be one fixed frame behind the most recent physical movement, which is intentional for deterministic fixed-step ordering and avoids repeated broad timing/order recalculation inside one step.
|
|
181
|
+
|
|
182
|
+
Advanced-mode surface classification is also step-phased. The pre-physics surface read uses the last committed contact-patch state for grip, runoff response only checks barrier reach and records the center track state it already computed, and the post-collision local refresh owns the final wheel/contact-patch update for that step. If collision correction moves a car after runoff, the recorded center state is discarded and the refresh recomputes from the final pose.
|
|
183
|
+
|
|
180
184
|
The simulation builds hidden timing lines around every track at an F1-style mini-sector spacing target of roughly `150m..200m`. Timing history is sampled per car and timing-line crossing timestamps are used to calculate:
|
|
181
185
|
|
|
182
186
|
- Interval to the car ahead as `intervalAheadSeconds` / `gapAheadSeconds`.
|
|
@@ -194,7 +198,7 @@ The race engine uses simulator units internally. `src/simulation/units.js` conve
|
|
|
194
198
|
|
|
195
199
|
The default `physicsMode` is `'arcade'`, which preserves the existing forgiving vehicle behavior for current browser hosts and demos.
|
|
196
200
|
|
|
197
|
-
`physicsMode: '
|
|
201
|
+
`physicsMode: 'advanced'` is opt-in for more believable F1-style limits. It still accepts only normal steering, throttle, brake, and pit-intent inputs, but the vehicle integrator keeps a real 2D world velocity, derives speed from that velocity, and applies a traction budget between longitudinal and lateral demand. Steering changes yaw through a rate-limited response, while body slip is derived from the difference between the car heading and actual velocity direction instead of being an artificial movement offset. External state placement through simulator APIs rejects non-finite numeric values and resynchronizes `velocityX/Y` whenever speed or heading is changed without an explicit velocity vector, so scenario/reset placement cannot leave stale advanced-mode velocity behind. Kerbs remain legal racing surface, but high-load kerb use is less stable and slower than track asphalt. Gravel and grass are averaged per wheel so one wheel over the edge creates partial grip/drag loss, while all-wheel runoff still costs major time. The rendered barrier wall is the hard outer runoff boundary in both physics modes; cars whose footprint reaches the wall's inner face are marked `destroyed`, stopped, removed from active collision and sensor participation, and placed in DNF timing order. Barrier destruction, stalled DNF, pre-start grid hold, red-flag hold, and pit-queue hold freeze scalar speed and advanced-mode velocity together; clearing a red flag resynchronizes active cars to the release speed and heading before the next observation while DNF/out-of-race cars remain frozen. `physicsMode` changes vehicle integration and grip behavior, not barrier consequences. Advanced mode does not teleport cars, magnetize them to the track, or bypass physics with hidden pose correction.
|
|
198
202
|
|
|
199
203
|
Simulator snapshots and environment observations expose additional vehicle telemetry: `lateralG`, `longitudinalG`, `gripUsage`, `slipAngleRadians`, `tractionLimited`, and `stabilityState`. These fields are optional public telemetry; existing `speedKph`, wheel-surface, steering, throttle, and brake fields remain unchanged.
|
|
200
204
|
|
|
@@ -274,13 +278,13 @@ Green-flag behavior uses:
|
|
|
274
278
|
|
|
275
279
|
The built-in racing controller still drives through normal vehicle inputs only. It chooses a forward racing target, blends steering from the target angle, upcoming track heading, lateral offset, edge recovery pressure, outward edge motion, and a corner-apex racing-line offset, then sends clamped steering/throttle/brake values into the vehicle physics integrator. It is expected to use the available track width and kerbs on generated circuits, including sharp generated turns. When heading alignment, steering load, or outward edge motion shows the car cannot yet carry throttle through a corner, throttle is cut before braking is requested so the controller does not fight itself. It does not snap car position, override heading, or bypass steering-rate/yaw-rate limits.
|
|
276
280
|
|
|
277
|
-
In
|
|
281
|
+
In advanced mode, the built-in controller uses separate tuning: earlier lookahead for braking, smaller high-speed steering requests, stronger edge recovery before the legal limit, throttle reduction when grip and slip telemetry show the car is overloaded, a higher legal-road speed floor so the baseline AI does not crawl through generated bends, and lower rejoin targets from kerb, grass, or gravel states. Extra stability braking is applied only when upcoming corner load, edge pressure, outward drift, high straight speed, or near-target speed makes it useful; ordinary below-target straight-line correction should not trigger brake pulses. This is intentionally a baseline driver, not a trained policy.
|
|
278
282
|
|
|
279
283
|
Traffic decisions use real meter-scaled gaps and lateral spacing, not raw simulator-size constants. A trailing car inside the attack window commits toward a visible passing lane instead of sitting on the centerline. A leading car with a close rear threat can defend the threatened side before the rival is fully alongside. Side-by-side overlap still creates spacing pressure so defensive and attacking moves are expressed as target-line choices and normal steering/brake/throttle inputs, not as collision avoidance teleports or artificial position overrides.
|
|
280
284
|
|
|
281
|
-
Rejoin behavior also remains physics-driven. When a car leaves the legal racing surface, the controller enters a short recovery hold and keeps using rejoin controls after the center point first reaches track or kerb until heading and edge margin are stable. That prevents the car from immediately returning to full racing throttle while still pointed across the track. Recovery targets look forward along the racing surface, bias inward from the edge, and request bounded surface-specific speeds for track, kerb, grass, and gravel states. In
|
|
285
|
+
Rejoin behavior also remains physics-driven. When a car leaves the legal racing surface, the controller enters a short recovery hold and keeps using rejoin controls after the center point first reaches track or kerb until heading and edge margin are stable. That prevents the car from immediately returning to full racing throttle while still pointed across the track. Recovery targets look forward along the racing surface, bias inward from the edge, and request bounded surface-specific speeds for track, kerb, grass, and gravel states. In advanced mode, rejoin controls also watch signed outward velocity relative to the track normal: a car sliding farther into runoff cuts throttle or brakes before trying to accelerate out, while very low-speed gravel/grass recovery limits steering lock and caps throttle when the heading is still outward so steering scrub does not pin the car in place. Barrier contact is not a recovery state; it destroys the car.
|
|
282
286
|
|
|
283
|
-
|
|
287
|
+
Advanced-mode steering scrub is dissipative in both forward and reverse motion. It opposes the signed forward velocity instead of always applying negative forward acceleration, so a car sliding backward with zero throttle cannot gain speed from steering scrub.
|
|
284
288
|
|
|
285
289
|
Tire energy can degrade to 1%. The vehicle physics layer converts tire energy into a nonlinear grip factor, so degradation has a visible performance cost across the full 100% to 1% range while still leaving a damaged car controllable enough to return to the pits.
|
|
286
290
|
|
|
@@ -319,7 +323,7 @@ Track state can classify a car as on:
|
|
|
319
323
|
|
|
320
324
|
Surface affects grip, drag, and rolling resistance.
|
|
321
325
|
|
|
322
|
-
Surface physics is wheel-based. `src/simulation/vehicle/wheelSurface.js` classifies each wheel contact patch against the track model and the car uses the worst current surface for physics: `barrier > gravel > grass > kerb > pit-box > pit-lane > pit-entry/pit-exit > track`. Normal main-track running uses an analytic signed-offset fast path from cached wheel geometry,
|
|
326
|
+
Surface physics is wheel-based. `src/simulation/vehicle/wheelSurface.js` classifies each wheel contact patch against the track model and the car uses the worst current surface for physics: `barrier > gravel > grass > kerb > pit-box > pit-lane > pit-entry/pit-exit > track`. Normal main-track running uses an analytic signed-offset fast path from cached wheel geometry, and pit connectors first resolve wheel centers through local connector geometry before falling back to full patch sampling only for genuinely ambiguous pit-road overlap. One wheel on gravel slows the car with gravel behavior even if the other three wheels are on asphalt. In addition, `vehiclePhysics.js` compares left-side and right-side wheel resistance and applies a small capped yaw-rate bias toward the slower side, so asymmetric gravel/grass/kerb contact can tug the car toward the dirty side without becoming a full tire-force simulation. One wheel on kerb reports `kerb` and applies kerb-level grip/drag without causing a track-limit violation by itself. If the car footprint reaches the rendered barrier wall's inner face, every physics mode marks the car destroyed instead of treating the wall as recoverable low-grip road. Pit entry, pit lane, pit exit, and pit-box surfaces remain legal drivable surfaces and do not count toward stalled off-track DNF timing.
|
|
323
327
|
|
|
324
328
|
Rendered material bands use those same thresholds. The visible gravel band starts at the outer kerb edge and ends at the simulated gravel edge; the visible runoff band starts at the gravel edge and ends at the barrier centerline; the barrier wall is drawn from the same `track.barrierWidth` used by destruction. Rendering, ray surface hits, wheel-surface classification, and barrier destruction must stay on this shared geometry contract.
|
|
325
329
|
|
package/docs/sense_contract.md
CHANGED
|
@@ -38,7 +38,7 @@ while the vector oracle uses schema names and object fields rather than calling
|
|
|
38
38
|
`rear-left`, `rear-right`.
|
|
39
39
|
- `nearbyCars` are sorted by car-relative distance, with deterministic ordering
|
|
40
40
|
for equal distances.
|
|
41
|
-
- Opponent radar fields use actual
|
|
41
|
+
- Opponent radar fields use actual advanced-mode velocity vectors when present.
|
|
42
42
|
- Ray object values, vector values, schemas, and visualizations must describe
|
|
43
43
|
the same active observation values. Debug-only precision may exist only when
|
|
44
44
|
clearly labeled and must not be presented as model input.
|
package/docs/system_specs.md
CHANGED
|
@@ -34,7 +34,17 @@ import {
|
|
|
34
34
|
} from '@inventure71/paddockjs/environment';
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
CSS-free data/helper import:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
import {
|
|
41
|
+
DriverData,
|
|
42
|
+
normalizeSimulatorDrivers,
|
|
43
|
+
createProceduralTrack,
|
|
44
|
+
} from '@inventure71/paddockjs/data';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The environment subpath is the only public headless training import. The data subpath is for host tooling and browser/server build code that needs data helpers without importing package CSS. Both subpaths must stay free of DOM, PixiJS, CSS, and browser app dependencies.
|
|
38
48
|
|
|
39
49
|
Root package exports:
|
|
40
50
|
|
|
@@ -43,9 +53,18 @@ Root package exports:
|
|
|
43
53
|
| Browser mounts | `mountF1Simulator`, `createPaddockSimulator` | all-in-one shell or composable package-owned UI surfaces |
|
|
44
54
|
| Composable helper mounts | `mountRaceControls`, `mountCameraControls`, `mountSafetyCarControl`, `mountTimingTower`, `mountRaceCanvas`, `mountTelemetryPanel`, `mountTelemetryCore`, `mountTelemetrySectors`, `mountTelemetrySectorBanner`, `mountTelemetryLapTimes`, `mountTelemetrySectorTimes`, `mountRaceTelemetryDrawer`, `mountCarDriverOverview`, `mountRaceDataPanel` | function-call wrappers around a composable simulator controller |
|
|
45
55
|
| Data helpers | `DriverData`, `VehicleData`, `CHAMPIONSHIP_ENTRY_BLUEPRINTS`, `DEMO_PROJECT_DRIVERS`, `buildChampionshipDriverGrid`, `formatDriverNumber`, `normalizeSimulatorDrivers` | demo data, host entry normalization, and rating helper construction |
|
|
56
|
+
| Theme helpers | `DEFAULT_PADDOCK_THEME`, `PADDOCK_THEME_CSS_VARIABLES`, `PADDOCK_THEME_TOKEN_KEYS`, `resolvePaddockTheme`, `applyPaddockTheme` | host-side theme preview/sync code without copying package token names |
|
|
46
57
|
| Package constants | `DEFAULT_F1_SIMULATOR_ASSETS`, `PADDOCK_SIMULATOR_PRESETS` | inspect bundled asset mapping and UI preset defaults |
|
|
47
58
|
| Track and units | `createProceduralTrack`, `REAL_F1_CAR_LENGTH_METERS`, `VISUAL_CAR_LENGTH_METERS`, `SIM_UNITS_PER_METER`, `TARGET_F1_TOP_SPEED_KPH`, `metersToSimUnits`, `simUnitsToMeters`, `kphToSimSpeed`, `simSpeedToKph`, `simSpeedToMetersPerSecond` | generated track definitions and physical/display unit conversion |
|
|
48
|
-
| Controller loop | `createPaddockDriverControllerLoop` |
|
|
59
|
+
| Controller loop | `createPaddockDriverControllerLoop` | compatibility browser export for shared model-controller orchestration; browser-free code should import it from `@inventure71/paddockjs/environment` |
|
|
60
|
+
|
|
61
|
+
Data subpath exports:
|
|
62
|
+
|
|
63
|
+
| Export | Use |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `DriverData`, `VehicleData`, `CHAMPIONSHIP_ENTRY_BLUEPRINTS`, `DEMO_PROJECT_DRIVERS`, `buildChampionshipDriverGrid`, `formatDriverNumber`, `normalizeSimulatorDrivers` | CSS-free host data normalization and rating helper construction |
|
|
66
|
+
| `createProceduralTrack` | CSS-free procedural track generation helper |
|
|
67
|
+
| `kphToSimSpeed`, `simSpeedToKph` | CSS-free speed conversion helpers |
|
|
49
68
|
|
|
50
69
|
Environment subpath exports:
|
|
51
70
|
|
|
@@ -60,10 +79,12 @@ Environment subpath exports:
|
|
|
60
79
|
| `createEnvironmentWorkerProtocol`, `handleEnvironmentMessage` | JSON-serializable worker bridge helpers |
|
|
61
80
|
|
|
62
81
|
`createProgressReward()` is published from the same subpath only as non-canonical demo reward code for examples and smoke tests. It must remain optional and replaceable; environment stepping must continue to work with a custom `reward(context)` callback or no reward callback. Reward callbacks are user-owned formulas over package-owned facts, including neutral per-driver `metrics` and `info.drivers[driverId]` episode state. They run only for `step(actions)` transition results; reset/read paths are reward-neutral. Non-finite or missing callback results are normalized to a neutral `0` reward for that controlled driver.
|
|
63
|
-
Ray observations expose track-transition distance and car distance per ray. Track transitions use `kind: 'exit'` when the ray starts on track and reaches the border, `kind: 'entry'` when the ray starts off track and reaches the road again, and `kind: null` with max distance when no transition is visible. Track-position, surface, pit-lane, and ray fallback queries
|
|
82
|
+
Ray observations expose track-transition distance and car distance per ray. Track transitions use `kind: 'exit'` when the ray starts on track and reaches the border, `kind: 'entry'` when the ray starts off track and reaches the road again, and `kind: null` with max distance when no transition is visible. Driver-precision road-edge, kerb, and illegal-surface channels use direct indexed ray-band intersections when the candidate boundary can be validated against driver track state, including validated off-track recovery boundaries. Barrier-origin recovery rays also stay on the direct indexed path when the tracer can already prove road-edge/kerb misses and the first non-barrier illegal-surface entry. Direct ray-band intersection consumes query-index segment IDs and typed centerline arrays instead of materializing temporary segment objects; the object-returning segment query helper remains for public/debug callers. Scratch-backed internal ray batches reuse the rich ray channel containers for road-edge, kerb, illegal-surface, and car hits, and car-hit footprint checks reuse the caller-owned ray vector plus a per-ray result target instead of recomputing ray geometry; no-scratch public ray calls still return fresh channel objects. Batch-training surface-only ray batches synthesize safe main-road origin state from same-pose `progress`/`signedOffset` geometry and reuse shared track-band boundary plus surface-hit containers, so kerb/illegal-surface channels stay indexed without nearest-track origin classification or per-ray hit-object replacement. Ambiguous recovery, pit-connector, and pit-lane-origin rays that are still not provable from the indexed bands are resolved by one bounded sampled recovery pass inside the ray-band tracer so all requested road/surface channels share the same sampled states. Debug precision uses the same tracer-owned path with additional sampled refinement for diagnostic distances; only missing-index cases use the older sampled fallback path. Track-position, surface, pit-lane, and ray fallback queries use an internal non-enumerable track query index built with the track model. Pit-road state classification reuses indexed route candidate buckets, one polyline projection result, and precomputed cumulative route distance tables for entry, fast-lane, working-lane, and exit roads. Connector-adjacent direct pit-entry/pit-exit override checks reuse that same projection target and route metadata; endpoint-progress shortcuts use precomputed endpoint segment windows, and mid-connector points use bounded indexed route candidates without becoming full pit-lane queries. The runtime track-query benchmark explicitly requires those pit-road probes to stay indexed with reused direct connector scratch, positive endpoint-window connector projections, zero connector full-route projection scans, reused route scratch, precomputed distances, zero cumulative-distance rebuilds, and zero pit fallbacks. Browser/expert mounts, headless environments, direct race-simulation construction, and compact training-style environment runs all use the indexed path. This changes runtime cost, not public observation shapes.
|
|
83
|
+
|
|
84
|
+
Scratch-backed internal ray batches also keep requested-channel dispatch in reusable scalar channel flags owned by the shared ray query. The runtime benchmark contract fails if the common direct sensor-ray path allocates channel `Set` containers or replaces those channel-flag containers between batches.
|
|
64
85
|
The package must not own model training, model persistence, model registries, or trained policy behavior. The supported contract is: external code reads observations, returns normalized actions, and advances either the headless environment or browser expert mode.
|
|
65
86
|
The shared expert runtime exposes `getActionSpec()` and `getObservationSpec()` so external code can inspect controlled drivers, action ranges, sensor layout, nearby-car limits, track lookahead fields, and the versioned vector schema before connecting a model. Environment reset may apply deterministic scenario placement presets or explicit placement/traffic layouts; model steps may not mutate position and still use normalized actions only. Neutral rollout recording, deterministic evaluation metrics, and the JSON worker protocol are environment utilities, not training algorithms.
|
|
66
|
-
`createPaddockDriverControllerLoop({ runtime, controller, actionRepeat })` is the shared public controller orchestrator for browser playback and JavaScript training-style loops. `runtime` may be browser expert mode or a headless environment, and `controller.decideBatch(context)` is called once per policy step for all controlled drivers in stable spec order. The loop lazily resets when scheduled playback starts from a fresh instance, caches `actionSpec` and `observationSpec`, reuses the active observation payload, supports async controllers, forwards actions through the normal `step(actions)` API, and repeats each decision for `actionRepeat` runtime frames. Scheduled playback stops and records `stats.lastError` when controller initialization, decision, stepping, or `onStep` hooks fail. It does not load checkpoints, run neural networks, parse model formats, compute rewards, or mutate vehicle state.
|
|
87
|
+
`createPaddockDriverControllerLoop({ runtime, controller, actionRepeat })` is the shared public controller orchestrator for browser playback and JavaScript training-style loops. `runtime` may be browser expert mode or a headless environment, and `controller.decideBatch(context)` is called once per policy step for all controlled drivers in stable spec order. The loop lazily resets when scheduled playback starts from a fresh instance, caches `actionSpec` and `observationSpec`, reuses the active observation payload, supports async controllers, forwards actions through the normal `step(actions)` API, and repeats each decision for `actionRepeat` runtime frames. Scheduled playback stops and records `stats.lastError` when controller initialization, decision, stepping, or `onStep` hooks fail. `stop()` cancels pending package-owned scheduler handles and custom scheduler handles that either expose `cancel()` or return browser-style numeric timeout/animation-frame ids. It does not load checkpoints, run neural networks, parse model formats, compute rewards, or mutate vehicle state.
|
|
67
88
|
|
|
68
89
|
All-in-one mount call:
|
|
69
90
|
|
|
@@ -75,7 +96,6 @@ const simulator = await mountF1Simulator(root, {
|
|
|
75
96
|
seed,
|
|
76
97
|
trackSeed,
|
|
77
98
|
trackGeneration,
|
|
78
|
-
trackQueryIndex,
|
|
79
99
|
warmup,
|
|
80
100
|
totalLaps,
|
|
81
101
|
physicsMode,
|
|
@@ -104,6 +124,8 @@ const simulator = await mountF1Simulator(root, {
|
|
|
104
124
|
});
|
|
105
125
|
```
|
|
106
126
|
|
|
127
|
+
The all-in-one shell normalizes `backLinkHref` before rendering it. Package-owned race controls accept relative URLs, hash URLs, and absolute `http:` / `https:` URLs; unsafe or malformed values fall back to the default `projects.html` link instead of being rendered into the anchor. Runtime readouts also validate snapshot-derived color values before writing CSS custom properties, and package-owned asset URLs are escaped before becoming CSS `url(...)` values.
|
|
128
|
+
|
|
107
129
|
Composable mount call:
|
|
108
130
|
|
|
109
131
|
```js
|
|
@@ -114,7 +136,6 @@ const simulator = createPaddockSimulator({
|
|
|
114
136
|
seed,
|
|
115
137
|
trackSeed,
|
|
116
138
|
trackGeneration,
|
|
117
|
-
trackQueryIndex,
|
|
118
139
|
warmup,
|
|
119
140
|
totalLaps,
|
|
120
141
|
physicsMode,
|
|
@@ -161,7 +182,7 @@ simulator.setRedFlagDeployed(true);
|
|
|
161
182
|
|
|
162
183
|
Pit APIs are rule-gated. `setPitIntent()` returns `false` until the active rules enable `rules.modules.pitStops.enabled` and the active track has pit-lane geometry. `tireStrategy` does not enable automatic pit routing by itself; it only controls available compounds, tire requirement stewarding, and pit target choices.
|
|
163
184
|
|
|
164
|
-
Standalone helper functions are also exported for host code that
|
|
185
|
+
The controller methods above are the canonical composable API. They keep component mounting, lifecycle, restart, theme syncing, and runtime state on one controller object. Standalone helper functions are also exported as thin wrappers for host code that already uses function-call style:
|
|
165
186
|
|
|
166
187
|
```js
|
|
167
188
|
mountRaceControls(root, simulator);
|
|
@@ -212,6 +233,10 @@ Returned controller:
|
|
|
212
233
|
// Included on both APIs:
|
|
213
234
|
destroy(),
|
|
214
235
|
restart(nextOptions),
|
|
236
|
+
setTheme(theme),
|
|
237
|
+
setThemeMode(mode),
|
|
238
|
+
getTheme(),
|
|
239
|
+
syncThemeFrom(element, { attribute, map }),
|
|
215
240
|
selectDriver(driverId),
|
|
216
241
|
setSafetyCarDeployed(deployed),
|
|
217
242
|
setRedFlagDeployed(deployed),
|
|
@@ -237,32 +262,39 @@ Returned controller:
|
|
|
237
262
|
- Composable mounting can also place camera controls and a safety-car button into separate host roots while keeping package-owned markup.
|
|
238
263
|
- Composable mounting can embed the timing tower directly inside the race canvas with `mountRaceCanvas(root, { includeTimingTower: true })`.
|
|
239
264
|
- The race canvas is required before `start()` because PixiJS needs a canvas host.
|
|
265
|
+
- Mount component roots before calling `start()`. A pre-start `restart(nextOptions)` re-resolves options and refreshes already mounted component markup, so non-asset changes such as title, kicker, UI visibility, theme, drivers, entries, seed, or track seed are reflected before `start()` binds the runtime.
|
|
240
266
|
- Timing tower, telemetry, controls, and race-data panels are optional from a runtime safety perspective; omitted panels simply do not render their readouts.
|
|
241
|
-
- Timing tower entries are
|
|
242
|
-
- Timing tower entries display team icons.
|
|
267
|
+
- Timing tower entries are compact content-sized rows stacked from the top of the timing list. Row vertical position must be based on rank/index, never distributed by available height or total entry count. `ui.timingEntryVerticalPadding` defaults to `5` and applies that pixel value above and below every row entry.
|
|
268
|
+
- Timing tower entries display team icons. `ui.timingGapMode` sets the initial display to interval-to-car-ahead timing or direct gap-to-leader timing, and the controller can change it at runtime with `setTimingGapMode()`, `getTimingGapMode()`, or `toggleTimingGapMode()`. The tower shows one compact `Int`/`Gap` header toggle by default. That toggle must stay aligned with the other timing header labels, preserve a practical `44px` target, draw active/focus treatment inside the timing header rather than outside the column, and never change the timing board width. `ui.timingGapModeToggle: false` hides only the manual header affordance; it must not disable the controller API. Seconds gaps are calculated from hidden timing-line crossings, and whole-lap deficits display as `+N`.
|
|
243
269
|
- Snapshots expose calibrated display units through `speedKph`, `distanceMeters`, and `gapMeters`; internal physics remains in simulator units. Public timer, lap-time, sector-time, gap-time, penalty-time, and service-countdown values are seconds.
|
|
244
270
|
- Snapshots expose automatic three-sector track metadata through `track.sectors` and per-car lap/sector timing through `car.lapTelemetry`, including completed current-lap sector splits before the active sector, live active-sector elapsed time, per-sector progress, and sector performance classes for overall-best, personal-best, and slower completed sector times. Future-sector timing/progress entries are cleared so banner and sidebar telemetry cannot display stale split values from another lap or state.
|
|
245
271
|
- Entries can include optional `team` metadata. Team color defaults to car color when omitted.
|
|
246
272
|
- Mounted package surfaces show a package-owned red start-light loading overlay until `start()` finishes PixiJS, asset, control, and initial readout initialization.
|
|
247
273
|
- `preset` is a preset-first API. Presets are resolved before explicit host overrides so hosts can use `dashboard`, `timing-overlay`, `compact-race`, or `full-dashboard` as a starting point and still override specific `ui` or `theme` fields.
|
|
248
|
-
- `theme` is the public sizing/color contract. It
|
|
249
|
-
- `
|
|
250
|
-
-
|
|
274
|
+
- `theme` is the public sizing/color contract. It resolves semantic package-owned tokens into complete light/dark themes, supports named derivative theme packages through `theme.themes`, selects the active package with `theme.use`, applies component-specific packages through `theme.componentThemes`, and accepts team selectors through `theme.teamThemes` or `entries[*].team.theme`. Component theme keys accept package component ids such as `timing-tower` and camelCase aliases such as `timingTower`; `selectedDriverPanel` targets both selected-driver package surfaces. Selected-driver surfaces use the selected team's theme by default when one is available while structural controls keep the active global theme unless explicitly overridden. Unknown theme tokens or component slots are ignored, one-sided light/dark token overrides generate and cache the opposite mode, and theme plus driver/team colors are validated before they are written to CSS variables. Legacy aliases such as `accentColor`, `greenColor`, and `yellowColor` remain migration aliases for the new semantic tokens.
|
|
275
|
+
- `setTheme(theme)`, `setThemeMode(mode)`, and `syncThemeFrom(element, options)` are runtime presentation APIs. They must resolve through the same theme normalizer used at mount time, reapply CSS variables to every package-owned mounted root, and must not call `restart()` or replace race state. Existing simulation state, timing order, selected driver, active penalties, camera state, speed setting, and pit intent state remain owned by the running app. `syncThemeFrom()` applies immediately, watches the configured attribute with `MutationObserver` when available, maps host attribute values through `options.map`, and returns a cleanup function.
|
|
276
|
+
- `initialCameraMode` accepts `'overview'`, `'leader'`, `'selected'`, `'driver'`, `'show-all'`, or `'pit'`; invalid values fall back to `'leader'`. The overview camera frames the active generated track bounds with package-owned padding and pit-lane extent. The driver camera follows the selected car from a lower screen anchor and rotates the world so the selected car points upward; its control is opt-in through `ui.driverCamera: true`, and `initialCameraMode: 'driver'` enables that control automatically. The pit camera frames the operational `track.pitLane` lane, boxes, service areas, and queue areas instead of the longer entry/exit access roads, zooms out when needed to keep that pit-lane work area inside the active race-view safe area, and its control is hidden/disabled if the active track has no pit lane. Zoom buttons and wheel zoom work in every camera mode, including overview, show-all, driver, and pit, but cannot zoom farther out than the active track frame.
|
|
277
|
+
- Camera controls default to external placement so they do not cover the race view. They can still be embedded in the race canvas by setting `ui.cameraControls: 'embedded'`, externally mounted, or omitted with `false`. Generated camera controls include a `Mute banners` toggle that is off by default and temporarily suppresses project/radio lower-thirds while active. The driver camera button is omitted unless `ui.driverCamera` is active.
|
|
251
278
|
- Telemetry surfaces are detached package components: core scalar readouts, sector graph, broadcast sector banner, lap-time table, and sector-time table. The broadcast sector banner shows the selected car identity, uses the selected car color for its frame/label, and keeps sector performance colors inside the sector bars. It is an explicitly mounted independent surface, not the default telemetry-drawer lower-third. `mountTelemetryPanel()` is the stack template around those detached pieces, owns vertical scrolling when constrained, and `ui.telemetryModules` controls which pieces appear in stack/drawer templates.
|
|
252
|
-
- `mountRaceTelemetryDrawer()` creates a package-owned race workbench: external top controls, race canvas, embedded timing tower, lower-third banner, top steward message, safety-car control, and a right-side telemetry drawer. The top row holds camera controls, banner mute, safety car, and telemetry toggle outside the canvas. Pass `{ raceDataTelemetryDetail: true }` when the drawer lower-third should include compact project telemetry detail. The drawer opens smoothly, takes width from the race view instead of overlaying it, is inert/hidden to interaction when closed, and embeds the same `telemetry-stack` component used by standalone telemetry panels.
|
|
279
|
+
- `mountRaceTelemetryDrawer()` creates a package-owned race workbench: external top controls, race canvas, embedded timing tower, lower-third banner, top steward message, safety-car control, and a right-side telemetry drawer. The top row holds camera controls, banner mute, safety car, and telemetry toggle outside the canvas. On narrow responsive containers, that toolbar stacks into readable full-width rows and keeps safety car plus telemetry as a two-column action row instead of squeezing them beside the camera controls. Pass `{ raceDataTelemetryDetail: true }` when the drawer lower-third should include compact project telemetry detail. The drawer opens smoothly, takes width from the race view instead of overlaying it, is inert/hidden to interaction when closed, and embeds the same `telemetry-stack` component used by standalone telemetry panels.
|
|
253
280
|
- The FPS readout can be shown or hidden with `ui.showFps`.
|
|
254
281
|
- `ui.layoutPreset: 'left-tower-overlay'` is a package-owned preset that creates a left broadcast gutter inside the race view, places the timing tower there at the same width as the default timing-board column, frames the PixiJS camera around the remaining usable race area, and keeps camera controls and start lights out of the tower area. In the combined shell, project and radio lower-thirds stay inside the race window while being allowed to cover the timing sidebar.
|
|
255
282
|
- `ui.raceDataBanners.initial` controls the starting lower-third (`'project'`, `'radio'`, or `'hidden'`), and `ui.raceDataBanners.enabled` controls which lower-third types can appear. The runtime banner mute state temporarily disables project/radio lower-thirds without changing these configured allow-lists.
|
|
256
283
|
- Project and radio lower-third pills include a package-owned top-right close button that dismisses the current pill early. The next pill appears only through normal driver selection or radio scheduling.
|
|
284
|
+
- When a composable host mounts more than one package-owned race-data panel, such as an embedded race-canvas lower-third plus `mountRaceDataPanel()`, the panels share the same runtime project/radio/hide state and each panel's open/dismiss controls remain wired.
|
|
257
285
|
- `ui.raceDataBannerSize` controls lower-third sizing: `'custom'` keeps the default CSS-variable-driven banner size for host tuning, while `'auto'` uses the race space to the right of the timing board when wide enough and overlaps the timing board only when space is constrained.
|
|
258
286
|
- `ui.raceDataTelemetryDetail` adds compact S1/S2/S3 sector detail to the project lower-third while keeping radio mode unchanged.
|
|
259
287
|
- `ui.timingTowerVerticalFit` controls vertical tower behavior in the combined overlay preset: `'expand-race-view'` grows the race window to fit the tower, while `'scroll'` crops the tower area and scrolls timing rows inside it. The same values are accepted as `mountRaceCanvas()` options when `includeTimingTower` embeds the tower in the race canvas.
|
|
260
|
-
-
|
|
288
|
+
- `ui.timingGapMode` controls whether the timing tower starts in `'interval'` or `'leader'` mode. `ui.timingGapModeToggle` defaults to `true`; setting it to `false` hides the manual header toggle while preserving runtime control through the mounted simulator/controller methods. Hosts that need a different control placement should hide the package toggle and call those methods from host-owned UI instead of restyling package internals.
|
|
289
|
+
- Hosts may scale the whole mounted simulator through the container. The horizontal proportions inside package-owned presets are not public API and should not be configurable through raw width or ratio options. The supported inline-size envelope starts at `320px` for the package-owned mount root or individual composable surface. Hosts should provide responsive outer containers with `min-width: 0` and avoid targeting package internals; PaddockJS owns timing-board width, camera safe-area math, lower-third containment, drawer sizing, and internal density tiers. The camera reads the current canvas dimensions so wider or taller host windows reveal more of the race view without needing host-owned camera math, and the renderer keeps grass coverage beyond the simulated world so deep zoom-out does not show host background. The timing tower has a package-owned max width because overly wide timing boards degrade readability; standalone hosts can constrain vertical height through the mount container and let the timing entries scroll internally. Mobile and narrow embeds are handled by package CSS plus a package-owned resize observer: embedded timing towers become left reveal panels when they no longer work as side gutters, camera controls stay in external control rows when mounted, narrow telemetry drawers reveal over the race view instead of forcing permanent canvas shrinkage, and closed reveal panels stay inert/hidden while the renderer and camera resize continuously without simulator remounts. A narrow lower-third may cover empty timing-tower chrome, but if the measured lower-third would cover any timing entry, PaddockJS adds measured lower-third clearance to the race view's minimum height; when timing entries remain clear, the height remains governed by the timing tower/race-view minimum. Template responsive narrow variants are enabled by default through `ui.responsiveNarrowLayout`; hosts can set `ui.responsiveNarrowLayout: false`, or pass `responsiveNarrowLayout: false` to composable race-canvas/drawer mounts, to opt out.
|
|
290
|
+
- Package layout density tiers are container-owned: `320px`-`519px` is mobile density, `520px`-`759px` is narrow tablet density, `760px`-`1119px` is tablet density, and `1120px` and wider is desktop density. Component behavior follows the actual package container width, not only the browser viewport, so embedded timing towers and composable pieces remain safe inside split tablet layouts.
|
|
291
|
+
- If the shell/mount root is narrower than `320px`, or if an individual package component is constrained below its component-specific inline/block minimum, the component sets `data-paddock-size-unsupported="true"` and displays a package-owned accessible unsupported-size placeholder instead of broken UI. The broadcast timing tower is one intentional exception to a blanket `320px` subcomponent rule: it remains valid at its package-owned narrow timing-board width. The placeholder uses `role="status"` and `aria-live="polite"`. Hosts should fix the outer container size rather than hiding package content or patching package internals.
|
|
292
|
+
- Package controls, drawer toggles, lower-third actions, and dismiss buttons maintain practical `44px` touch targets, shared visible focus states, and `touch-action: manipulation`. Timing rows are compact selectable broadcast entries controlled by `ui.timingEntryVerticalPadding`. Touch driving or virtual joystick controls are intentionally out of scope.
|
|
261
293
|
- The host does not need to provide simulator assets.
|
|
262
294
|
- The host passes data, not internal DOM.
|
|
263
295
|
- Host driver IDs and entry `driverId` values must be unique. Entries may omit `driverNumber`; provided numbers must be unique.
|
|
264
296
|
- `totalLaps` is normalized to a finite positive integer before simulation so invalid input cannot produce zero-lap, negative-lap, or non-finite snapshots.
|
|
265
|
-
- `physicsMode` accepts `'arcade'` and `'
|
|
297
|
+
- `physicsMode` accepts `'arcade'` and `'advanced'`. The default is `'arcade'` to preserve existing hosts. `'advanced'` is opt-in and keeps cars controlled only through steering, throttle, brake, and pit intent while enabling traction-budget limits, steering scrub, velocity-heading slip, surface-specific grip/drag, and advanced telemetry. The old `'simulator'` mode name is removed and falls back to `'arcade'` like any other invalid value. Snapshots expose `physicsMode` plus per-car `lateralG`, `longitudinalG`, `gripUsage`, `slipAngleRadians`, `tractionLimited`, `stabilityState`, and the latest `appliedControls`.
|
|
266
298
|
- `restart(nextOptions)` can change race data and deterministic seeds such as `trackSeed`, but it does not support changing asset URLs. Asset changes require `destroy()` and a fresh mount because PixiJS texture loading is an initialization boundary.
|
|
267
299
|
- `onDriverOpen(driver)` is the navigation boundary.
|
|
268
300
|
- Lifecycle callbacks are optional: `onLoadingChange`, `onReady`, `onError`, `onDriverSelect`, `onRaceEvent`, `onLapChange`, and `onRaceFinish`. Lifecycle callback failures are routed to `onError` when possible and must not stop the simulator loop. `onDriverOpen(driver)` is host-owned navigation code and is not wrapped as a lifecycle callback.
|
|
@@ -272,24 +304,27 @@ Returned controller:
|
|
|
272
304
|
- The browser UI can opt into top steward messages with `ui.penaltyBanners` and timing-row penalty badges with `ui.timingPenaltyBadges`. Steward messages render track-limit warning events and penalty decisions from the simulation; time-penalty messages put the penalty seconds in a large left chip and use penalty-colored backgrounds, while warnings use warning-colored backgrounds. Timing-row `!` badges are rendered only from `snapshot.penalties`; warning events do not count. UI code must not recalculate steward decisions.
|
|
273
305
|
- The simulator must stay interactive after being installed through `npm install @inventure71/paddockjs`.
|
|
274
306
|
- The package must build correctly through a browser bundler that supports JavaScript modules, CSS imports, and image imports.
|
|
307
|
+
- Package CSS must be scoped to package mount roots/components and must not style same-named host elements outside `.f1-sim-component` roots. Generic simulator classes such as `.sim-control`, `.camera-controls`, `.timing-list`, `.telemetry-header`, and `.start-lights` are package internals, not host styling hooks. The stylesheet uses system font stacks by default and must not force remote font requests; hosts that want custom typography should load those fonts themselves and pass theme/CSS-variable overrides.
|
|
275
308
|
- The simulation should remain deterministic for the same seed, track seed, drivers, entries, and rules.
|
|
276
309
|
- Warmup is enabled by default for browser mounts, headless environments, and direct simulation creation. It runs on a disposable runtime and must not mutate the visible initial race state; under the default `warmup.policy: 'config-change'`, identical configuration fingerprints reuse cached warmup while seed/config changes rerun warmup automatically.
|
|
277
310
|
- When `trackSeed` is omitted in a browser mount, the simulator creates a fresh procedural circuit for that mount. Explicit `trackSeed` values are deterministic and cached by seed plus resolved generation options for repeated mounts. Cached procedural definitions are immutable; callers that import `createProceduralTrack(seed, options)` should treat the returned definition as read-only and clone it before custom mutation. `trackGeneration` forwards the same procedural options used by `createProceduralTrack(seed, options)`: `profile`, `length`, `startStraight`, `pitLane`, `shape`, `validation`, and `attempts`. The `race` profile preserves the default full circuit with pit lane; `training-short`, `training-medium`, and `training-technical` are smaller pitless presets intended for training or demos. Explicit option fields override profile defaults after the profile is resolved. Procedural generation traces seeded connected region boundaries, smooths and warps them into centerline controls, then rejects circuits with excessive local heading jumps, turn accumulation, self-intersections, poor clearance, invalid length, or weak shape variation.
|
|
278
311
|
- The renderer should target a paced 60 FPS simulation/render loop.
|
|
279
|
-
- The render loop should pause while the race canvas is offscreen or the document is hidden, then resume without catching up the elapsed hidden time. Layout measurements needed for overlay camera safe areas should be cached between resize/layout invalidations. Runtime DOM updates should skip unchanged text/markup so visible embeds do not rewrite stable readouts every frame.
|
|
312
|
+
- The render loop should pause while the race canvas is offscreen or the document is hidden, then resume without catching up the elapsed hidden time. Runtime visibility must not rely on a single observer state: it should resync from canvas geometry after observer, scroll, resize, focus, and page-show signals, and ticker sync should reconcile the intended runtime state with PixiJS' actual started/stopped state. Layout measurements needed for overlay camera safe areas should be cached between resize/layout invalidations. Runtime DOM updates should skip unchanged text/markup so visible embeds do not rewrite stable readouts every frame.
|
|
280
313
|
- Restart and rerender paths must destroy replaced PixiJS display children while preserving shared loaded textures.
|
|
281
314
|
- Restart supports race/data/seed changes but does not support asset URL changes or browser expert mode changes. Texture loading and ticker ownership are mount-time boundaries; hosts must destroy and mount again to change either boundary.
|
|
282
315
|
- Expert environment code can create a headless `createPaddockEnvironment()` from the `@inventure71/paddockjs/environment` subpath. It requires explicit `controlledDrivers`, accepts normalized actions `{ steering, throttle, brake }` plus optional `pitIntent` and `pitCompound`, advances only through `step(actions)`, and returns environment-loop JavaScript results with `observation`, `reward`, `metrics`, `terminated`, `truncated`, `done`, `events`, `state`, and `info`. `steering` is an absolute normalized steering target: `-1` points at maximum left, `0` points at center, `1` points at maximum right, and intermediate values are percentages of the maximum steering angle; the vehicle integrator rate-limits motion toward that target instead of snapping the wheel. `steering`, `throttle`, and `brake` are required for each controlled-driver action. Strict action policy throws on missing or non-finite vehicle controls, while report policy records the error and releases stale manual controls so the built-in driver owns that physics step. `info.drivers[driverId]` exposes per-driver `terminated`, `truncated`, `endReason`, `episodeStep`, and `episodeId` for batched loops; selected-driver resets clear only selected driver truncation/termination and do not rewind global `info.step`. For scoped reset results, `info.controlledDrivers` matches the drivers present in `observation` and `metrics`, while `info.drivers` preserves episode state for all configured controlled drivers. Controlled drivers do not receive tire-threshold automatic pit calls; they request automatic pit service through `pitIntent`, may choose the target tire through `pitCompound`, and observe pit-lane/service/race-control state in `observation[driverId].object.self` and `.race`. `pitIntent: 0` is a no-op clear request and does not fail when pit stops are disabled. `self.onTrack` follows wheel-level legality, so track, kerb, and legal pit-lane/box surfaces are on-track while gravel/grass/barrier are off-track. Barrier-wall contact in both physics modes is measured against the rendered wall's inner face; contact marks the car destroyed/DNF, removes it from active collision/sensor participation, emits a `car-destroyed` event, and terminates that driver's episode with `endReason: 'destroyed'`. Opt-in stalled off-track DNF removes the car from the same active participation systems while keeping `destroyed: false` and terminates controlled-driver episodes with `endReason: 'stalled-off-track'` when `rules.modules.stalledDnf.enabled` is `true`. Reset is still an episode-boundary `resetDrivers()` call. Environment scenarios may set reset positions with `preset`, `placements`, and relative `traffic`; `resetDrivers(placements)` may reset selected controlled drivers between episodes without recreating the whole simulation. Partial `reset(options)` calls preserve omitted nested option groups while replacing arrays and explicit scenario placement maps. After placement, selected cars are classified by the same runoff/barrier rules before observations are returned, so cars already inside terminal barrier space report destroyed metrics and stable miss-valued rays instead of doing alive-car far-out ray scans. Both scenario placement and `resetDrivers()` are setup data, not policy actions. `participantInteractions` may make real physics cars non-colliding, sensor-hidden, non-blocking for pit occupancy, or excluded from race order, but those cars still live in `snapshot.cars` and move only through normal physics/control APIs. The `batch-training` profile is non-colliding, sensor-hidden, pit-non-blocking, excluded from race order, and still rendered. `replayGhosts` are separate trajectory-driven entities in `snapshot.replayGhosts`; they are visual/reference overlays and never enter car physics, timing, pit, order, or penalty systems. Replay ghosts are sensor-hidden unless their own sensor flags explicitly opt them into ray or nearby observations.
|
|
283
316
|
- Headless environments also accept an optional `externalRenderer` observer hook. It receives `{ snapshot, observation, meta }` on `reset`, `step`, and `resetDrivers`, so external tooling can stream authoritative frames to visualizers without letting that transport mutate simulation state.
|
|
284
317
|
- Controller loop code should use `createPaddockDriverControllerLoop()` instead of duplicating browser/headless stepping. Its controller context groups observations by controlled driver id, includes `orderedObservations` for direct batch tensor assembly, exposes metrics, events, cached specs, and reset-driver ids, and keeps per-driver resets as controller reset hooks when the runtime supports `resetDrivers()`. `previousActions` is the last action map actually applied before the current `actions`; on the first controlled frame after reset it is empty, and after repeated frames it reflects the prior physics step rather than pre-copying the current decision.
|
|
285
|
-
- Expert ray sensors originate from the controlled car center. The default compact set is `[-135, -60, -20, 0, 20, 60, 135, 180]`, giving forward, side, and rear awareness while staying small. Rays detect track edges against the actual track geometry and detect car hits by ray-to-car-footprint intersection. Hosts may opt into per-ray lengths, predefined layouts such as `driver-front-heavy`, and surface-aware channels for kerb and illegal surface detection; those channels are computed only when requested.
|
|
318
|
+
- Expert ray sensors originate from the controlled car center. The default compact set is `[-135, -60, -20, 0, 20, 60, 135, 180]`, giving forward, side, and rear awareness while staying small. Rays detect track edges against the actual track geometry and detect car hits by ray-to-car-footprint intersection; scratch-backed car-hit checks use the same caller-owned ray vector as the road/surface tracer and scalar local footprint math. Hosts may opt into per-ray lengths, predefined layouts such as `driver-front-heavy`, and surface-aware channels for kerb and illegal surface detection; those channels are computed only when requested. `precision: 'driver'` road-edge, kerb, and illegal-surface channels use validated direct indexed ray-band intersections for safe normal-driver main-track geometry and validated off-track recovery boundaries; the `batch-training` profile keeps the accelerated direct indexed contract for safe main-track rays, including surface-only batches that reuse shared surface-hit and track-band boundary scratch. Ambiguous recovery, pit-connector, and pit-lane-origin rays use bounded sampled validation inside the ray-band tracer, and `precision: 'debug'` uses that same tracer path with extra sampled refinement for diagnostics. Only missing-index cases use the older sampled fallback path. This preserves model-facing distances without substituting debug-only refinement. Barrier walls are rendered/physical terminal boundaries, not model-facing ray targets; active ray objects, vectors, schemas, and visualizations must not expose a `barrier` ray channel. Ray `precision` defaults to `driver`, which is the active model-facing sensor contract; `debug` precision is only for explicitly labeled diagnostics. `observation.profile: 'physical-driver'` exposes local driver-like yaw, contact-patch, boundary, richer ray, and opponent-radar senses while omitting default lookahead samples unless explicitly configured. `observation.output` can be `full`, `vector`, or `object`, `includeSchema: false` omits repeated schema payloads, and `vectorType: 'float32'` returns typed vector buffers for high-throughput JavaScript loops. `result.stateOutput` can be `full`, `minimal`, or `none`; `none` returns `state: null` and is intended for loops that rely on observations, metrics, and info instead of full snapshots.
|
|
319
|
+
- Scratch-backed expert ray batches dispatch those requested channels through reusable scalar flags on the shared ray query. This is an internal runtime contract only; public ray objects still expose the same road-edge, kerb, illegal-surface, and car channel objects.
|
|
286
320
|
- Track surface bands are single-source geometry. Rendering derives visible gravel, runoff, and barrier offsets from the same track widths used by wheel surfaces and ray surface hits: road edge, kerb outer edge, gravel outer edge, runoff outer edge, and barrier wall width. New code must not add independent magic offsets for these bands.
|
|
287
|
-
- Browser expert mode is opt-in with `expert: { enabled: true, controlledDrivers, frameSkip }`. When enabled, the returned controller exposes `expert.reset()`, `expert.step(actions)`, `expert.getObservation()`, and `expert.getState()`.
|
|
321
|
+
- Browser expert mode is opt-in with `expert: { enabled: true, controlledDrivers, frameSkip }`. It shares the environment episode lifecycle. The default episode horizon is a high safety cap (`1_000_000` expert steps, roughly 4.6 hours at 60Hz with `frameSkip: 1`) so normal browser sessions should end by race finish instead of max-step truncation. Bounded training, evaluation, and smoke loops should pass an explicit smaller `episode.maxSteps`; `episode.endOnRaceFinish` controls whether race finish is terminal. When enabled, the returned controller exposes `expert.reset()`, `expert.step(actions)`, `expert.getObservation()`, and `expert.getState()`.
|
|
288
322
|
- Browser expert mode also exposes `attachExternalRenderer(source)`, `detachExternalRenderer()`, and `getExternalRendererState()` for render-only external-frame playback. The `source` callback contract is `subscribe(onFrame) => unsubscribe`, where each frame is `{ snapshot, observation, meta? }`.
|
|
289
323
|
- Browser expert mode wraps the same `RaceSimulation` instance that the visual canvas renders. It must not create a parallel simulation for the same mount.
|
|
290
324
|
- Browser expert mode disables automatic ticker-driven simulation advancement. The visual canvas updates only after explicit expert `reset()` or `step(actions)` calls.
|
|
291
325
|
- While external renderer mode is attached, browser expert runtime is strict render-only and rejects local `step()`, `resetDrivers()`, and `reset()`. Hosts must detach before returning to local stepping; detach restores the current local simulation track surface if external frames had redrawn the shared track asset.
|
|
292
326
|
- Policy Runner `Live preview stream` mode accepts `ws://`/`wss://` push streams and `http://`/`https://` polling endpoints that return `preview:snapshot` frames.
|
|
327
|
+
- Policy Runner `Policy server` mode uses compact HTTP protocol version `2`. Reset/init requests include static specs, configuration, `previousActionFields`, and `metricFields`; per-decision `/policy/decide-batch` requests include `driverIds` plus aligned compact `vectors`, `previousActions`, `metrics`, and `events` arrays only. There is no rich per-decision compatibility toggle in the local-preview policy-server controller; server integrations must read vector inputs by matching `driverIds[index]` to `vectors[index]` and cache the tuple field order received on reset. Full public snapshots remain available through explicit host/debug/export/state APIs, not as the policy-server transport default.
|
|
293
328
|
- Browser expert mode may opt into `expert.visualizeSensors: true` or `expert.visualizeSensors: { rays: true }`. When enabled, ray sensors render in the race canvas world layer from the selected controlled car by default, using the same observation result produced by explicit expert steps. The overlay draws a separate colored marker for each detected active channel on each ray, including road-edge, kerb, illegal surface, and car hits. Barrier walls are visible track geometry, not ray-hit markers, and browser components must not reintroduce a barrier ray label. Hosts may request `visualizeSensors: { rays: true, drivers: 'all' }` for the heavier all-controlled-car overlay or pass an explicit driver id list. Every model-facing sense follows this rule: the model receives the active environment observation, and Policy Runner/expert visualization displays that same observation instead of recomputing a more precise or different browser-only value. Extra diagnostics must be clearly separated from model senses.
|
|
294
329
|
|
|
295
330
|
## Current Visible Features
|
|
@@ -298,8 +333,8 @@ Returned controller:
|
|
|
298
333
|
- Race canvas rendered with PixiJS.
|
|
299
334
|
- Procedural track rendering with asphalt texture and DRS overlays.
|
|
300
335
|
- Driver selection from cars and timing tower rows.
|
|
301
|
-
- Camera modes: overview, leader, selected, show all, and pit.
|
|
302
|
-
Overview frames the active generated track bounds with package-owned padding and pit-lane extent; show all dynamically fits the active pack; pit frames the operational pit-lane work area when the track has one.
|
|
336
|
+
- Camera modes: overview, leader, selected, driver, show all, and pit.
|
|
337
|
+
Overview frames the active generated track bounds with package-owned padding and pit-lane extent; driver rotates around the selected car for user-driving views; show all dynamically fits the active pack; pit frames the operational pit-lane work area when the track has one.
|
|
303
338
|
- Zoom controls.
|
|
304
339
|
- FPS readout.
|
|
305
340
|
- Start lights.
|
|
@@ -313,6 +348,8 @@ Returned controller:
|
|
|
313
348
|
- Race-finish winner banner and final top-three classification.
|
|
314
349
|
- `Open project` button driven by `onDriverOpen(driver)`.
|
|
315
350
|
|
|
351
|
+
Full public race snapshots include `car.trackState` as the serialized car-center track classification. The stable fields are `distance`, `signedOffset`, `crossTrackError`, `surface`, `inPitLane`, `pitLanePart`, `pitBoxId`, `curvature`, and `heading`; pit-specific fields are `null` when not active. In-memory `snapshot.track` remains the live runtime geometry object, but JSON serialization of that track uses a cached rounded public view, exports decimated `track.samples` rows through `track.sampleSchema`, and omits engine-only pit query helpers such as `pitLane.bounds`, `pitLane.boxBounds`, `pitLane.connectorBounds`, pit-crew metadata, and duplicated pit-box/service-area team fields so full-state export/stream paths do not stringify internal geometry helpers or redundant static pit metadata. Runtime readouts normalize missing optional snapshot arrays and race-control groups before rendering, so partial startup or external-renderer frames do not break the package-owned UI.
|
|
352
|
+
|
|
316
353
|
## Runtime Requirements
|
|
317
354
|
|
|
318
355
|
- Browser environment with DOM APIs.
|
|
@@ -336,7 +373,20 @@ Expected:
|
|
|
336
373
|
- `npm pack --dry-run` succeeds and includes source files plus bundled assets.
|
|
337
374
|
- A packed tarball installs and builds inside a fresh temporary Vite consumer app.
|
|
338
375
|
- The tracked showcase host builds.
|
|
339
|
-
- The quick Chromium browser smoke verifies showcase canvas rendering, overflow constraints,
|
|
376
|
+
- The quick Chromium browser smoke verifies showcase canvas rendering, overflow constraints, one public API action, and live customization theme switching. The release browser smoke verifies the desktop/mobile matrix, package-panel overflow constraints, customization route interactions, public API buttons, and visual policy-runner stepping across generation and race configurations.
|
|
377
|
+
|
|
378
|
+
For internal runtime-efficiency development only, also run:
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
npm run benchmark:runtime -- --profile=standard --verify --json
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Expected:
|
|
385
|
+
|
|
386
|
+
- The benchmark suite includes simulation stepping, render snapshot creation, timing DOM updates, compact policy-server JSON, and full snapshot JSON characterization.
|
|
387
|
+
- Full snapshot JSON characterization now enforces compact serialized track geometry, including a capped per-snapshot track JSON size after decimating exported track samples, because `snapshot.track` is the dominant static export cost.
|
|
388
|
+
- Compact policy-server transport remains materially smaller and cheaper to stringify than full observation/snapshot payloads; regressions should be treated as performance contract failures unless the API intentionally changes.
|
|
389
|
+
- This is a contributor regression tool, not a host-consumer or release-note requirement.
|
|
340
390
|
|
|
341
391
|
Run from a browser host that consumes the published package:
|
|
342
392
|
|
package/docs/theming.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Theming
|
|
2
|
+
|
|
3
|
+
PaddockJS exposes a public theme API so host websites can sync simulator colors with site-level light/dark modes without copying private CSS variable names.
|
|
4
|
+
|
|
5
|
+
## Mount-Time Theme
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
await mountF1Simulator(root, {
|
|
9
|
+
drivers,
|
|
10
|
+
entries,
|
|
11
|
+
theme: {
|
|
12
|
+
mode: 'system',
|
|
13
|
+
tokens: {
|
|
14
|
+
primary: { light: '#008c55', dark: '#00ff84' },
|
|
15
|
+
pitLane: '#7c3aed',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Mount-time `theme` sets defaults for a new simulator.
|
|
22
|
+
|
|
23
|
+
## Runtime Theme Changes
|
|
24
|
+
|
|
25
|
+
Use controller methods for site toggles:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
simulator.setThemeMode('dark');
|
|
29
|
+
|
|
30
|
+
simulator.setTheme({
|
|
31
|
+
mode: 'light',
|
|
32
|
+
tokens: { primary: '#008c55' },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const stopThemeSync = simulator.syncThemeFrom(document.documentElement, {
|
|
36
|
+
attribute: 'data-theme',
|
|
37
|
+
map: { light: 'light', dark: 'dark' },
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Do not call `restart({ theme })` only to change presentation. Runtime theme APIs update package CSS variables while preserving race state, selected driver, active penalties, speed, camera state, and pit intent state.
|
|
42
|
+
|
|
43
|
+
## Public Helpers
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
import {
|
|
47
|
+
DEFAULT_PADDOCK_THEME,
|
|
48
|
+
PADDOCK_THEME_CSS_VARIABLES,
|
|
49
|
+
PADDOCK_THEME_TOKEN_KEYS,
|
|
50
|
+
applyPaddockTheme,
|
|
51
|
+
resolvePaddockTheme,
|
|
52
|
+
} from '@inventure71/paddockjs';
|
|
53
|
+
|
|
54
|
+
applyPaddockTheme(previewRoot, resolvePaddockTheme({
|
|
55
|
+
...DEFAULT_PADDOCK_THEME,
|
|
56
|
+
mode: 'light',
|
|
57
|
+
}));
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use these helpers when a host needs a preview, custom theme editor, or site-level sync. Do not mirror `--paddock-color-*` names manually in host code.
|
|
61
|
+
|
|
62
|
+
## Fonts
|
|
63
|
+
|
|
64
|
+
The package stylesheet uses system font stacks and does not load remote fonts. Hosts that want branded typography should load fonts in the host app and then theme the simulator through public tokens/CSS variables.
|