@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.
Files changed (136) hide show
  1. package/CHANGELOG.md +176 -1
  2. package/INSTALL_AND_UPDATE.md +24 -4
  3. package/README.md +84 -512
  4. package/docs/architecture.md +38 -24
  5. package/docs/component_inventory.md +72 -0
  6. package/docs/composable-layouts.md +77 -0
  7. package/docs/concepts.md +2 -2
  8. package/docs/custom_model_controller.md +6 -6
  9. package/docs/data-helpers.md +50 -0
  10. package/docs/data_contract.md +104 -27
  11. package/docs/getting-started.md +75 -0
  12. package/docs/headless-environment.md +68 -0
  13. package/docs/index.md +18 -4
  14. package/docs/learnings.md +6 -6
  15. package/docs/rules.md +12 -8
  16. package/docs/sense_contract.md +1 -1
  17. package/docs/system_specs.md +71 -21
  18. package/docs/theming.md +64 -0
  19. package/docs/training.md +26 -19
  20. package/docs/troubleshooting.md +76 -0
  21. package/docs/upgrading-hosts-to-3.md +42 -0
  22. package/examples/python/README.md +6 -0
  23. package/examples/python/base_policy_server.py +19 -0
  24. package/package.json +10 -3
  25. package/src/api/PaddockSimulatorController.js +150 -24
  26. package/src/api/runtimeTheme.js +59 -0
  27. package/src/app/F1SimulatorApp.js +213 -29
  28. package/src/app/banners/raceDataBannerController.js +71 -36
  29. package/src/app/banners/stewardMessageController.js +5 -4
  30. package/src/app/camera/cameraConstants.js +2 -0
  31. package/src/app/camera/cameraController.js +86 -29
  32. package/src/app/camera/cameraGeometry.js +12 -1
  33. package/src/app/domBindings.js +89 -9
  34. package/src/app/layoutScheduler.js +27 -0
  35. package/src/app/layoutSupport.js +165 -0
  36. package/src/app/raceOverlayClearanceSupport.js +158 -0
  37. package/src/app/readouts/carOverviewRenderer.js +16 -8
  38. package/src/app/readouts/controlStateRenderer.js +1 -1
  39. package/src/app/readouts/raceStatusRenderer.js +30 -21
  40. package/src/app/readouts/readoutFormatters.js +2 -0
  41. package/src/app/readouts/telemetryRenderer.js +17 -8
  42. package/src/app/readouts/timingTowerRenderer.js +262 -49
  43. package/src/app/runtime/frameLoop.js +5 -1
  44. package/src/app/runtime/runtimeVisibility.js +66 -5
  45. package/src/config/cssValues.js +16 -0
  46. package/src/config/defaultOptions.js +58 -38
  47. package/src/config/themeOptions.js +830 -0
  48. package/src/config/timingGapMode.js +13 -0
  49. package/src/config/urlOptions.js +25 -0
  50. package/src/data/championship.js +14 -9
  51. package/src/data/index.d.ts +212 -0
  52. package/src/data/index.js +14 -0
  53. package/src/environment/controllerLoop.js +17 -3
  54. package/src/environment/episode.js +11 -1
  55. package/src/environment/index.d.ts +1 -2
  56. package/src/environment/metrics.js +59 -16
  57. package/src/environment/observationVector.js +50 -33
  58. package/src/environment/observations.js +89 -20
  59. package/src/environment/options.js +1 -1
  60. package/src/environment/runtime.js +44 -11
  61. package/src/environment/scenarios.js +1 -1
  62. package/src/environment/sensors/carRays.js +48 -37
  63. package/src/environment/sensors/index.js +501 -97
  64. package/src/environment/sensors/indexedRayBands.js +134 -46
  65. package/src/environment/sensors/rayBandTrace.js +1171 -0
  66. package/src/environment/sensors/rayChannels.js +108 -0
  67. package/src/environment/sensors/rayConfig.js +71 -25
  68. package/src/environment/sensors/rayGeometry.js +23 -17
  69. package/src/environment/sensors/sensorTargets.js +67 -1
  70. package/src/environment/sensors/surfaceRays.js +167 -118
  71. package/src/environment/sensors/trackRays.js +157 -48
  72. package/src/index.d.ts +153 -9
  73. package/src/index.js +52 -3
  74. package/src/simulation/collisionGeometry.js +260 -96
  75. package/src/simulation/driver/driverController.js +1 -1
  76. package/src/simulation/driver/edgeRecovery.js +1 -1
  77. package/src/simulation/driver/racingControls.js +2 -2
  78. package/src/simulation/driver/rejoinControls.js +2 -2
  79. package/src/simulation/pit/pitFlow.js +21 -9
  80. package/src/simulation/pit/pitKinematics.js +2 -2
  81. package/src/simulation/pit/pitQueue.js +5 -3
  82. package/src/simulation/pit/pitRouteMovement.js +9 -4
  83. package/src/simulation/pit/pitRouting.js +79 -46
  84. package/src/simulation/pit/pitState.js +1 -0
  85. package/src/simulation/pit/pitTireService.js +7 -3
  86. package/src/simulation/race/raceFinish.js +25 -8
  87. package/src/simulation/race/raceOrder.js +189 -32
  88. package/src/simulation/race/raceProgress.js +129 -37
  89. package/src/simulation/race/raceSetup.js +4 -6
  90. package/src/simulation/race/raceStep.js +26 -14
  91. package/src/simulation/race/safetyCar.js +1 -1
  92. package/src/simulation/race/stalledDnf.js +1 -1
  93. package/src/simulation/raceSimulation.js +20 -17
  94. package/src/simulation/rules/collisionSteward.js +40 -23
  95. package/src/simulation/rules/rulesReview.js +77 -56
  96. package/src/simulation/snapshots/raceSnapshots.js +37 -0
  97. package/src/simulation/timing/drsTiming.js +143 -30
  98. package/src/simulation/timing/gapEstimation.js +25 -3
  99. package/src/simulation/timing/lapTelemetry.js +138 -40
  100. package/src/simulation/timing/raceTiming.js +5 -1
  101. package/src/simulation/timing/sectorPerformance.js +104 -26
  102. package/src/simulation/timing/timingHistory.js +229 -14
  103. package/src/simulation/timing/timingLines.js +65 -7
  104. package/src/simulation/track/pitLaneLayout.js +30 -3
  105. package/src/simulation/track/pitLaneState.js +218 -19
  106. package/src/simulation/track/spatialQueries.js +66 -33
  107. package/src/simulation/track/trackMath.js +91 -24
  108. package/src/simulation/track/trackModel.js +2 -0
  109. package/src/simulation/track/trackQueryIndex.js +1468 -189
  110. package/src/simulation/track/trackQueryPitIndex.js +138 -14
  111. package/src/simulation/track/trackQueryScratch.js +7 -0
  112. package/src/simulation/track/trackQueryStats.js +25 -0
  113. package/src/simulation/track/trackSnapshotJson.js +139 -0
  114. package/src/simulation/track/trackStatePolicy.js +249 -2
  115. package/src/simulation/vehicle/contactResolution.js +99 -62
  116. package/src/simulation/vehicle/mainTrackWheelSurface.js +73 -34
  117. package/src/simulation/vehicle/pitWheelSurface.js +81 -28
  118. package/src/simulation/vehicle/runoffResponse.js +54 -29
  119. package/src/simulation/vehicle/vehicleGeometry.js +245 -51
  120. package/src/simulation/vehicle/vehicleKinematics.js +2 -2
  121. package/src/simulation/vehicle/vehiclePhysics.js +45 -30
  122. package/src/simulation/vehicle/vehicleSnapshots.js +35 -1
  123. package/src/simulation/vehicle/vehicleState.js +7 -1
  124. package/src/simulation/vehicle/wheelSurface.js +807 -92
  125. package/src/simulation/warmup/runtimeWarmup.js +0 -1
  126. package/src/styles.css +1568 -519
  127. package/src/ui/bannerTemplates.js +38 -28
  128. package/src/ui/cameraControlsTemplate.js +16 -5
  129. package/src/ui/carOverviewTemplate.js +12 -5
  130. package/src/ui/raceCanvasTemplate.js +31 -8
  131. package/src/ui/raceControlsTemplate.js +27 -10
  132. package/src/ui/shellTemplate.js +3 -1
  133. package/src/ui/telemetryDrawerTemplate.js +23 -10
  134. package/src/ui/telemetryTemplates.js +58 -32
  135. package/src/ui/templateUtils.js +48 -0
  136. package/src/ui/timingTowerTemplate.js +29 -12
package/docs/training.md CHANGED
@@ -46,6 +46,7 @@ The current expert API is a JavaScript environment contract. It supports:
46
46
  - reset-only scenario placement through `scenario.preset`, `scenario.placements`, `scenario.traffic`, and episode-boundary `resetDrivers(placements)`
47
47
  - multi-car interaction profiles through `participantInteractions`, including no-collision training cars that are hidden from other cars' ray and nearby-car sensors by default
48
48
  - per-driver episode state and neutral step metrics for batched training loops
49
+ - `episode.maxSteps` as a controlled-driver episode safety horizon; the default is high for browser/expert usability, so training and evaluation loops should set an explicit smaller value
49
50
  - neutral rollout recording through `createRolloutRecorder()`
50
51
  - deterministic evaluation metrics through `runEnvironmentEvaluation()` and `createEvaluationTracker()`
51
52
  - a JSON-serializable worker protocol wrapper through `createEnvironmentWorkerProtocol()`
@@ -93,7 +94,7 @@ Use a user-owned controller for browser playback and JavaScript training-style l
93
94
  ```js
94
95
  import {
95
96
  createPaddockDriverControllerLoop,
96
- } from '@inventure71/paddockjs';
97
+ } from '@inventure71/paddockjs/environment';
97
98
 
98
99
  const controller = {
99
100
  async init(ctx) {
@@ -155,15 +156,15 @@ built-in driver applied. This is intended for audits or clean local imitation
155
156
  datasets; it does not blend built-in AI with a model during normal controlled
156
157
  stepping.
157
158
 
158
- For model training, prefer `physicsMode: 'simulator'` so the policy learns against the same grip, yaw-rate, contact-patch, kerb, and runoff behavior exposed by those senses. Arcade physics can still be useful as a debugging baseline, but it should not be treated as the main training target for realistic driver policies.
159
+ For model training, prefer `physicsMode: 'advanced'` so the policy learns against the same grip, yaw-rate, contact-patch, kerb, and runoff behavior exposed by those senses. Arcade physics can still be useful as a debugging baseline, but it should not be treated as the main training target for realistic driver policies.
159
160
 
160
- Surface-aware rays are opt-in because they are more expensive than the default road-edge/car channels. The environment normalizes both the legacy compact ray shape and the newer per-ray layout:
161
+ Surface-aware rays are opt-in because they are more expensive than the default road-edge/car channels. The environment normalizes both the compact ray shape and the newer per-ray layout:
161
162
 
162
163
  ```js
163
164
  const env = createPaddockEnvironment({
164
165
  drivers,
165
166
  controlledDrivers: ['budget'],
166
- physicsMode: 'simulator',
167
+ physicsMode: 'advanced',
167
168
  observation: { profile: 'physical-driver' },
168
169
  sensors: {
169
170
  rays: {
@@ -181,13 +182,15 @@ const env = createPaddockEnvironment({
181
182
 
182
183
  Only requested ray channels are computed. The default compact config still computes the existing road-edge and car readings, so existing policy vectors keep their current shape unless the profile or sensor config opts into richer fields. Barrier walls are not a ray channel; they are visible track geometry and a terminal destruction condition in both physics modes. Active ray objects, vectors, schemas, and visualizations must not expose barrier ray fields.
183
184
 
184
- `precision: 'driver'` is the default and the recommended model-facing ray contract. It returns the normal sampled driver-sensor distances without extra refinement. `precision: 'debug'` exists only for clearly labeled diagnostics with additional edge refinement. If the Policy Runner or expert visualization is showing model senses, it must render the active observation values exactly and must not replace them with debug-precision readings.
185
+ `precision: 'driver'` is the default and the recommended model-facing ray contract. Safe main-track road-edge, kerb, and illegal-surface rays use direct indexed boundary intersections against the same surface bands used by wheel surfaces and rendering. Normal driver rays validate direct candidates against driver-precision track state; the `batch-training` profile keeps the accelerated direct indexed contract for safe main-track rays. Surface-only batch-training rays also reuse same-pose `progress`/`signedOffset` origin-state synthesis plus shared surface-hit and track-band boundary scratch, so kerb/illegal-surface vector batches avoid nearest-track origin classification and per-ray hit-object replacement. Recovery rays can also use the direct path when the indexed boundary is validated against driver-precision track state, including barrier-to-runoff illegal-surface recovery and off-track road/kerb re-entry. Low-curvature recovery and validation samples may reuse the origin segment to stay off generic nearest-track classification, while curved ambiguous recovery, pit-connector, and pit-lane-origin rays stay inside the ray-band tracer and use one bounded sampled validation pass for all requested road/surface channels instead of falling back to separate legacy road and surface scans. `precision: 'debug'` uses the same tracer-owned path with additional sampled refinement for clearly labeled diagnostics; only missing-index cases use the older sampled fallback path. If the Policy Runner or expert visualization is showing model senses, it must render the active observation values exactly and must not replace them with debug-precision readings.
186
+
187
+ Scratch-backed driver ray batches keep requested-channel dispatch in reusable scalar channel flags rather than rebuilding channel `Set` containers per ray. The indexed boundary traversal also reuses one query-index sample point across ray-grid steps instead of allocating temporary points. The runtime benchmark gate enforces zero channel-set allocations, stable channel-flag containers, and sample-point reuse for the common direct ray path.
185
188
 
186
189
  When a full observation includes both `object` and `vector`, Policy Runner model inputs that need ray geometry use the observation object's actual `angleDegrees` and `lengthMeters`. Compact vector schemas intentionally carry ray channel values, not out-of-band ray geometry, so consumers with per-driver sensor overrides must read the active object observation or the exact per-driver spec instead of reconstructing layout from a default preset.
187
190
 
188
191
  The model-facing sense proof boundary is documented in [Model Sense Contract](sense_contract.md). The executable contract test compares object observations against independent simulator-snapshot oracles, decodes vector entries by schema name, and checks compact vector-only output against full output for the same deterministic state. New model-facing sense fields must extend that oracle instead of relying only on snapshot or shape tests.
189
192
 
190
- Track-position, wheel-surface, pit-lane, and ray fallback queries can be backed by an internal startup-built track query index. Browser/expert mounts, headless environments, and direct race-simulation construction now default to indexed queries and can opt out with `trackQueryIndex: false` for comparison runs. Compact vector-only, schema-free, no-state environment runs enable that index on the environment-owned simulation, and `batch-training` ray runs keep it enabled even when full state output is returned for inspection. Internal benchmarks can explicitly compare indexed and legacy-disabled modes. This does not change the observation shape. For normal driver interactions, ray `precision: 'driver'` keeps sampled road-edge, kerb, and illegal-surface distances with or without the index, preserving zero-distance origin surface hits and avoiding debug-only precision in policy inputs. The `batch-training` participant profile keeps its indexed recovery-ray path as a separate high-throughput training contract with characterization coverage. Indexed ray-boundary shortcuts remain available to non-driver/debug precision paths.
193
+ Track-position, wheel-surface, pit-lane, and ray queries use the internal startup-built track query index. Browser/expert mounts, headless environments, direct race-simulation construction, compact vector-only training loops, and `batch-training` ray runs all use that canonical indexed path. Indexed pit-road state resolution reuses route candidate buckets, one polyline projection result, and index-owned cumulative route distance tables for entry, fast-lane, working-lane, and exit classification; connector-adjacent direct pit-entry/pit-exit shortcut checks reuse that same projection target and route metadata. Endpoint-progress shortcut checks use precomputed route endpoint segment windows, while mid-connector points use bounded indexed route candidates without becoming full pit-lane queries. The runtime track-query benchmark explicitly probes those pit-road routes and requires direct connector scratch reuse, positive endpoint-window connector projections, zero connector full-route projection scans, grid hits, scratch reuse, precomputed route distances, zero cumulative-distance rebuilds, zero road-grid misses, and zero pit fallbacks. This does not change the observation shape. Ray `precision: 'driver'` uses indexed projection plus driver-precision validation for road-edge, kerb, and illegal-surface distances, preserving zero-distance origin surface hits and avoiding debug-only refinement in policy inputs. The internal ray-band tracer reports whether a ray used the pure `direct` path or bounded `sampled` recovery so benchmarks can catch regressions without changing public ray objects or vectors. Debug precision remains available only for explicitly labeled diagnostics.
191
194
 
192
195
  `resetDrivers()` is part of episode control, not policy action. After reset placement, the environment immediately reclassifies the selected cars against the same runoff/barrier rules used during stepping before building observations. Recovery starts inside the legal recovery band remain normal physical starts. Placements that are already inside the barrier destruction boundary return a terminal per-driver episode state and miss-valued rays, so a batch loop can assign its own negative reward and reset that driver without paying for far-out ray geometry. In multi-driver batches, top-level `done` is true once every reported controlled driver is terminal, including mixed states where one driver is terminated and another is max-step truncated; per-driver `info.drivers[driverId]` keeps the distinct reason.
193
196
 
@@ -287,6 +290,8 @@ Use that override deliberately. The default no-collision training profile is sen
287
290
 
288
291
  Compact vector mode is intended for high-throughput loops. `env.getObservationSpec()` remains the canonical schema source, so external code can request `output: 'vector'` and `includeSchema: false` without serializing object observations and schema data on every step. If `sensorsByDriver` changes ray or nearby-car shape for specific drivers, read `observationSpec.perDriver[driverId].vector.schema` for that driver's exact compact schema. JavaScript training loops can also request `vectorType: 'float32'` for typed numeric buffers. Keep the default array output for JSON-only bridges unless the bridge explicitly packs typed arrays.
289
292
 
293
+ The HTTP Policy Runner server path uses this compact convention by default. `/policy/reset` sends `protocolVersion: 2` plus static metadata, `previousActionFields`, and `metricFields` once, and `/policy/decide-batch` sends only `driverIds` plus aligned `vectors`, `previousActions`, `metrics`, and `events` arrays. Servers should read model inputs from `body.vectors[index]` where `body.driverIds[index]` is the driver being evaluated; `previousActions[index]` is a `[steering, throttle, brake]` tuple for the same driver, and `metrics[index]` follows the reset-time `metricFields` order. Rich observation objects and schemas are no longer repeated on every decision. This is a breaking transport contract for policy-server integrations. There is no rich per-decision fallback in Policy Runner server mode; use object/full observation output only in local JavaScript, headless, debug, or explicit state/export flows.
294
+
290
295
  Use `result.stateOutput` to avoid returning more state than the loop needs:
291
296
 
292
297
  ```js
@@ -304,19 +309,23 @@ const env = createPaddockEnvironment({
304
309
 
305
310
  `stateOutput: 'minimal'` returns the lean public observation snapshot. `stateOutput: 'none'` returns `state: null`. The default is still `full` for existing callers. `resetDriversObservationScope: 'reset'` also scopes `info.controlledDrivers` to the drivers present in that reset result's `observation` and `metrics`; use `info.drivers` if you need all configured drivers' episode bookkeeping. When `stateOutput: 'none'`, `observation.output: 'vector'`, and `includeSchema: false`, the environment uses an internal compact training snapshot before building observations and metrics. That optimization is not exposed as policy state. Reward callbacks still receive the documented reward context on `step(actions)`, and reset/read calls remain reward-neutral.
306
311
 
307
- Simulator-mode velocity senses are authoritative only when the simulator owns a real world velocity. Arcade snapshots intentionally expose `velocityX/Y` as `null`. When simulator-mode cars are terminal or held by race control, grid logic, or pit queue logic, the environment freezes scalar speed and `velocityX/Y` together at zero instead of carrying old motion into observations or recorded transitions.
312
+ Advanced-mode velocity senses are authoritative only when the simulator owns a real world velocity. Arcade snapshots intentionally expose `velocityX/Y` as `null`. When advanced-mode cars are terminal or held by race control, grid logic, or pit queue logic, the environment freezes scalar speed and `velocityX/Y` together at zero instead of carrying old motion into observations or recorded transitions.
308
313
 
309
- On the local 20-car batch-training benchmark used for this package work (`physicsMode: 'simulator'`, `frameSkip: 4`, `physical-driver`, `driver-front-heavy` rays), the measured environment action cost after compact output and indexed track queries was approximately:
314
+ On the local 3.0.0-to-4.0.0 comparison runs (`physicsMode: 'advanced'`, `physical-driver`, `driver-front-heavy` rays, procedural worst-seed checks, and vector observations), the current engine-style paths measured as a net runtime improvement in real environment stepping:
310
315
 
311
316
  ```txt
312
- nearest track states 1.52x faster than legacy-disabled
313
- pit lane states 2.02x faster than legacy-disabled
314
- sampled high-curvature off-track rays 14.81x faster than legacy-disabled
315
- wheel surface near pit connector 1.12x faster than legacy-disabled
316
- 20-car batch recovery environment 1.03x faster than legacy-disabled
317
+ 20-car race step 4.67x faster than 3.0.0
318
+ normal env, seed 104729 4.05x faster than 3.0.0
319
+ normal env, seed 4101 1.45x faster than 3.0.0
320
+ normal env, seed 47 5.07x faster than 3.0.0
321
+ batch-training env, worst seeds 1.32x-1.39x faster than 3.0.0
322
+ wheel surface near pit connector 89.27x faster than 3.0.0
323
+ snapshot JSON, 20 cars 4.21x faster than 3.0.0
317
324
  ```
318
325
 
319
- These numbers are hardware- and track-dependent, but the relative result is the useful guidance: compact state/observation output reduces serialization/allocation overhead, and indexed track queries prevent off-track recovery rays from becoming the dominant cost center.
326
+ Those numbers are hardware- and track-dependent, but the relative result is the useful guidance: compact state/observation output reduces serialization/allocation overhead, indexed track queries prevent off-track recovery rays from becoming the dominant cost center in real environment loops, and direct indexed ray-band intersections keep common driver-precision road/surface rays off the expensive sampled nearest-query path. The high-volume seed sweep covered 69 procedural seeds, 447,120 ray builds, 3,129,840 individual rays, 149,040 wheel-surface classifications, and 1,380 environment steps with zero invalid ray payloads, zero invalid environment observations, zero invalid wheel classifications, and zero nearest/pit fallbacks.
327
+
328
+ There is one deliberate microbenchmark caveat: adversarial standalone `buildRaySensors()` sweeps that force normal-profile rays from kerb, gravel, and runoff poses on complex generated tracks can be slower than 3.0.0 because the current path performs more bounded indexed exact validation to preserve driver-precision surface semantics. On the worst checked seeds, real environment stepping still measured faster than 3.0.0. Treat the standalone ray torture case as a precision/diagnostic stress test, not the expected training-loop cost.
320
329
 
321
330
  ## Scenario Reset Control
322
331
 
@@ -404,7 +413,7 @@ recorder.recordStep(result, action, next);
404
413
 
405
414
  Each transition has `{ observation, action, reward, nextObservation, terminated, truncated, info }`. If the environment has no reward callback, `reward` is still `null`; the recorder does not invent one. Recorded transitions snapshot arrays, typed vectors, actions, observations, rewards, and info at record time, so later policy-loop mutations do not rewrite previous rollout rows.
406
415
 
407
- Environment step results also include reward-neutral `metrics[driverId]` facts such as progress delta, legal progress delta, kerb use, illegal/off-track state, severe cuts, destruction state, under-30-kph state, spin/backwards/instability state, lap completion, lap time, and contact count. `spinOrBackwards` is true for large heading-error backwards cases and for active simulator instability labels such as `spin-risk`, `understeer`, `oversteer`, and `destroyed`. `contactCount` counts each normalized physical contact event once per involved driver even when the event carries both legacy `carId`/`otherCarId` fields and normalized `driverIds`. These metrics are facts for logging, evaluation, and user-defined reward functions; PaddockJS does not turn them into a built-in objective. If a car touches the rendered barrier wall's inner face in either physics mode, that driver receives `metrics[driverId].destroyed: true` and `info.drivers[driverId].endReason: 'destroyed'`; external training code should assign any super-negative crash reward itself and then call `resetDrivers()` for the next episode. If opt-in `rules.modules.stalledDnf.enabled: true` retires a controlled car for being stuck off legal surfaces, the episode terminates with `endReason: 'stalled-off-track'` while `metrics[driverId].destroyed` stays `false`.
416
+ Environment step results also include reward-neutral `metrics[driverId]` facts such as progress delta, legal progress delta, kerb use, illegal/off-track state, severe cuts, destruction state, under-30-kph state, spin/backwards/instability state, lap completion, lap time, and contact count. `spinOrBackwards` is true for large heading-error backwards cases and for active simulator instability labels such as `spin-risk`, `understeer`, `oversteer`, and `destroyed`. `contactCount` counts each normalized physical contact event once per involved driver even when the event carries older `carId`/`otherCarId` fields and normalized `driverIds`. These metrics are facts for logging, evaluation, and user-defined reward functions; PaddockJS does not turn them into a built-in objective. If a car touches the rendered barrier wall's inner face in either physics mode, that driver receives `metrics[driverId].destroyed: true` and `info.drivers[driverId].endReason: 'destroyed'`; external training code should assign any super-negative crash reward itself and then call `resetDrivers()` for the next episode. If opt-in `rules.modules.stalledDnf.enabled: true` retires a controlled car for being stuck off legal surfaces, the episode terminates with `endReason: 'stalled-off-track'` while `metrics[driverId].destroyed` stays `false`.
408
417
 
409
418
  Deterministic evaluation helpers report simulator quality metrics such as distance, off-track steps, contacts, recovery success, pass count, and first lap time when available. They use the same wheel-level legal-surface and contact-event semantics as environment metrics. `runEnvironmentEvaluation()` can accept compact base options with `result.stateOutput: 'none'`; it internally requests the minimal snapshot needed for evaluation bookkeeping. Direct `createEvaluationTracker(result)` calls require a result with `state.snapshot`, so use `stateOutput: 'minimal'` or `full` for manual tracker wiring. Evaluation helpers do not update a model or compute rewards.
410
419
 
@@ -415,10 +424,8 @@ Deterministic evaluation helpers report simulator quality metrics such as distan
415
424
  ## Visual Playback Loop
416
425
 
417
426
  ```js
418
- import {
419
- createPaddockDriverControllerLoop,
420
- mountF1Simulator,
421
- } from '@inventure71/paddockjs';
427
+ import { createPaddockDriverControllerLoop } from '@inventure71/paddockjs/environment';
428
+ import { mountF1Simulator } from '@inventure71/paddockjs';
422
429
 
423
430
  const simulator = await mountF1Simulator(root, {
424
431
  drivers,
@@ -0,0 +1,76 @@
1
+ # Troubleshooting
2
+
3
+ Use this when a host integration or package release check fails.
4
+
5
+ ## Raw Node Import Fails
6
+
7
+ Do not use this as the main package check:
8
+
9
+ ```bash
10
+ node -e "import('@inventure71/paddockjs')"
11
+ ```
12
+
13
+ The root package is browser-oriented and imports CSS/assets. Use the browser-free subpaths in Node:
14
+
15
+ ```js
16
+ import { createPaddockEnvironment } from '@inventure71/paddockjs/environment';
17
+ import { DriverData } from '@inventure71/paddockjs/data';
18
+ ```
19
+
20
+ ## CSS Is Missing
21
+
22
+ Import the package stylesheet from a browser entry when the host bundler does not extract it automatically:
23
+
24
+ ```js
25
+ import '@inventure71/paddockjs/styles.css';
26
+ ```
27
+
28
+ If styles are present but the simulator is squeezed or overlapping, check the host container size before changing package internals. PaddockJS supports roots at least `320px` wide and shows an `Unsupported size` placeholder below supported bounds.
29
+
30
+ ## Assets Do Not Resolve
31
+
32
+ PaddockJS owns default simulator assets. Hosts should not copy them manually. In the package repo, verify publish contents with:
33
+
34
+ ```bash
35
+ npm run pack:dry
36
+ ```
37
+
38
+ In a host app, verify through the host's browser build rather than a raw Node import.
39
+
40
+ ## Simulator Looks Frozen
41
+
42
+ Check whether browser expert mode is enabled:
43
+
44
+ ```js
45
+ expert: { enabled: true, controlledDrivers: ['budget'] }
46
+ ```
47
+
48
+ Expert mode intentionally waits for `simulator.expert.step(actions)`. Basic website mounts should omit `expert` unless they are playing back a model or debugging policy observations.
49
+
50
+ ## Policy Server Receives No Observations
51
+
52
+ Policy Runner server mode uses protocol version `2`. `/policy/decide-batch`
53
+ receives aligned compact arrays, not rich `observations`. Static specs,
54
+ configuration, `previousActionFields`, and `metricFields` are sent once to
55
+ `/policy/reset`, so servers should cache `observationSpec` and read model
56
+ inputs from `body.vectors[index]` for `body.driverIds[index]` during decisions.
57
+
58
+ ## Theme Toggle Resets The Race
59
+
60
+ Use runtime theme APIs instead of restarting:
61
+
62
+ ```js
63
+ simulator.setThemeMode('dark');
64
+ simulator.setTheme({ tokens: { primary: '#008c55' } });
65
+ ```
66
+
67
+ `restart(nextOptions)` is for race/data/seed changes, not normal light/dark presentation sync.
68
+
69
+ ## 3.x Upgrade Symptoms
70
+
71
+ - Remove host usage of `trackQueryIndex`; indexed track queries are package internals.
72
+ - Replace old strict physics usage with `physicsMode: 'advanced'`, or omit `physicsMode` for the default `arcade` mode.
73
+ - Use `@inventure71/paddockjs/environment` for headless simulation.
74
+ - Use `@inventure71/paddockjs/data` for CSS-free data/tooling imports.
75
+
76
+ See [Upgrading Hosts To PaddockJS 3.x](upgrading-hosts-to-3.md).
@@ -0,0 +1,42 @@
1
+ # Upgrading Hosts To PaddockJS 4.x
2
+
3
+ Use this checklist when moving a browser host website from 2.x-era PaddockJS usage to the 4.x package API.
4
+
5
+ 1. Install the 4.x package:
6
+
7
+ ```bash
8
+ npm install @inventure71/paddockjs@4
9
+ ```
10
+
11
+ 2. Import package CSS intentionally anywhere browser mounts are used:
12
+
13
+ ```js
14
+ import '@inventure71/paddockjs/styles.css';
15
+ ```
16
+
17
+ The stylesheet is scoped to PaddockJS roots and uses system fonts by default. Load any branded web fonts from the host app instead of relying on package-side remote font imports.
18
+
19
+ 3. Remove host usage of `trackQueryIndex`. Track query indexing is package-owned and internal in 4.x.
20
+
21
+ 4. Replace `physicsMode: 'simulator'` with `physicsMode: 'advanced'`, or omit `physicsMode` to keep the default `arcade` mode.
22
+
23
+ 5. Decide how the host should theme the simulator. Use mount-time `theme` for defaults, then use `simulator.setThemeMode(mode)` or `simulator.setTheme(theme)` for site light/dark toggles. Do not call `restart({ theme })` only to change presentation.
24
+
25
+ 6. Reuse public theme helpers instead of copying CSS variable names:
26
+
27
+ ```js
28
+ import {
29
+ PADDOCK_THEME_CSS_VARIABLES,
30
+ PADDOCK_THEME_TOKEN_KEYS,
31
+ applyPaddockTheme,
32
+ resolvePaddockTheme,
33
+ } from '@inventure71/paddockjs';
34
+ ```
35
+
36
+ 7. Decide whether the host should enable the driver camera with `ui.driverCamera: true` or `initialCameraMode: 'driver'`.
37
+
38
+ 8. Use `@inventure71/paddockjs/environment` for browser-free simulation/training imports.
39
+
40
+ 9. Use `@inventure71/paddockjs/data` for CSS-free data/helper imports such as `DriverData`, `normalizeSimulatorDrivers`, `createProceduralTrack`, and speed conversion helpers.
41
+
42
+ 10. Rebuild the host bundle and browser-smoke the host page, including theme toggles, selected-driver surfaces, and any composable mount roots.
@@ -14,6 +14,8 @@ It is not a trainer and not a model/checkpoint format owner. It only shows trans
14
14
  - Preview latest-frame HTTP endpoint (for polling visualizers)
15
15
  - `http://127.0.0.1:8787/preview/frame`
16
16
 
17
+ Policy HTTP uses protocol version `2`. `POST /policy/reset` receives static metadata (`actionSpec`, `observationSpec`, configuration) once per session. `POST /policy/decide-batch` is compact and receives `driverIds`, `vectors`, `previousActions`, `metrics`, and `events`; it does not receive rich observation objects, schemas, snapshots, or track metadata every decision.
18
+
17
19
  ## Run
18
20
 
19
21
  ```bash
@@ -55,6 +57,10 @@ Subclass `BasePolicyServer` and override:
55
57
 
56
58
  Use `broadcast_preview_frame(snapshot, observation, meta)` to push authoritative frames to the browser renderer.
57
59
 
60
+ Inside `decide_batch(ctx)`, compact per-driver fields are aligned arrays:
61
+ `ctx["vectors"][index]` belongs to `ctx["driverIds"][index]`. The base example's
62
+ `compact_values_by_driver(ctx, "vectors")` helper maps them back to driver ids.
63
+
58
64
  ## Important Boundary
59
65
 
60
66
  - This server does not step the simulator by itself.
@@ -10,6 +10,9 @@ This file is intentionally minimal and transport-focused:
10
10
  - /preview (broadcasts {snapshot, observation, meta})
11
11
 
12
12
  Extend `BasePolicyServer` and override hook methods to plug your own model/runtime.
13
+ Policy HTTP uses protocolVersion 2. Reset receives static specs/configuration;
14
+ decide-batch receives compact per-driver vectors instead of rich observations.
15
+ Compact per-driver fields are arrays aligned with driverIds.
13
16
  """
14
17
 
15
18
  from __future__ import annotations
@@ -33,6 +36,20 @@ def _zero_action() -> Dict[str, float]:
33
36
  }
34
37
 
35
38
 
39
+ def compact_values_by_driver(context: Mapping[str, Any], field: str) -> Dict[str, Any]:
40
+ """Map a compact aligned field such as vectors/metrics back to driver ids."""
41
+ driver_ids = list(context.get("driverIds") or [])
42
+ values = context.get(field) or []
43
+ if isinstance(values, Mapping):
44
+ return {driver_id: values.get(driver_id) for driver_id in driver_ids}
45
+ if not isinstance(values, list):
46
+ return {driver_id: None for driver_id in driver_ids}
47
+ return {
48
+ driver_id: values[index] if index < len(values) else None
49
+ for index, driver_id in enumerate(driver_ids)
50
+ }
51
+
52
+
36
53
  @dataclass
37
54
  class ServerState:
38
55
  session_id: int = 0
@@ -70,6 +87,8 @@ class BasePolicyServer:
70
87
  async def decide_batch(self, context: Mapping[str, Any]) -> Dict[str, Dict[str, float]]:
71
88
  """Return per-driver controls for one batched policy step."""
72
89
  driver_ids = list(context.get("driverIds") or [])
90
+ vectors_by_driver = compact_values_by_driver(context, "vectors")
91
+ _ = vectors_by_driver
73
92
  return {driver_id: _zero_action() for driver_id in driver_ids}
74
93
 
75
94
  async def publish_preview_frame(self, frame: Mapping[str, Any]) -> None:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inventure71/paddockjs",
3
- "version": "2.0.1",
3
+ "version": "4.0.0",
4
4
  "type": "module",
5
5
  "description": "A data-driven browser F1 simulator component for host websites.",
6
6
  "license": "Apache-2.0",
@@ -45,6 +45,11 @@
45
45
  "import": "./src/environment/index.js",
46
46
  "default": "./src/environment/index.js"
47
47
  },
48
+ "./data": {
49
+ "types": "./src/data/index.d.ts",
50
+ "import": "./src/data/index.js",
51
+ "default": "./src/data/index.js"
52
+ },
48
53
  "./styles.css": "./src/styles.css"
49
54
  },
50
55
  "files": [
@@ -75,13 +80,15 @@
75
80
  "test": "node scripts/run-vitest.mjs --slow",
76
81
  "test:fast": "node scripts/run-vitest.mjs",
77
82
  "types:check": "tsc -p tsconfig.types.json --noEmit",
78
- "check": "npm run test:fast && npm run types:check && npm run pack:dry && npm run consumer:smoke && npm run showcase:ci && npm run browser:smoke:quick -- --skip-build",
79
- "check:release": "npm test && npm run types:check && npm run pack:dry && npm run consumer:smoke && npm run showcase:ci && npm run browser:smoke:full -- --skip-build",
83
+ "docs:check": "node scripts/docs-check.mjs",
84
+ "check": "npm run docs:check && npm run test:fast && npm run types:check && npm run pack:dry && npm run consumer:smoke && npm run showcase:ci && npm run browser:smoke:quick -- --skip-build",
85
+ "check:release": "npm run docs:check && npm test && npm run types:check && npm run pack:dry && npm run consumer:smoke && npm run showcase:ci && npm run browser:smoke:full -- --skip-build",
80
86
  "pack:dry": "npm pack --dry-run",
81
87
  "consumer:smoke": "node scripts/consumer-package-smoke.mjs",
82
88
  "browser:smoke": "node smoke/browser-smoke.mjs",
83
89
  "browser:smoke:quick": "node smoke/browser-smoke.mjs --quick",
84
90
  "browser:smoke:full": "node smoke/browser-smoke.mjs --full",
91
+ "benchmark:runtime": "node scripts/benchmark-runtime-efficiency.mjs",
85
92
  "showcase:install": "npm --prefix local-preview ci",
86
93
  "showcase:dev": "npm --prefix local-preview run dev",
87
94
  "showcase:build": "npm --prefix local-preview run build",