@inventure71/paddockjs 4.1.0 → 4.3.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 +76 -0
- package/INSTALL_AND_UPDATE.md +31 -5
- package/README.md +34 -3
- package/docs/advanced_ai_pace.md +131 -0
- package/docs/advanced_physics_rebuild.md +166 -0
- package/docs/architecture.md +111 -30
- package/docs/cleanup_coverage.md +284 -0
- package/docs/component_inventory.md +32 -16
- package/docs/composable-layouts.md +2 -0
- package/docs/concepts.md +12 -1
- package/docs/custom_model_controller.md +1 -1
- package/docs/data_contract.md +33 -10
- package/docs/data_model.md +4 -2
- package/docs/efficiency_cleanup_2026-10-13.md +80 -0
- package/docs/getting-started.md +3 -0
- package/docs/grid_walk.md +80 -0
- package/docs/index.md +5 -2
- package/docs/keyboard_driving_fix.md +118 -0
- package/docs/limit_driving_2026-10-14.md +88 -0
- package/docs/loading.md +141 -0
- package/docs/maintainability_audit_2026-09-08.md +146 -0
- package/docs/racing_ai_improvements.md +145 -0
- package/docs/rules.md +43 -13
- package/docs/sense_contract.md +4 -1
- package/docs/system_specs.md +40 -12
- package/docs/testing-reliability-guide.md +67 -0
- package/docs/v11_improvements.md +108 -0
- package/package.json +23 -15
- package/src/api/MountedSimulatorComponents.js +101 -0
- package/src/api/PaddockSimulatorController.js +56 -81
- package/src/app/BrowserExpertAdapter.js +0 -6
- package/src/app/BrowserExpertExternalRenderer.js +72 -50
- package/src/app/ExternalTrackSnapshot.js +50 -0
- package/src/app/F1SimulatorApp.js +242 -297
- package/src/app/HostLifecycleEmitter.js +93 -0
- package/src/app/banners/raceDataBannerController.js +9 -4
- package/src/app/camera/cameraConstants.js +1 -1
- package/src/app/camera/cameraController.js +10 -13
- package/src/app/camera/cameraGeometry.js +1 -1
- package/src/app/controls/SimulatorViewControls.js +45 -0
- package/src/app/controls/workbenchBindings.js +64 -0
- package/src/app/controls/workbenchDisclosure.js +41 -0
- package/src/app/domBindings.js +21 -13
- package/src/app/gridWalk/GridWalkController.js +180 -0
- package/src/app/gridWalk/gridWalkCamera.js +88 -0
- package/src/app/gridWalk/gridWalkControls.js +24 -0
- package/src/app/gridWalk/gridWalkCrew.js +246 -0
- package/src/app/gridWalk/gridWalkLabels.js +104 -0
- package/src/app/gridWalk/gridWalkLayout.js +46 -0
- package/src/app/gridWalk/gridWalkNavigation.js +99 -0
- package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
- package/src/app/gridWalk/gridWalkSession.js +50 -0
- package/src/app/layoutSupport.js +2 -0
- package/src/app/raceOverlayClearanceSupport.js +21 -7
- package/src/app/readouts/carOverviewRenderer.js +9 -4
- package/src/app/readouts/readoutFormatters.js +1 -8
- package/src/app/readouts/telemetryRenderer.js +1 -3
- package/src/app/readouts/timingTowerRenderer.js +48 -50
- package/src/app/rendering/appAssets.js +8 -10
- package/src/app/rendering/carRenderer.js +27 -79
- package/src/app/rendering/displayUtils.js +0 -6
- package/src/app/rendering/expertSensorRenderer.js +1 -1
- package/src/app/rendering/replayGhostRenderer.js +4 -1
- package/src/app/rendering/trackRenderer.js +2 -2
- package/src/app/runtime/frameLoop.js +18 -17
- package/src/app/runtime/pixiStartupLifecycle.js +94 -0
- package/src/app/runtime/runtimeTiming.js +4 -1
- package/src/app/runtime/runtimeVisibility.js +7 -2
- package/src/config/cssValues.js +1 -1
- package/src/config/defaultOptions.js +12 -173
- package/src/config/restartOptions.js +1 -0
- package/src/config/runtimeSeed.js +10 -0
- package/src/config/simulatorDefaults.js +106 -0
- package/src/config/telemetryModules.js +20 -0
- package/src/config/theme/cssTokens.js +85 -0
- package/src/config/theme/themeCss.js +72 -0
- package/src/config/theme/themeInputs.js +155 -0
- package/src/config/theme/themeResolver.js +172 -0
- package/src/config/theme/themeSelectors.js +69 -0
- package/src/config/theme/themeTokens.js +257 -0
- package/src/config/theme/themeUtils.js +15 -0
- package/src/config/themeOptions.js +14 -830
- package/src/config/timingGapMode.js +0 -2
- package/src/config/uiOptions.js +53 -0
- package/src/data/championship.js +16 -158
- package/src/data/championshipDefaults.js +87 -0
- package/src/data/driverData.js +10 -20
- package/src/data/entryIdentity.js +67 -0
- package/src/data/entryPresentation.js +23 -0
- package/src/data/entryTypes.d.ts +160 -0
- package/src/data/index.d.ts +12 -195
- package/src/data/ratingScale.js +14 -0
- package/src/data/teamTypes.d.ts +23 -0
- package/src/data/trackTypes.d.ts +101 -0
- package/src/data/vehicleData.js +12 -23
- package/src/environment/actions.js +3 -2
- package/src/environment/controllerLoop.js +57 -44
- package/src/environment/controllerScheduler.js +30 -0
- package/src/environment/driverObservations.js +260 -0
- package/src/environment/driverReset.js +51 -0
- package/src/environment/episode.js +0 -1
- package/src/environment/evaluation.js +6 -90
- package/src/environment/evaluationTracker.js +100 -0
- package/src/environment/events.js +23 -0
- package/src/environment/index.d.ts +35 -91
- package/src/environment/metrics.js +3 -17
- package/src/environment/observationSchema.js +104 -0
- package/src/environment/observationVector.js +16 -115
- package/src/environment/observations.js +16 -316
- package/src/environment/options.js +5 -24
- package/src/environment/renderFrames.js +21 -0
- package/src/environment/resetOptions.js +41 -0
- package/src/environment/results.js +125 -0
- package/src/environment/rewards.js +10 -16
- package/src/environment/runtime.js +17 -300
- package/src/environment/scenarioOptions.js +90 -0
- package/src/environment/scenarioPlacements.js +70 -0
- package/src/environment/scenarioPresets.js +78 -0
- package/src/environment/scenarios.js +5 -236
- package/src/environment/scratch.js +18 -0
- package/src/environment/sensorOptions.js +20 -0
- package/src/environment/sensors/index.js +2 -556
- package/src/environment/sensors/indexedRayBands.js +0 -18
- package/src/environment/sensors/nearbyCars.js +18 -15
- package/src/environment/sensors/opponentRadar.js +3 -2
- package/src/environment/sensors/pitConnectorProximity.js +20 -0
- package/src/environment/sensors/rayBandTrace.js +1 -37
- package/src/environment/sensors/rayBatch.js +81 -0
- package/src/environment/sensors/rayConfig.js +26 -0
- package/src/environment/sensors/rayGeometry.js +0 -8
- package/src/environment/sensors/rayGuards.js +0 -6
- package/src/environment/sensors/rayOutputs.js +75 -0
- package/src/environment/sensors/rayQuery.js +110 -0
- package/src/environment/sensors/rayScratch.js +146 -0
- package/src/environment/sensors/sensorTargets.js +12 -4
- package/src/environment/sensors/surfaceRays.js +6 -61
- package/src/environment/sensors/trackRays.js +7 -80
- package/src/environment/simulationSetup.js +30 -0
- package/src/environment/specs.js +15 -109
- package/src/index.d.ts +43 -202
- package/src/index.js +28 -6
- package/src/placeholder/index.js +1 -8
- package/src/rendering/displayLifecycle.js +6 -0
- package/src/rendering/proceduralTrackAsset.js +1 -1
- package/src/rendering/renderSnapshot.js +3 -0
- package/src/rendering/track/finishLineRenderer.js +1 -1
- package/src/rendering/track/gridRenderer.js +1 -1
- package/src/rendering/track/offsetStrokeSafety.js +2 -2
- package/src/rendering/track/pitLaneRenderer.js +1 -1
- package/src/rendering/track/trackMaterialRenderer.js +4 -4
- package/src/rendering/track/trackRenderConstants.js +0 -1
- package/src/rendering/track/trackRenderGeometry.js +1 -7
- package/src/simulation/collisionGeometry.js +12 -20
- package/src/simulation/driver/advancedPathControls.js +53 -0
- package/src/simulation/driver/advancedRacePace.js +112 -0
- package/src/simulation/driver/advancedRacingControls.js +61 -0
- package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
- package/src/simulation/driver/advancedRacingSteering.js +76 -0
- package/src/simulation/driver/advancedRacingTiming.js +16 -0
- package/src/simulation/driver/arcadeRacePace.js +83 -0
- package/src/simulation/driver/arcadeRacingControls.js +111 -0
- package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
- package/src/simulation/driver/driverControlConstants.js +0 -2
- package/src/simulation/driver/driverController.js +16 -5
- package/src/simulation/driver/edgeRecovery.js +2 -3
- package/src/simulation/driver/racingControls.js +10 -396
- package/src/simulation/driver/racingDriverProfile.js +48 -0
- package/src/simulation/driver/racingLinePlan.js +54 -20
- package/src/simulation/driver/rejoinControls.js +59 -112
- package/src/simulation/driver/safetyCarControls.js +12 -0
- package/src/simulation/driver/trafficScan.js +136 -27
- package/src/simulation/participants/participantInteractions.js +24 -15
- package/src/simulation/pit/pitCarPlacement.js +29 -0
- package/src/simulation/pit/pitFlow.js +8 -30
- package/src/simulation/pit/pitIntent.js +4 -3
- package/src/simulation/pit/pitOccupancy.js +2 -2
- package/src/simulation/pit/pitQueue.js +2 -18
- package/src/simulation/pit/pitRouteMovement.js +1 -36
- package/src/simulation/pit/pitRouting.js +5 -81
- package/src/simulation/pit/pitService.js +3 -32
- package/src/simulation/pit/pitServiceConstants.js +1 -0
- package/src/simulation/pit/pitServiceProgress.js +34 -0
- package/src/simulation/pit/pitSnapshots.js +8 -4
- package/src/simulation/pit/pitState.js +1 -5
- package/src/simulation/race/classification.js +1 -8
- package/src/simulation/race/raceFinish.js +3 -8
- package/src/simulation/race/raceOrder.js +2 -13
- package/src/simulation/race/raceProgress.js +6 -20
- package/src/simulation/race/raceSetup.js +1 -1
- package/src/simulation/race/raceStep.js +20 -11
- package/src/simulation/race/redFlag.js +2 -4
- package/src/simulation/race/safetyCar.js +5 -9
- package/src/simulation/race/stalledDnf.js +2 -4
- package/src/simulation/raceSimulation.js +6 -38
- package/src/simulation/replay/replayGhosts.js +44 -32
- package/src/simulation/rules/collisionSteward.js +0 -14
- package/src/simulation/rules/penaltyActions.js +37 -0
- package/src/simulation/rules/penaltyLedger.js +8 -1
- package/src/simulation/rules/pitLaneSpeedingSteward.js +1 -1
- package/src/simulation/rules/rulesReview.js +20 -16
- package/src/simulation/rules/trackLimitsSteward.js +2 -2
- package/src/simulation/simMath.js +4 -0
- package/src/simulation/snapshots/raceSnapshots.js +30 -11
- package/src/simulation/timing/drsState.js +8 -0
- package/src/simulation/timing/drsTiming.js +2 -4
- package/src/simulation/timing/lapTelemetry.js +7 -4
- package/src/simulation/timing/sectorPerformance.js +14 -6
- package/src/simulation/timing/timingLines.js +12 -6
- package/src/simulation/track/pitLaneAccess.js +2 -2
- package/src/simulation/track/pitLaneLayout.js +4 -135
- package/src/simulation/track/pitLaneModel.js +70 -0
- package/src/simulation/track/pitLaneState.js +0 -1
- package/src/simulation/track/pitServiceLayout.js +121 -0
- package/src/simulation/track/proceduralTrack.js +77 -0
- package/src/simulation/track/spatialQueries.js +76 -31
- package/src/simulation/track/trackBuilder.js +54 -0
- package/src/simulation/track/trackConstants.js +0 -22
- package/src/simulation/track/trackMath.js +4 -29
- package/src/simulation/track/trackModel.js +12 -240
- package/src/simulation/track/trackModelCache.js +105 -0
- package/src/simulation/track/trackNearestQueries.js +1218 -0
- package/src/simulation/track/trackQueryConstants.js +3 -0
- package/src/simulation/track/trackQueryConstruction.js +368 -0
- package/src/simulation/track/trackQueryIndex.js +12 -1933
- package/src/simulation/track/trackQuerySegments.js +174 -0
- package/src/simulation/track/trackQueryStats.js +12 -0
- package/src/simulation/track/trackRayQueries.js +165 -0
- package/src/simulation/vehicle/advancedContactResponse.js +89 -0
- package/src/simulation/vehicle/advancedTireForces.js +92 -0
- package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
- package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
- package/src/simulation/vehicle/contactResolution.js +23 -9
- package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
- package/src/simulation/vehicle/pitWheelSurface.js +0 -12
- package/src/simulation/vehicle/runoffResponse.js +2 -4
- package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
- package/src/simulation/vehicle/vehicleGeometry.js +85 -103
- package/src/simulation/vehicle/vehicleKinematics.js +10 -0
- package/src/simulation/vehicle/vehiclePhysics.js +42 -196
- package/src/simulation/vehicle/vehicleSnapshots.js +18 -6
- package/src/simulation/vehicle/vehicleState.js +5 -3
- package/src/simulation/vehicle/wheelSurface.js +13 -432
- package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
- package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
- package/src/styles.css +173 -19
- package/src/ui/bannerTemplates.js +4 -2
- package/src/ui/cameraControlsTemplate.js +4 -2
- package/src/ui/gridWalkTemplate.js +29 -0
- package/src/ui/htmlEscaping.js +8 -0
- package/src/ui/raceCanvasTemplate.js +3 -2
- package/src/ui/shellTemplate.js +3 -0
- package/src/ui/telemetryDrawerTemplate.js +4 -2
- package/src/ui/telemetryTemplates.js +3 -22
- package/src/ui/templateUtils.js +2 -8
- package/src/ui/viewControlsTemplate.js +21 -0
- package/examples/train-basic-policy.mjs +0 -276
- package/examples/trainingData.mjs +0 -133
package/docs/data_contract.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
This file documents the data that host websites pass into PaddockJS.
|
|
4
4
|
|
|
5
|
+
`gridWalk?: { enabled?: boolean, initialView?: 'grid-walk' | 'race', ambientMotion?: boolean }` enables [Grid Walk](grid_walk.md). Defaults are disabled, Grid Walk initial view when enabled, and ambient motion enabled. `SimulatorDriver` and `ChampionshipEntryBlueprint` accept `presentation?: EntryPresentation` with optional `name`, `summary`, `technologies: string[]`, `details: string[]`, `image: { src, alt? }`, `href` and `linkLabel`. Matching entry metadata overrides driver metadata. Full driver names, legacy `raceData` and `link` are compatible fallbacks. URLs are normalized and text is escaped; Grid Walk links are anchors independent of `onDriverOpen`. Its shared race banner displays name/summary/link content only; technologies, details and image remain accepted metadata but are not rendered in an extra inspector.
|
|
6
|
+
|
|
7
|
+
Grid Walk mode controls are a separate presentation surface: `simulator.mountViewControls(controlsRoot)` or `mountViewControls(controlsRoot, simulator)`. Mount it before `start()` alongside a standalone race canvas; the all-in-one shell and race telemetry drawer compose it automatically above the scene. Its buttons share the controller's `getView()` / `setView()` state, stay disabled during startup and hide when Grid Walk is disabled. `ui.viewControls: 'switch' | 'toggle'` chooses the default two-button switch or a single Start race / Stop race action. Stop pauses exact race progress and shows the original starting grid using display poses; Start resumes saved progress. Both variants use the same mounting API, including after restart. No host-specific routing is required.
|
|
8
|
+
|
|
5
9
|
## Mount Options
|
|
6
10
|
|
|
7
11
|
All-in-one API:
|
|
@@ -136,7 +140,7 @@ result = env.step({
|
|
|
136
140
|
`controlledDrivers` is required. It supports one or many externally controlled cars. Non-controlled participants use the built-in driver AI in the stable 1.0 environment API.
|
|
137
141
|
`externalRenderer` is optional and observer-only. It can be a function or `{ onFrame(frame) }`, and receives `{ snapshot, observation, meta }` on `reset`, `step`, and `resetDrivers`. The hook does not mutate simulation state and hook failures are isolated so stepping continues.
|
|
138
142
|
`warmup` is optional and enabled by default. It primes a disposable runtime during load/reset creation and caches by configuration fingerprint so repeated identical resets skip warmup. Use `warmup: { enabled, policy: 'config-change' | 'always' | 'never', steps }` or `warmup: false` to disable.
|
|
139
|
-
`physicsMode` is optional and accepts `'arcade'` or `'advanced'`. Invalid values, including the removed old `'simulator'` name, fall back to `'arcade'`, which is the default compatibility mode. `'advanced'` keeps the same public action contract but uses
|
|
143
|
+
`physicsMode` is optional and accepts `'arcade'` or `'advanced'`. Invalid values, including the removed old `'simulator'` name, fall back to `'arcade'`, which is the default compatibility mode. `'advanced'` keeps the same public action contract but uses four-contact force and yaw-inertia dynamics and exposes additional telemetry fields on snapshots and observations: `velocityX`, `velocityY`, `lateralG`, `longitudinalG`, `gripUsage`, `slipAngleRadians`, `tractionLimited`, and `stabilityState`. In advanced mode, `slipAngleRadians` is derived from the car heading versus actual velocity direction, and each wheel contributes forces from its own surface and normal load; wheel snapshots retain their existing contact-patch shape. `gripUsage` is the maximum requested combined force divided by adhesion over the four contacts; it can exceed one. `tractionLimited` flags this demand threshold, while `stabilityState` distinguishes front/rear demand imbalance and large body slip. Acceleration telemetry is the velocity change over the caller step, projected into the body axes at the start of that step, in m/s²; G divides it by 9.80665. Tire slip at a contact is internal and must not be substituted for the public body `slipAngleRadians`. Terminal and race-control-held advanced-mode cars publish `velocityX: 0` and `velocityY: 0` whenever scalar `speed` is zero, so training loops do not see stale pre-DNF, pre-grid, pre-red-flag, or pre-queue motion. Arcade snapshots publish `velocityX` and `velocityY` as `null`; consumers that need a heading-aligned arcade velocity should derive it from `heading` and `speed`. The rebuilt advanced model intentionally changes trajectories and grip-response distributions; controllers/checkpoints trained on the previous advanced dynamics need reevaluation or retraining. Action shapes and observation schema remain unchanged.
|
|
140
144
|
`rules` is an optional override object for the race rules documented in [rules.md](rules.md). Flat keys such as `standingStart: false` still work for existing behavior. Advanced systems live under `rules.modules` so hosts can choose a preset and then override individual modules:
|
|
141
145
|
|
|
142
146
|
```js
|
|
@@ -301,6 +305,8 @@ Operationally this is one architecture with two modes:
|
|
|
301
305
|
- local expert mode: browser owns stepping
|
|
302
306
|
- external render mode: browser only renders authoritative external frames
|
|
303
307
|
|
|
308
|
+
Browser external-renderer frames accept live indexed track models and JSON snapshot geometry, including the package's compact `sampleSchema` / sample-table form. The browser restores display-only query infrastructure and omitted pit bounds, caches unchanged received geometry, and reports malformed geometry through external-renderer state. It does not regenerate the track or compute replacement model senses. Frame `observation` remains the authoritative model-facing data; detaching clears live senses and invalidates queued callbacks.
|
|
309
|
+
|
|
304
310
|
Policy Runner maps this directly through supported controller modes: `Distilled policy` and `Policy server` for browser-owned stepping, and `Live preview stream` for external render-only frames. The browser-owned HTTP policy-server transport uses protocol version `2`: `/policy/reset` receives static metadata such as `actionSpec`, `observationSpec`, configuration, `previousActionFields`, and `metricFields` once per session, while `/policy/decide-batch` receives only `driverIds` plus parallel compact `vectors`, `previousActions`, `metrics`, and `events` arrays aligned by driver index. Per-decision payloads intentionally do not include full observation objects, schemas, snapshots, or track/static metadata. Server integrations must treat the reset payload as the place to cache specs/configuration and tuple field order; Policy Runner server mode does not expose a legacy rich-observation transport option.
|
|
305
311
|
|
|
306
312
|
`steering` is an absolute steering-wheel target: `-1` points at the maximum left steering limit, `0` points at center, `1` points at the maximum right steering limit, and intermediate values are percentages of that limit. The vehicle physics moves the steering angle toward that target through the configured steering-rate limit, so centering is physical rather than an instantaneous snap. `throttle` and `brake` are clamped from `0` to `1`. `steering`, `throttle`, and `brake` are required on every controlled-driver action; missing or non-finite values fail action validation instead of defaulting to zero.
|
|
@@ -455,6 +461,8 @@ recorder.recordStep(previous, action, next);
|
|
|
455
461
|
|
|
456
462
|
Each recorded transition has `{ observation, action, reward, nextObservation, terminated, truncated, info }`. Recorded transitions are cloned at record time, including numeric arrays and typed vectors, so later mutation of a policy action object or observation buffer cannot change already-recorded samples. This is data export only; it does not train or update a model.
|
|
457
463
|
|
|
464
|
+
Controller hooks (`init`, `reset`, `decideBatch`, `onStep`) receive an optional typed `context.signal`; Paddock-owned loops always supply an AbortSignal for the current generation. Forward it to cancellable I/O. Stop, full reset and partial reset abort prior work, then future operations receive a fresh signal. Obsolete completions and abort rejections cannot apply stale actions or overwrite the resumed loop's error/running state. Custom callers constructing contexts may omit the field.
|
|
465
|
+
|
|
458
466
|
Deterministic evaluation helpers run fixed seeds/scenarios and report environment quality metrics:
|
|
459
467
|
|
|
460
468
|
```js
|
|
@@ -468,7 +476,7 @@ const report = runEnvironmentEvaluation({
|
|
|
468
476
|
});
|
|
469
477
|
```
|
|
470
478
|
|
|
471
|
-
Evaluation reports include distance, lap progress, off-track step count, contact count, recovery success, pass count, and
|
|
479
|
+
Evaluation reports include distance, lap progress, off-track step count, contact count, recovery success, pass count, and a recorded completed-lap duration when available. They follow the same wheel-level legal-surface and contact-event definitions as environment metrics and starter contact rewards. `createEvaluationTracker(result, { lapScope })` and `runEnvironmentEvaluation({ ..., lapScope })` accept `'since-start'` (default) or `'include-history'`. Since-start ignores laps already complete at tracker creation and captures the first subsequently observed completion with a valid `lapTelemetry.lastLapTime`. Include-history may initialize from the latest valid recorded lap in the initial snapshot. Snapshots do not contain a multi-lap history here; unavailable, negative or nonfinite durations stay `null`. Neither mode substitutes total elapsed time or best-lap time. `runEnvironmentEvaluation()` can accept compact base options with `result.stateOutput: 'none'`; it internally requests the minimal snapshot needed for evaluation bookkeeping. Direct tracker usage requires `state.snapshot`, so use `stateOutput: 'minimal'` or `full`. Evaluation metrics are not rewards.
|
|
472
480
|
|
|
473
481
|
`step(actions)` returns a JavaScript object designed for environment loops:
|
|
474
482
|
|
|
@@ -634,9 +642,11 @@ sensors: {
|
|
|
634
642
|
}
|
|
635
643
|
```
|
|
636
644
|
|
|
637
|
-
|
|
645
|
+
Global rays and `sensorsByDriver` overrides use the same normalization for observations, compact vectors, and specs. Partial `env.reset()` overrides preserve explicitly supplied per-ray lengths; `lengthMeters` / `defaultLengthMeters` changes affect rays that inherit a default length. Supplying a new `layout` or `anglesDegrees` replaces the inherited generated ray arrangement unless explicit `rays` are supplied in that override. Explicit angles also replace an inherited preset layout.
|
|
638
646
|
|
|
639
|
-
|
|
647
|
+
Ray precision defaults to `driver`. Driver precision is the active model-facing sensor contract and uses the indexed geometry and bounded sampled recovery described below, without debug-only refinement. `precision: 'debug'` is available for clearly labeled diagnostics with additional edge refinement, but debug precision must not be displayed as model senses unless the policy is also running with that exact sensor config.
|
|
648
|
+
|
|
649
|
+
Surface channels are computed only when requested. `kerb` is legal racing surface. `illegalSurface` reports the first grass or gravel surface crossed by the ray. Requested surface-ray channels are part of the active object observation and compact vector schema for both default and physical-driver profiles. Vector surface fields always follow `kerb`, then `illegalSurface` when enabled, irrespective of the channel array order; this also applies to per-driver overrides and schema-free array/Float32 output. `precision: 'driver'` road-edge, kerb, and illegal-surface channels use the canonical indexed track projection path with driver-precision validation, preserving zero-distance origin surface hits and avoiding debug-only refinement in policy inputs. Requested surface channels preserve an origin hit at `distanceMeters: 0` even if another requested channel has no visible boundary. Barrier-origin recovery rays stay on direct indexed band results whenever the tracer can already prove the misses and the first non-barrier illegal-surface entry; ambiguous pit-connector, pit-lane-origin, and remaining unusual geometry cases may still use sampled validation internally, but the returned ray object shape is unchanged. Barrier walls are not exposed as a model-facing ray target; active ray objects, vectors, schemas, and visualizations must not expose a `barrier` ray channel. Barrier contact is enforced as a shared destruction boundary in both physics modes at the rendered wall's inner face and reported through events, snapshots, metrics, and per-driver episode state.
|
|
640
650
|
|
|
641
651
|
Nearby-car observations are car-relative. When advanced-mode velocity fields are present, `closingRateMetersPerSecond` and `timeToContactSeconds` use the actual 2D velocity vector instead of assuming the car is moving in its heading direction, so slip and recovery states remain physically accurate:
|
|
642
652
|
|
|
@@ -722,7 +732,7 @@ Each `car.lapTelemetry` snapshot includes current/last/best lap and sector timin
|
|
|
722
732
|
|
|
723
733
|
`totalLaps` is optional. Values are normalized to a finite positive integer, with invalid or non-positive input falling back to a one-lap race.
|
|
724
734
|
|
|
725
|
-
`trackSeed` is optional. If omitted, each mounted browser simulator creates a fresh procedural circuit. Passing a `trackSeed` makes the track deterministic; repeated procedural
|
|
735
|
+
`trackSeed` is optional. If omitted, each mounted browser simulator creates a fresh procedural circuit. Passing a `trackSeed` makes the track deterministic; repeated procedural contracts use a bounded eight-entry least-recently-used cache so multiple mounts can reuse the same immutable generated track definition when the resolved generation options also match, while evicted contracts regenerate deterministically. Callers should treat `createProceduralTrack(seed, options)` results as read-only. Calling `restart({ trackSeed })` rebuilds the simulation on the deterministic circuit for that seed.
|
|
726
736
|
|
|
727
737
|
`trackGeneration` is optional and applies only when a procedural `trackSeed` is used. It accepts profile presets plus explicit overrides:
|
|
728
738
|
|
|
@@ -771,6 +781,7 @@ Each driver must have:
|
|
|
771
781
|
customFields: {
|
|
772
782
|
Specialty: 'Late braking',
|
|
773
783
|
},
|
|
784
|
+
driverModel: { id: 'host-policy-v2' },
|
|
774
785
|
}
|
|
775
786
|
```
|
|
776
787
|
|
|
@@ -785,6 +796,7 @@ Fields:
|
|
|
785
796
|
- `tire`: `S`, `M`, or `H`.
|
|
786
797
|
- `raceData`: short project/radio lines shown in the UI.
|
|
787
798
|
- `customFields`: optional driver overview fields. Use an object or an array of `{ label, value }`.
|
|
799
|
+
- `driverModel`: optional opaque host metadata. PaddockJS preserves the value on normalized driver data and internal car state for host/controller bookkeeping, but does not interpret it or include it in model observations.
|
|
788
800
|
|
|
789
801
|
## Entry Shape
|
|
790
802
|
|
|
@@ -805,6 +817,7 @@ Entries are optional. If omitted, defaults are used.
|
|
|
805
817
|
customFields: {
|
|
806
818
|
Style: 'Patient race manager',
|
|
807
819
|
},
|
|
820
|
+
driverModel: { id: 'driver-policy-v2' },
|
|
808
821
|
},
|
|
809
822
|
vehicle: {
|
|
810
823
|
id: 'budget-bb01',
|
|
@@ -820,6 +833,7 @@ Entries are optional. If omitted, defaults are used.
|
|
|
820
833
|
{ label: 'Aero kit', value: 'Low drag' },
|
|
821
834
|
{ label: 'Battery map', value: 'Conservative' },
|
|
822
835
|
],
|
|
836
|
+
driverModel: { id: 'vehicle-policy-v2' },
|
|
823
837
|
},
|
|
824
838
|
team: {
|
|
825
839
|
id: 'ledger-racing',
|
|
@@ -836,7 +850,7 @@ Entries are optional. If omitted, defaults are used.
|
|
|
836
850
|
}
|
|
837
851
|
```
|
|
838
852
|
|
|
839
|
-
Entries match drivers by `driverId`. Entry `driverId` values must be unique. `driverNumber` is optional; if omitted, PaddockJS falls back to stable grid order.
|
|
853
|
+
Entries match drivers by `driverId`. Entry `driverId` values must be unique. `driverNumber` is optional; if omitted, PaddockJS falls back to stable grid order. All resolved numbers must be unique, including explicit numbers colliding with stable grid-order fallbacks. A collision rejects normalization/mounting with the number and conflicting driver IDs; PaddockJS does not silently renumber entries. Opaque `driverModel` metadata may be supplied on the top-level driver, entry driver blueprint, or entry vehicle blueprint; precedence is top-level driver, then entry driver, then entry vehicle.
|
|
840
854
|
The car/driver overview primarily renders the existing driver and vehicle rating components from `driver` and `vehicle`. `team` is optional team-level metadata for race identity and pit behavior; `color` defaults to the driver/car color, and `icon` defaults from the team name or timing code. `team.pitCrew` may define `speed`, `consistency`, and `reliability` from `0` to `1`; these values affect optional pit-stop variability when `rules.modules.pitStops.variability.enabled` is true. The timing tower uses the team icon in the car/team column. `driver.customFields`, `vehicle.customFields`, and top-level driver `customFields` are accepted as extra metadata after those defined components.
|
|
841
855
|
|
|
842
856
|
## Rating Rules
|
|
@@ -852,6 +866,8 @@ Rating conversion lives in:
|
|
|
852
866
|
- `src/data/driverData.js`
|
|
853
867
|
- `src/data/vehicleData.js`
|
|
854
868
|
|
|
869
|
+
The built-in racing policy consumes existing driver ratings. `pace` and `racecraft` affect ability, `consistency` affects line and speed judgement precision, and `aggression` plus `riskTolerance` affect willingness to use smaller margins. `patience` remains part of traffic decisions. Normalized consistency is retained in internal vehicle state; missing legacy consistency defaults to the neutral constructor value `0.75`. No new host option, action, snapshot or environment-observation field is introduced. Existing legacy vehicle fallbacks remain: when explicit vehicle coefficients are omitted, car creation derives power from pace and grip partly from racecraft. Use explicit vehicle entries for comparisons that must hold the car identical across skills. Explicit vehicle ratings determine the same physical setup across driver skill levels.
|
|
870
|
+
|
|
855
871
|
## Callback Contract
|
|
856
872
|
|
|
857
873
|
PaddockJS does not directly own host navigation. The host should provide:
|
|
@@ -880,6 +896,8 @@ Optional lifecycle callbacks:
|
|
|
880
896
|
|
|
881
897
|
`onRaceEvent` receives simulation events such as `contact`, `penalty`, `track-limits`, `pit-lane-speeding`, `car-dnf`, `safety-car`, `green-flag`, `start-lights-out`, and `race-finish`. `car-dnf` currently reports stalled off-track retirements with `reason: 'stalled-off-track'`. `contact` events include metadata from the production body collision solver: `firstShapeId`, `secondShapeId`, `contactType`, `depth`, and `timeOfImpact`. Lifecycle callback errors are caught; if `onError` exists, it receives `{ callback: name }` context for callback failures.
|
|
882
898
|
|
|
899
|
+
Normal browser ticker frames and expert-driven frames use the same lifecycle path. `onLapChange` and `onRaceFinish` therefore run in either mode, duplicate delivery of one simulation event is suppressed, and separate same-time events remain distinct when their stable event, penalty, driver, car, or sequence identifiers differ.
|
|
900
|
+
|
|
883
901
|
Race snapshots include a top-level `penalties` array. Each penalty entry includes `id`, `type`, `driverId`, `strictness`, `status`, `penaltySeconds`, `pendingPenaltySeconds`, `serviceType`, `serviceRequired`, `serviceServedAt`, `appliedAt`, `cancelledAt`, `unserved`, `positionDrop`, `gridDrop`, `disqualified`, `consequences`, `lap`, `at`, and rule-specific context such as `otherCarId`, `aheadDriverId`, `atFaultDriverId`, `sharedFault`, and `impactSpeedKph` for collision penalties or `speedKph`, `speedLimitKph`, `excessKph`, and `pitLanePart` for pit-lane speeding penalties. Clear rear contact has one at-fault driver; unclear meaningful contact records one shared-fault penalty per involved driver. Multiple time penalties for the same driver are summed into the car snapshot's `penaltySeconds` and adjusted finish/classification time.
|
|
884
902
|
|
|
885
903
|
Each full public car snapshot includes `trackState` for the car-center track classification used by runtime readouts and environment surfaces. Its stable public fields are `distance`, `signedOffset`, `crossTrackError`, `surface`, `inPitLane`, `pitLanePart`, `pitBoxId`, `curvature`, and `heading`. `pitLanePart` and `pitBoxId` are `null` when the car is not classified on a pit-lane part or pit box. The existing flat `signedOffset`, `crossTrackError`, `surface`, `inPitLane`, `pitLanePart`, and `pitBoxId` fields remain as convenience mirrors for common UI and host checks.
|
|
@@ -931,6 +949,7 @@ Current UI options:
|
|
|
931
949
|
ui: {
|
|
932
950
|
layoutPreset: 'standard',
|
|
933
951
|
cameraControls: 'external',
|
|
952
|
+
viewControls: 'switch',
|
|
934
953
|
showFps: true,
|
|
935
954
|
showTimingTower: true,
|
|
936
955
|
showTelemetry: true,
|
|
@@ -960,6 +979,7 @@ debug: {
|
|
|
960
979
|
|
|
961
980
|
- `layoutPreset`: `'standard'` or `'left-tower-overlay'`. The overlay preset creates a left broadcast gutter inside the race canvas, places the timing tower in that gutter at the same width as the default timing-board column, and frames the camera around the remaining race-view area. Camera controls are external by default. In the combined shell, the project/radio lower-third stays inside the race window and can render over the timing sidebar.
|
|
962
981
|
- `cameraControls`: `'embedded'`, `'external'`, or `false`. The default is external so camera controls do not cover the race view. Embedded controls render inside the race canvas only when explicitly requested. External controls are mounted with `mountCameraControls(root)` or included in package-owned workbench templates. The generated controls include mode buttons, zoom buttons, and a `Mute banners` toggle that temporarily disables project/radio lower-thirds while active. `ui.driverCamera: true` adds the opt-in `driver` camera button; it is omitted from generated controls by default. A browser-playback speed button is optional for normal camera controls through `ui.simulationSpeedControl: true`; the complete race workbench enables it by default and cycles `1x`, `2x`, `3x`, `4x`, `5x`, `10x`, then back to `1x`. `false` leaves camera controls unrendered, though callers can still drive selection through controller methods.
|
|
982
|
+
- `viewControls`: `'switch'` (default) or `'toggle'`. Controls the separately mounted Grid Walk view surface and built-in compositions: either Grid Walk / Race buttons or one Start race / Stop race button. Start enters or resumes Race; Stop pauses it and returns to the original display grid without resetting race state. Unknown values normalize to `'switch'`; `restart({ ui: { viewControls: ... } })` updates the mounted variant. Grid Walk must be enabled for either variant to appear.
|
|
963
983
|
- `showFps`: controls whether the race canvas renders the FPS readout.
|
|
964
984
|
- `showRaceDataPanel`: controls whether the precombined shell includes the project/radio lower-third inside the race window.
|
|
965
985
|
- `showTimingTower`, `showTelemetry`: reserved component visibility flags for host layout decisions.
|
|
@@ -1074,7 +1094,7 @@ After every car completes `totalLaps`, `getSnapshot()` returns:
|
|
|
1074
1094
|
|
|
1075
1095
|
Cars also include `team`, `speedKph`, `distanceMeters`, `gapAheadMeters`, `gapAheadSeconds`, `intervalAheadSeconds`, `leaderGapSeconds`, `gapAheadLaps`, `intervalAheadLaps`, `leaderGapLaps`, `finished`, `finishTime`, `finishRank`, `status`, `raceStatus`, `wavedFlag`, `destroyed`, `destroyReason`, `destroyedAt`, `dnf`, `dnfReason`, `dnfAt`, `dnfOrder`, `penaltySeconds`, `adjustedFinishTime`, `classifiedRank`, and `lapTelemetry`. A car that has crossed the finish before the whole race is complete exposes `status: 'waved-flag'`, `raceStatus: 'waved-flag'`, and `wavedFlag: true`; the timing order keeps those cars frozen by provisional `finishRank` until final classification is available, and later barrier contact or stalled-off-track detection does not convert that result into DNF. A destroyed/out-of-race car exposes `dnf: true`, keeps compatibility `raceStatus: 'destroyed'`, shows `DNF` in the timing tower, renders faded/gray in the race canvas, and is ordered below running cars by `dnfOrder`. Stalled off-track DNFs use `dnfReason: 'stalled-off-track'` and `destroyed: false`; barrier DNFs keep `destroyed: true`. Classification entries also expose `gapLaps`, `intervalLaps`, `positionDrop`, `disqualified`, and DNF metadata. `gapAheadSeconds` and `intervalAheadSeconds` are the interval to the car directly ahead when both cars are on the same lead-lap cycle; `gapAheadLaps` and `intervalAheadLaps` are the whole-lap deficit to that car when it is at least one lap ahead. `leaderGapSeconds` is the same-lead-lap gap to P1. `leaderGapLaps` is the whole-lap deficit to P1. Seconds gaps are measured from the timestamp difference when both cars crossed the same hidden timing line, with fallback interpolation only when no shared timing line exists yet. The timing tower prefers lap labels such as `+1` over seconds when the relevant lap gap is positive, shows `WAVED` for a finished car before full race classification, and shows `DNF` for retired cars. The first car to finish sets a provisional `raceControl.winner` and receives a `car-finish` event, but the race keeps running until every race participant has finished or is DNF. A DNF car restored before final classification re-enters live timing and must finish; after final classification, resurrection does not reopen the race. Final `raceControl.classification` sorts finishers by `finishTime + penaltySeconds` after converting unserved drive-through and stop-go penalties, applies position-drop and disqualification consequences, then appends DNF entries after finishers.
|
|
1076
1096
|
|
|
1077
|
-
When `rules.modules.pitStops.enabled` is true, cars also expose `pitIntent`, `pitStop`, and `usedTireCompounds`. `pitIntent` and `pitStop.intent` use `0` for no request, `1` for an opportunistic request that stays active until a free-enough entry window appears, and `2` for a committed request that enters at the next pit-entry window even if pit capacity or gap checks would block mode `1`. `pitStop.status` is one of `pending`, `entering`, `queued`, `servicing`, `exiting`, or `completed`; `pitStop.phase` may be `entry`, `queue`, `queue-release`, `penalty`, `service`, `exit`, or `null`. It also includes the assigned shared service `boxId`/`boxIndex`, the driver's `garageBoxId`/`garageBoxIndex`, optional team id/color, planned pit-call race distance, physical pit-entry race distance, service seconds remaining, `penaltyServiceRemainingSeconds`, `penaltyServiceTotalSeconds`, `servingPenaltyIds`, target tire, optional `serviceProfile`, queue flag, and completed stop count. Host APIs and expert actions can choose the target tire compound with `setPitIntent(driverId, intent, targetCompound)` or `pitCompound`; invalid compounds are rejected. Automatic pit calls can share the same entry lap, but a pending mode `1` car only joins when the active pit-lane population is below `maxConcurrentPitLaneCars` and the nearest active pit-lane car is at least `minimumPitLaneGapMeters` ahead; mode `2` bypasses that opportunistic-capacity gate and commits to the automatic pit-entry route. Team-mates share one service area; every car enters through the working-lane queue point first. If the service area is free, the queue point behaves as a rolling gate and the car immediately follows a short `queue-release` route into service without exposing `queued`; if the area is occupied, the car exposes `status: queued` until servicing, queue-release, and just-exiting cars have physically cleared the active spot. Completed stops can be re-armed by later tire condition or host intent, so `stopsCompleted` can increase beyond one during longer races. `pitLaneSpeedLimitKph` applies only while the automatic route is inside the straight main pit lane/working lane; entry and exit connector roads remain legal pit surfaces but are not limiter zones. The automatic route brakes before the main lane start, travels along the main fast lane, and crosses into the team service area only near the assigned stop. If `phase` is `penalty`, the car is stationary before tire service and the browser renderer shows a red `+Ns` countdown above the car; if `phase` is `service`, it shows a yellow `Ns` countdown for the normal pit-service time. Optional pit-stop variability records the resolved `serviceProfile`; `pitStops.variability.perfect: true` forces the configured default service time for deterministic training. `usedTireCompounds` starts with the initial tire and receives the tire selected by completed automatic pit stops.
|
|
1097
|
+
When `rules.modules.pitStops.enabled` is true, cars also expose `pitIntent`, `pitStop`, and `usedTireCompounds`. `pitIntent` and `pitStop.intent` use `0` for no request, `1` for an opportunistic request that stays active until a free-enough entry window appears, and `2` for a committed request that enters at the next pit-entry window even if pit capacity or gap checks would block mode `1`. `pitStop.status` is one of `pending`, `entering`, `queued`, `servicing`, `exiting`, or `completed`; `pitStop.phase` may be `entry`, `queue`, `queue-release`, `penalty`, `service`, `exit`, or `null`. It also includes the assigned shared service `boxId`/`boxIndex`, the driver's `garageBoxId`/`garageBoxIndex`, optional team id/color, planned pit-call race distance, physical pit-entry race distance, service seconds remaining, `penaltyServiceRemainingSeconds`, `penaltyServiceTotalSeconds`, `servingPenaltyIds`, target tire, optional `serviceProfile`, queue flag, and completed stop count. Host APIs and expert actions can choose the target tire compound with `setPitIntent(driverId, intent, targetCompound)` or `pitCompound`; invalid compounds are rejected. Automatic pit calls can share the same entry lap, but a pending mode `1` car only joins when the active pit-lane population is below `maxConcurrentPitLaneCars` and the nearest active pit-lane car is at least `minimumPitLaneGapMeters` ahead; mode `2` bypasses that opportunistic-capacity gate and commits to the automatic pit-entry route. Team-mates share one service area; every car enters through the working-lane queue point first. If the service area is free, the queue point behaves as a rolling gate and the car immediately follows a short `queue-release` route into service without exposing `queued`; if the area is occupied, the car exposes `status: queued` until servicing, queue-release, and just-exiting cars have physically cleared the active spot. Completed stops can be re-armed by later tire condition or host intent, so `stopsCompleted` can increase beyond one during longer races. Automatic tire-condition rearming chooses another available compound rather than silently targeting the current tire again; explicit host/model target compounds are preserved. `pitLaneSpeedLimitKph` applies only while the automatic route is inside the straight main pit lane/working lane; entry and exit connector roads remain legal pit surfaces but are not limiter zones. The automatic route brakes before the main lane start, travels along the main fast lane, and crosses into the team service area only near the assigned stop. If `phase` is `penalty`, the car is stationary before tire service and the browser renderer shows a red `+Ns` countdown above the car; if `phase` is `service`, it shows a yellow `Ns` countdown for the normal pit-service time. Optional pit-stop variability records the resolved `serviceProfile`; `pitStops.variability.perfect: true` forces the configured default service time for deterministic training. `usedTireCompounds` starts with the initial tire and receives the tire selected by completed automatic pit stops.
|
|
1078
1098
|
|
|
1079
1099
|
Car snapshots expose `surface`, `signedOffset`, `crossTrackError`, `inPitLane`, `pitLanePart`, `pitBoxId`, and `pitLaneCrossTrackError` so hosts can distinguish main-circuit running from pit entry, fast lane, working lane, pit exit, and service-box states without recalculating geometry. `surface` is the worst current wheel/contact-patch surface, not only the center point. Each car also exposes `wheels`, with one entry per contact patch:
|
|
1080
1100
|
|
|
@@ -1094,6 +1114,8 @@ Car snapshots expose `surface`, `signedOffset`, `crossTrackError`, `inPitLane`,
|
|
|
1094
1114
|
|
|
1095
1115
|
Use `wheels` when a host needs exact debug overlays, per-wheel surface labels, or track-limit state. A car is track-limit illegal only when all four wheel contact patches are fully outside the same white line; a mixed state such as one wheel on gravel and one wheel still inside the white line reports the worse physics surface but does not count as a track-limit violation.
|
|
1096
1116
|
|
|
1117
|
+
Supported mutable snapshot metadata is caller-owned: penalty `consequences` entries, `usedCompounds`, and `wheelOffsets`; car team `pitCrew` and `pitCrewStats`; and pit-stop `serviceProfile.pitCrew` are copied. Mutating them does not change live enforcement or service behavior, and later live changes do not alter prior snapshots. Optional fields remain absent or undefined as supplied; no crew defaults are added by serialization. The live track reference described below is an explicit exception.
|
|
1118
|
+
|
|
1097
1119
|
## Track And Lap Telemetry Snapshot
|
|
1098
1120
|
|
|
1099
1121
|
Every built track is automatically divided into three equal sectors. `snapshot.track.sectors` exposes:
|
|
@@ -1165,7 +1187,7 @@ const simulator = await mountF1Simulator(root, options);
|
|
|
1165
1187
|
|
|
1166
1188
|
Controller methods:
|
|
1167
1189
|
|
|
1168
|
-
- `destroy()`: removes listeners, destroys PixiJS runtime, clears the host root.
|
|
1190
|
+
- `destroy()`: removes listeners, destroys PixiJS runtime, clears the host root, and releases that root for a future mount. A live all-in-one root rejects a second mount; failed initialization clears and releases it automatically.
|
|
1169
1191
|
- `restart(nextOptions)`: restarts the simulation with merged non-asset options. Use `destroy()` and mount a new simulator to change bundled or host-provided asset URLs.
|
|
1170
1192
|
- `selectDriver(driverId)`: selects and focuses a driver.
|
|
1171
1193
|
- `setSafetyCarDeployed(deployed)`: toggles safety car state.
|
|
@@ -1195,15 +1217,16 @@ Composable controllers additionally expose the canonical composable mounting API
|
|
|
1195
1217
|
- `mountTelemetryPanel(root, { includeOverview })`: renders the package-owned telemetry stack template. The stack is only a composition of detached telemetry surfaces, owns vertical scrolling when its host is shorter than its contents, and includes the car/driver overview by default unless `includeOverview: false` is passed or `ui.telemetryIncludesOverview` is `false`.
|
|
1196
1218
|
- `mountTelemetryCore(root)`: renders selected-car scalar telemetry only.
|
|
1197
1219
|
- `mountTelemetrySectors(root)`: renders the live sector progress graph only.
|
|
1220
|
+
- `mountTelemetrySectorBanner(root)`: renders the selected driver's compact sector-progress lower-third only.
|
|
1198
1221
|
- `mountTelemetryLapTimes(root)`: renders current, last, and best lap timing only.
|
|
1199
1222
|
- `mountTelemetrySectorTimes(root)`: renders last and best sector timing only.
|
|
1200
1223
|
- `mountRaceTelemetryDrawer(root, { timingTowerVerticalFit, drawerInitiallyOpen, raceDataTelemetryDetail, responsiveNarrowLayout })`: renders a template that combines an external top control row, race canvas, embedded timing tower, the project/radio lower-third, top steward message, safety-car control, and a right-side telemetry drawer. The control row contains camera controls, the `1x..10x` simulation-speed toggle, banner mute, the safety-car button, and the telemetry toggle so those controls do not cover the race view. Pass `raceDataTelemetryDetail: true` when this template should put compact S1/S2/S3 detail in the project lower-third instead of mounting a second sector popup. The drawer embeds the same package-owned telemetry stack used by `mountTelemetryPanel()` and takes layout space from the race window when opened on wide containers; narrow drawer reveal behavior is enabled by default, stacks the top workbench controls into readable full-width rows, and can be disabled with `responsiveNarrowLayout: false`.
|
|
1201
1224
|
- `mountCarDriverOverview(root)`: renders the package-owned car/driver overview as a separate component with a Car/Driver toggle, center visual, and linked stat cells from the existing driver/vehicle rating components.
|
|
1202
1225
|
- `mountRaceDataPanel(root)`: renders the project/race-data lower-third as a separate component for hosts that intentionally want it outside the race canvas.
|
|
1203
1226
|
- `querySelector(selector)` / `querySelectorAll(selector)`: search across the mounted package-owned composable roots. These exist for integration tests and advanced host glue; ordinary hosts should prefer explicit controller methods and mounted component roots.
|
|
1204
|
-
- `start()`: initializes PixiJS, binds mounted controls, and starts the simulation loop.
|
|
1227
|
+
- `start()`: initializes PixiJS, binds mounted controls, and starts the simulation loop. Concurrent callers await the same initialization. If initialization fails, partial runtime state is destroyed and a later call may retry.
|
|
1205
1228
|
|
|
1206
|
-
Mount component roots before calling `start()`. If a component is not mounted, the runtime skips that UI surface instead of requiring hidden placeholder DOM. Every public mounted surface renders lightweight static package markup and a surface-specific `data-paddock-loading` overlay immediately. `start()` removes those overlays only after PixiJS, assets, controls, initial DOM readouts, and the first frame have initialized. If `restart(nextOptions)` is called before `start()`, mounted component markup is refreshed from the new resolved options so copy, component visibility, driver data, and theme changes do not leave stale pre-start HTML behind.
|
|
1229
|
+
Mount component roots before calling `start()`. Each surface key owns one root: remounting that surface into another root clears the prior root, and different surfaces cannot share one root. If a component is not mounted, the runtime skips that UI surface instead of requiring hidden placeholder DOM. Every public mounted surface renders lightweight static package markup and a surface-specific `data-paddock-loading` overlay immediately. `start()` removes those overlays only after PixiJS, assets, controls, initial DOM readouts, and the first frame have initialized. If `restart(nextOptions)` is called before `start()`, mounted component markup is refreshed from the new resolved options so copy, component visibility, driver data, and theme changes do not leave stale pre-start HTML behind.
|
|
1207
1230
|
|
|
1208
1231
|
This loading overlay is the package's client-side startup state. It begins after the PaddockJS JavaScript bundle has executed and a mount function has run. Hosts that need an instant placeholder before package JavaScript loads should render host-owned fallback HTML/CSS around or inside the mount root, then let `mountF1Simulator()` or the composable `mount*()` calls replace that root content when the package starts.
|
|
1209
1232
|
|
package/docs/data_model.md
CHANGED
|
@@ -10,7 +10,7 @@ The race loop does not read raw rating numbers directly. It receives constructor
|
|
|
10
10
|
|
|
11
11
|
## Rating Scale
|
|
12
12
|
|
|
13
|
-
Driver and vehicle stats use a `0-100` rating scale.
|
|
13
|
+
Driver and vehicle stats use a `0-100` rating scale. The common bounds, numeric validation, clamping, and conversion live in `src/data/ratingScale.js`; each data class owns its stat definitions and error labels.
|
|
14
14
|
|
|
15
15
|
- `50` is neutral.
|
|
16
16
|
- Values above `50` add to the class default.
|
|
@@ -55,6 +55,7 @@ Driver stats:
|
|
|
55
55
|
```js
|
|
56
56
|
{
|
|
57
57
|
ratings,
|
|
58
|
+
customFields,
|
|
58
59
|
pace,
|
|
59
60
|
racecraft,
|
|
60
61
|
consistency,
|
|
@@ -94,7 +95,7 @@ Vehicle stats:
|
|
|
94
95
|
- `dragEfficiency`: Converts inversely to `dragCoefficient`; higher rating means less drag.
|
|
95
96
|
- `mechanicalGrip`: Converts to `tireGrip`.
|
|
96
97
|
- `weightControl`: Converts inversely to `mass`; higher rating means lower mass.
|
|
97
|
-
- `tireCare`: Converts to
|
|
98
|
+
- `tireCare`: Converts to the tire-wear divisor used by both vehicle physics modes; higher values reduce tire-energy loss when tire degradation is enabled.
|
|
98
99
|
|
|
99
100
|
`VehicleData.toConstructorArgs()` returns:
|
|
100
101
|
|
|
@@ -103,6 +104,7 @@ Vehicle stats:
|
|
|
103
104
|
id,
|
|
104
105
|
name,
|
|
105
106
|
ratings,
|
|
107
|
+
customFields,
|
|
106
108
|
powerNewtons,
|
|
107
109
|
brakeNewtons,
|
|
108
110
|
downforceCoefficient,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Efficiency and maintainability follow-up — 2026-10-13
|
|
2
|
+
|
|
3
|
+
This continues the isolated v11 improvement pass after the user requested sustained hardware-browser measurements and a broader cleanup audit. The starting point includes the earlier AI, lifecycle and motion fixes, not untouched v11. Source, scripts and both built consumers were preserved under `/tmp/paddock-v11-efficiency-20261013-pass2/` before edits. Work remains uncommitted on `v11-improvements`; original v11 is preserved and no Git/GitHub mutation is part of this pass.
|
|
4
|
+
|
|
5
|
+
## Checkable scope
|
|
6
|
+
|
|
7
|
+
- [x] Inspect live branch/worktree state, current ownership, existing cleanup records and test contracts.
|
|
8
|
+
- [x] Scan package/runtime/environment/data/config/UI/rendering and both consumers for large modules, duplication and unused paths; deeply inspect concrete candidates and callsites.
|
|
9
|
+
- [x] Preserve the exact starting source and builds and identify the native GPU.
|
|
10
|
+
- [x] Implement bounded work reduction and ownership cleanup with focused tests and exact-output comparisons.
|
|
11
|
+
- [x] Complete matched hardware frame-time, CPU and retained-memory measurements.
|
|
12
|
+
- [x] Run standard performance contracts and both package gates against the final candidate.
|
|
13
|
+
- [x] Verify final interactions, update Linear and record remaining limits.
|
|
14
|
+
|
|
15
|
+
This is an audit of the full module map and demonstrated risks, not a claim that every line is optimal. Historical [cleanup coverage](cleanup_coverage.md) and [the September audit](maintainability_audit_2026-09-08.md) informed candidate selection; their old evidence was not treated as current proof. Supported compatibility barrels remain intact. Large geometry/surface kernels retain their current boundaries because splitting them by line count would add numerical/invalidation risk without a demonstrated benefit.
|
|
16
|
+
|
|
17
|
+
## Findings and target ownership
|
|
18
|
+
|
|
19
|
+
| Finding | Change | Proof |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| Overlay synchronization observes the class/style attributes it writes, risking observer → RAF feedback | Compare current values before writing/removing clearance state; retain geometry and timing-board width | Modeled observer convergence plus native-browser idle follow-up |
|
|
22
|
+
| Replay halos rebuild fixed Graphics geometry each frame | Cache tint/effective opacity, keeping all pose and sprite/label updates | 120 moving frames produce zero additional halo rebuilds; changed appearance and removal/recreation covered |
|
|
23
|
+
| Timing refreshes rewrite unchanged attributes/classes/styles and append every existing row | Guard writes against DOM values and reconcile keyed positions only when order changes | Gap-only updates produce one text write, no row moves and preserve focus; insertion, removal, order and penalty changes covered |
|
|
24
|
+
| Pooled render snapshots still build rank-wrapper/order collections | Direct ranked-then-index-sorted writer with per-target weak scratch | Real-simulation order/identity tests and exact baseline/current frames including excluded participants, DNF and classification |
|
|
25
|
+
| Nearby projections and radar lookup are rebuilt per controlled driver | Lazy preparation shared within one observation build; fresh returned records | Multi-driver flags, retained results, limits, ties, car/ghost IDs and direct-vector equivalence |
|
|
26
|
+
| Nearby candidates allocate complete payloads before losing the shortlist | Admit by squared distance/order first | Crowded fields, exact radius boundaries, stable ties and zero limits |
|
|
27
|
+
| Advanced braking previews repeatedly query identical capacity endpoints | Decision-local lazy reuse of speed-zero and maximum-speed endpoints | Exact controls/trajectories on four tracks; tire/DRS/surface mutation tests; measured query count reduction |
|
|
28
|
+
| Display destruction policy is duplicated between app and track layers | `src/rendering/displayLifecycle.js` owns child destruction, preserving shared textures | Real Pixi nested destruction and repeated cleanup |
|
|
29
|
+
| Workbench DOM mechanics and sense presentation expand broad entrypoints | Focused controls/disclosure modules and Policy Runner `senseReadout.js` | Existing lifecycle/control suites, focused missing/vector/physical sense cases and full browser smoke |
|
|
30
|
+
| Two driver constants and a now-obsolete internal sensor wrapper have no consumers | Remove only proven internal dead declarations after named/import/export screening | Public types, import graph and package gates; no public barrel removal |
|
|
31
|
+
|
|
32
|
+
The app retains race state, selection, options, camera invalidation and layout orchestration. Control modules accept explicit DOM references, signal and command callbacks, not the whole mutable runtime. Sense presentation reads the active observation only. Snapshot scratch adds no public fields and releases participant references; nearby contexts never persist across builds or mutable snapshots.
|
|
33
|
+
|
|
34
|
+
`F1SimulatorApp.js` decreases from 1,347 to 1,275 lines; the showcase entrypoint decreases from 3,211 to 3,069. These reductions describe responsibility placement, not a performance metric. Numerical kernels and supported facades are not removed merely to improve counts.
|
|
35
|
+
|
|
36
|
+
## Semantic and work-reduction evidence
|
|
37
|
+
|
|
38
|
+
The first serializer/sensor cleanup preserves **5,558,802 serialized bytes** exactly across 160 retained environment results, eight resets, 50 render frames and 50 ghost observations. SHA-256: `ac2bd02e4de2d52498e500f03b5d3d4960a86f0ddd8f4ae2250a739731e4845a`.
|
|
39
|
+
|
|
40
|
+
Advanced endpoint reuse preserves exact controls and trajectory/observation hashes on the canonical track and procedural seeds 12, 7110 and 7203, each with 1,200 steps and eight advanced cars, including changes to tires, DRS and wheel surfaces between decisions. Lateral-capacity queries fall **1,273,206 → 637,075 (49.96%)**. One real driver decision falls **53 → 14**, with the interior query sequence unchanged. Counting instrumentation is confined to temporary copies; actual tire-force kernels are unchanged.
|
|
41
|
+
|
|
42
|
+
Three alternating fresh-process runs of the same 10,000-step, 20-car advanced workload report baseline totals 2,290.6 / 2,222.9 / 2,243.6 ms and candidate totals 1,873.5 / 1,869.6 / 1,909.9 ms. Median falls 2,243.6 → 1,873.5 ms, about **16.5%**. This is a measured result for this workload on this machine, not a universal CPU/FPS speedup. Other desktop development activity remains a timing-noise source.
|
|
43
|
+
|
|
44
|
+
## Hardware-browser measurements
|
|
45
|
+
|
|
46
|
+
An isolated headed Chrome 154 session reports `ANGLE Metal Renderer: Apple M4 Pro`. Sampling uses a 1,280 × 800 viewport and device/canvas density 2, the built complete workbench, fixed race/track seeds, 60-second intervals, and real Pixi draw callbacks. Each case starts after exactly 600 fixed simulation steps, with matching car-state SHA-256 across sources. Typed arrays bound instrumentation; hooks and observers are restored afterward. Update-work samples measure the runtime callback, while CDP metrics cover broader page task/style/layout costs. Heap samples request GC outside frame-timing intervals.
|
|
47
|
+
|
|
48
|
+
| Mode | Baseline → final FPS | Draw p95 ms | Draw p99 ms | Gaps over 50 ms | Mean update work ms |
|
|
49
|
+
| --- | --- | --- | --- | --- | --- |
|
|
50
|
+
| Arcade 1x | 58.57 → 58.51 | 23.0 → 23.7 | 25.5 → 25.5 | 0 → 0 | 0.412 → 0.808 |
|
|
51
|
+
| Advanced 1x | 58.53 → 58.53 | 23.7 → 23.4 | 25.6 → 25.5 | 0 → 0 | 0.512 → 0.825 |
|
|
52
|
+
| Advanced 10x | 58.57 → 58.60 | 23.5 → 22.8 | 26.3 → 26.3 | 0 → 0 | 1.775 → 1.993 |
|
|
53
|
+
|
|
54
|
+
Frame pacing is maintained; these data do not establish a browser CPU or FPS improvement. Average callback work rises in these samples even as exact operation counts fall. Broader page task time is mixed: estimated task CPU is 22.09% → 24.82% arcade, 23.18% → 24.36% advanced and 27.71% → 27.12% advanced 10x. This is page task duration divided by elapsed time, not total process, device or GPU utilization. The final arcade sample overlapped the end of the demo smoke gate; a separate baseline/final repeat is retained to examine the variation rather than silently dropping results.
|
|
55
|
+
|
|
56
|
+
A subsequent arcade pair, without the package gate running, reports FPS 58.60 → 58.49, p95 22.8 → 19.3 ms, p99 25.5 → 25.4 ms, callback mean 0.444 → 0.697 ms and page task CPU 24.16% → 24.70%. The final repeat contains two gaps over 50 ms. The higher callback cost is therefore retained as a measured limitation, not dismissed as a single overlapping-gate artifact or hidden behind the simulation speedup.
|
|
57
|
+
|
|
58
|
+
An earlier uncontrolled 10x baseline had one 368 ms gap; its raw record remains in the evidence. A diagnostic sample that changed browser density after mounting is excluded because the Pixi canvas retained its old density. Neither sample is used to claim a speedup. The collaborative preview's SwiftShader results do not establish hardware performance.
|
|
59
|
+
|
|
60
|
+
A canonical stopped-runtime probe observes the complete panel for three seconds after settling: baseline performs **360 class writes**, the final candidate performs **zero class/style writes**, with the private ticker stopped and timing tower still **255 px** wide. This isolates the overlay observer feedback loop from normal racing activity.
|
|
61
|
+
|
|
62
|
+
## Verification and remaining limits
|
|
63
|
+
|
|
64
|
+
Focused control/startup/lifecycle, replay/disposal, observer, render-order, nearby and advanced pace tests pass. Independent review finds no ordering, aliasing, stale-cache or precision issue in the current simulation/environment changes. A nearby configuration with `maxCars: 0` still prepares one context; this minor overhead is retained to avoid widening the change.
|
|
65
|
+
|
|
66
|
+
`npm run check` passes **75 files, 1,195 fast tests**, with 93 intentionally deferred slow cases, public types, dry pack, fresh packed-consumer install/build, bundle boundaries, both consumer builds, quick Chromium smoke and complete demo smoke. The final standard runtime benchmark validates all **23 cases**. Three earlier alternating baseline/candidate runs also preserve every non-timing contract across all 23 cases; timing-valued diagnostic fields are explicitly excluded from equality checks. `npm run check:release` passes all **75 exhaustive batches / 1,288 tests**, four dependency audits with zero vulnerabilities, types/pack/consumers/builds, the full Chromium showcase matrix and complete demo smoke. Source changes preceded both final gates; subsequent changes only document evidence.
|
|
67
|
+
|
|
68
|
+
Native focus acceptance updates one actual timing gap text node with zero row moves and the same focused button. This guarantee concerns unchanged ordering; actual reordering can still blur a moved row and remains a preexisting interaction limitation. Native interaction at **390 × 844** additionally exercises telemetry open/close, timing reveal, Budget Buddy selection, interval-to-leader gap switching and Overview camera; document width remains 390 px with all ten rows present. The painted native screenshot is `output/playwright/final-narrow.png`.
|
|
69
|
+
|
|
70
|
+
After two warm-up mounts, **ten native GPU mount/destroy cycles** leave nodes **3,131 → 3,131**, listeners **94 → 94**, documents **1 → 1** and the existing page canvas count **2**. Post-GC heap rises 99.94 → 100.86 MB, so this does not claim zero heap growth. Each temporary controller mounted one hardware-rendered canvas and removed it on destroy. Private GPU/driver allocation retention is not quantified by these JavaScript/CDP metrics.
|
|
71
|
+
|
|
72
|
+
The final **five-minute, eleven-sample** advanced-mode soak performs ten seeded restarts with alternating driver selection and drawer state. Nodes remain within **3,131–3,135** and finish at **3,131**; listeners **94**, documents **1**, and canvases **2** are constant throughout. Heap starts at 74.32 MB immediately after restart, warms to 102.18 MB, briefly reaches 129.29 MB, then returns to 103.10 MB. Samples three through ten stay within **102.99–103.36 MB**; final heap is **103.36 MB**. This supports bounded retention over the measured window, not a guarantee for unlimited runtimes or all GPU allocations. The exhaustive package gate runs in parallel with the retention probe; it is excluded from native frame-time repeat measurements.
|
|
73
|
+
|
|
74
|
+
All temporary comparisons, raw profiles, browser records and inventory are under `/tmp/paddock-v11-efficiency-20261013-pass2/`; CLI probes live in ignored `output/playwright/`.
|
|
75
|
+
|
|
76
|
+
The broader numerical geometry/surface kernels remain large; the pass does not claim complete architectural decomposition. No new browser CPU/FPS gain is claimed, and the callback-cost increase remains a useful profiling follow-up. Existing release-only publishing/lock synchronization items in `v11_improvements.md` remain outside this optimization task. Original v11 stays clean at the approved starting commit, while all candidate changes remain uncommitted without an upstream.
|
|
77
|
+
|
|
78
|
+
The PaddockJS Linear project document was updated with task, implementation, verification and remaining limits: [v11 improvement pass](https://linear.app/mgiorgetti/document/v11-improvement-pass-racecraft-render-cost-and-lifecycle-reliability-b6c4a20b8501). Temporary browser/profiling servers are closed; the candidate demo remains available at `http://127.0.0.1:4181/`.
|
|
79
|
+
|
|
80
|
+
Subsequent skill-dependent physical-envelope tuning, timing-line boundary repair and measured limitations are documented in [limit driving, 2026-10-14](limit_driving_2026-10-14.md). Earlier results above describe their recorded candidate.
|
package/docs/getting-started.md
CHANGED
|
@@ -68,6 +68,8 @@ await mountF1Simulator(root, { drivers, entries });
|
|
|
68
68
|
|
|
69
69
|
That placeholder covers the time before the full PaddockJS bundle executes. Once the bundle runs, PaddockJS owns the root markup and shows its package loading overlays until the first real simulator frame is ready.
|
|
70
70
|
|
|
71
|
+
Plain static HTML pages cannot resolve bare npm imports such as `@inventure71/paddockjs/placeholder` directly in the browser without a bundler or import map. Static hosts should run `createPaddockLoadingPlaceholder()` in the site build script, inject the generated HTML into the static page, and copy `@inventure71/paddockjs/placeholder.css` into the static output. See [Startup Loading](loading.md) for the bundled and static-host patterns.
|
|
72
|
+
|
|
71
73
|
## CSS
|
|
72
74
|
|
|
73
75
|
Most browser bundlers consume the root package stylesheet automatically. If your host build does not, import the stylesheet explicitly from a browser entry:
|
|
@@ -100,6 +102,7 @@ If a package surface is constrained below its supported size, PaddockJS shows an
|
|
|
100
102
|
## Where To Go Next
|
|
101
103
|
|
|
102
104
|
- Use [Composable Layouts](composable-layouts.md) when the host needs separate package-owned surfaces.
|
|
105
|
+
- Use [Startup Loading](loading.md) when the host needs instant first paint, static HTML integration, or a clearer split between placeholders and runtime overlays.
|
|
103
106
|
- Use [Theming](theming.md) for runtime light/dark sync and token customization.
|
|
104
107
|
- Use [Data Contract](data_contract.md) for full driver, entry, rule, UI, callback, and asset option shapes.
|
|
105
108
|
- Use [Troubleshooting](troubleshooting.md) when a host build or browser smoke fails.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Grid Walk
|
|
2
|
+
|
|
3
|
+
Grid Walk is an opt-in browser presentation around the existing race. It stages the same cars on the same track, adds preparing crew, visitors walking along the grid and readable entry labels, and reuses the race project banner at the bottom. Both mounting APIs support it. Existing consumers continue to start in Race without Grid Walk UI.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import { createPaddockSimulator } from '@inventure71/paddockjs';
|
|
7
|
+
|
|
8
|
+
const simulator = createPaddockSimulator({
|
|
9
|
+
drivers: [{ id: 'surveyor', name: 'Surveyor', color: '#f05060' }],
|
|
10
|
+
entries: [{
|
|
11
|
+
driverId: 'surveyor',
|
|
12
|
+
presentation: {
|
|
13
|
+
name: 'Surveyor Field Workspace',
|
|
14
|
+
summary: 'Map field observations in a shared workspace.',
|
|
15
|
+
href: '/projects/surveyor',
|
|
16
|
+
linkLabel: 'View project',
|
|
17
|
+
},
|
|
18
|
+
}],
|
|
19
|
+
gridWalk: { enabled: true, initialView: 'grid-walk', ambientMotion: true },
|
|
20
|
+
ui: { viewControls: 'toggle' }, // Omit for the two-button Grid Walk / Race switch.
|
|
21
|
+
});
|
|
22
|
+
simulator.mountViewControls(document.getElementById('sim-view-controls'));
|
|
23
|
+
simulator.mountRaceCanvas(document.getElementById('sim-race'));
|
|
24
|
+
await simulator.start();
|
|
25
|
+
simulator.setView('race');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Host composition
|
|
29
|
+
|
|
30
|
+
The Grid Walk / Race switch is a separately mountable package component. Place `simulator.mountViewControls(controlsRoot)` outside the scene alongside `mountRaceCanvas(canvasRoot)`, before `await simulator.start()`. The equivalent function-style helper is `mountViewControls(controlsRoot, simulator)`, imported from the browser package. It uses the package's control styling and calls the same `setView()` API; it does not create another simulator. `ui.viewControls` selects `'switch'` (the default Grid Walk / Race buttons) or `'toggle'` (one ordinary action button). The toggle reads **Start race** in Grid Walk with a green fill and **Stop race** in Race with a red fill, using the semantic `theme.colors.greenFlag` / `redFlag` colors in both themes. Stop pauses exact progress and presents the original display grid; Start resumes it through the same lifecycle below, without restarting the race. Invalid option values fall back to `'switch'`. Both variants use the same `mountViewControls()` API and remain synchronized after `restart({ ui: { viewControls: ... } })`.
|
|
31
|
+
|
|
32
|
+
A standalone race canvas does not insert a mode switch. Composable hosts must mount the switch explicitly or provide their own buttons calling `setView()`. The all-in-one shell and `mountRaceTelemetryDrawer()` already compose the package switch above the scene when Grid Walk is enabled. Mounted switches share the active view, remain disabled until runtime startup completes, and hide when Grid Walk is disabled, including after a restart. The top entry selector and bottom project banner remain part of the scene. The selector is horizontally centered with centered entry text. The external workbench camera controls fill their available row, and action controls reserve the wider of their labels so Start/Stop or Telemetry/Close changes do not shift neighboring controls.
|
|
33
|
+
|
|
34
|
+
## Lifecycle and state
|
|
35
|
+
|
|
36
|
+
- `gridWalk.enabled` defaults to `false`. When enabled, `initialView` defaults to `'grid-walk'`; use `'race'` to begin playback with Grid Walk available afterward. `ambientMotion` defaults to `true`.
|
|
37
|
+
- `getView()` returns `'grid-walk'` or `'race'`. `setView(view)` returns `true` for an accepted change and `false` for an unchanged/invalid view, disabled Grid Walk, or a composable controller that has not started. View changes do not call `restart()`.
|
|
38
|
+
- Before the first departure from Grid Walk, the simulation clock and lights are held. Choosing Race clears the crew over 650 ms of visible activity, then starts the existing deterministic lights sequence. With reduced motion or `ambientMotion: false`, clearing is immediate. Returning during clearing cancels the transition.
|
|
39
|
+
- Returning after playback has begun pauses the exact live race and shows its original starting grid using display poses only. Race resumes saved time, laps, order, tires, pit state, selected entry and fractional fixed-step remainder. The simulation object, RNG, entries and track are not recreated or changed by browsing or resizing. Project banner selection/open state survives a mode switch.
|
|
40
|
+
- Standing starts use actual assigned grid distances and offsets, including penalties present when the presentation is captured. With `rules.standingStart: false`, staging uses display-only staggered slots; Race resumes the existing rolling-start state without adding a lights sequence.
|
|
41
|
+
- Explicit `restart()` still creates a new race, captures its grid and applies the configured initial view. Nested Grid Walk options merge on restart. Resizing and mode switches never implicitly restart.
|
|
42
|
+
- Grid Walk is excluded from `expert.enabled`: expert/reset/step callers own their separate lifecycle. Combining these opt-ins throws during option resolution.
|
|
43
|
+
|
|
44
|
+
## Entry content and links
|
|
45
|
+
|
|
46
|
+
`presentation` may be supplied on a driver or its matching entry. The entry's presentation takes precedence. `name` falls back to the driver's full name and `summary` to its first legacy `raceData` item. The bottom banner uses the same project name, driver number, position/code, summary and project link fields as the race banner. It initially shows the selected entry; clicking a car/label or changing the top selector updates it. Rich metadata fields (`technologies`, `details`, `image`) remain accepted for host compatibility but are not displayed. There is no separate inspector or expanded information. Content is rendered as text, not HTML.
|
|
47
|
+
|
|
48
|
+
`href` falls back to the driver's `link`. Navigation is an ordinary anchor supporting browser open/copy/modifier behavior; Grid Walk does not route through `onDriverOpen`. That existing callback remains available on the legacy race banner. Hosts supply all destinations. URLs accept relative, hash, HTTP and HTTPS URLs through the package's URL normalization; script, data and protocol-relative URLs are excluded.
|
|
49
|
+
|
|
50
|
+
The external mode switch and in-scene entry selector reuse the race camera-control styling. Project details reuse the actual race banner template, renderer and compact variant; only navigation uses an ordinary anchor in Grid Walk. Appearance follows existing package theme tokens for text, surfaces, borders, primary and focus colors. No host-specific names, categories or routing are embedded in the runtime.
|
|
51
|
+
|
|
52
|
+
## Camera and accessibility
|
|
53
|
+
|
|
54
|
+
The measured canvas host's width and height choose composition, independent of viewport, device or user-agent. Wide starts horizontal, tall starts vertical. A retained orientation uses a ratio band of 0.92–1.08 near square to avoid flapping. The camera rotates and frames the same world. The grid and entries are never rebuilt or reordered for another orientation.
|
|
55
|
+
|
|
56
|
+
The camera measures the wrapping top controls and shared bottom banner to reserve a safe scene area. It keeps a stable scale for that area and a single world position along the original grid axis. Selecting an on-screen car changes the project without resetting the camera, zoom or other labels. Previous/next and the native selector bring offscreen entries into view; Home explicitly recenters the selected entry. Resizing, rotation and mode switches preserve the browsing position.
|
|
57
|
+
|
|
58
|
+
Drag the canvas or an entry label with a mouse or touch to move along the grid. All camera input uses the same axis: horizontal composition accepts left/right dragging and arrow keys, vertical composition accepts up/down. Diagonal drags and scroll cannot move across that axis. A vertical wheel travels front to rear in both compositions; horizontal trackpad input is accepted only in horizontal composition. Browsing is bounded near the grid. The canvas consumes wheel input only while it moves the grid camera; continuing past an end scrolls the host page. Race zoom does not handle wheel input while Grid Walk owns the scene. Ctrl/Meta wheel remains available to browser zoom, and scrolling outside the canvas keeps normal host behavior. Host-handled and non-cancelable wheel events are left alone; modified keyboard navigation such as Ctrl/Cmd+Home keeps its browser behavior. Race releases wheel events at zoom limits and restores native touch scrolling. Only active Grid Walk applies `touch-action: none` to its canvas/entry drag targets; it never changes body/document overflow. A drag starting on a car or label does not select it; a click or tap does, including a gesture that moves only in the forbidden direction.
|
|
59
|
+
|
|
60
|
+
HTML labels follow the actual car poses in two lanes, with connector lines if space requires displacement. Labels are packed as a group, preserving spatial order and preventing collisions with other labels, controls or the banner. When a lane cannot fit all visible cars at readable size, it keeps the entries nearest the camera center. This decision uses geometry, never selection. Every entry remains reachable through the selector and camera. Short scenes use single-line labels so readable entry targets still fit between the controls and banner in either orientation. Full accessible names and native titles remain available when constrained text is truncated. Label surfaces are opaque in both themes.
|
|
61
|
+
|
|
62
|
+
In constrained all-in-one embeds, the opt-in shell also uses its container width for the existing stacked timing/telemetry composition. Overlapping timing towers clear the Grid Walk scene and return in Race; outboard towers retain their layout. Timing-board width tokens are unchanged.
|
|
63
|
+
|
|
64
|
+
Controls are keyboard-operable. Selection focuses the project banner title; Escape or Close restores the selected visible label, or the selector if it has been panned offscreen. Dismissal preserves camera position and zoom. The banner follows `ui.raceDataBannerSize`, `ui.showRaceDataPanel`, enabled banner types and the Race mute state; with project banners disabled, selection focuses the entry label instead. Grid Walk keeps project-only content even when Race telemetry detail is enabled. Links keep ordinary browser behavior. Focus is not trapped because the grid remains available.
|
|
65
|
+
|
|
66
|
+
There is no motion button. Hosts can set `ambientMotion: false` for a static presentation. `prefers-reduced-motion` freezes visitors and crew, disables banner transitions and makes clearing immediate; live changes are respected. Each car has two to five mechanics with varied, stable preparation positions and poses. Visitors have varied clothing and stride and walk from front to rear in a continuous, spatially distributed flow along each side lane. Visitors use deliberately irregular longitudinal spacing, varied lateral paths and individual pace changes and sideways sway. Broad cosmetic offsets hide the underlying coverage cells; bounded movement preserves continuous traffic without drifting into long empty stretches. Their paths extend beyond the full canvas at both ends throughout the permitted camera range, including after resizing, so visitors enter and leave offscreen rather than appearing at a visible spawn point. Camera changes extend the path by filling only newly exposed offscreen coverage without moving existing visitors to new positions. Their cosmetic randomness is independent of the race RNG. People geometry is created once and only transforms change during animation. Camera gestures redraw without reallocating an unchanged renderer. A live reduced-motion preference is also checked by the frame loop so delayed media events cannot leave the ticker running. Existing visibility/activity handling pauses unnecessary rendering when hidden. Static Grid Walk stops the ticker and draws only for interaction/layout changes. Race logic and simulation updates are skipped while browsing.
|
|
67
|
+
|
|
68
|
+
Very small containers retain the package's minimum-size limits. Provide at least 320 px width and enough height for the scene and controls. Browser checks exercise 320 × 460, 390 × 360, 390 × 760, 640 × 640, 720 × 460, 1120 × 620 and 1400 × 400 hosts. In short containers the shared banner bounds its height and allows its own content to scroll. The project banner retains the race component's text truncation and compact layout.
|
|
69
|
+
|
|
70
|
+
## Startup, failure and recovery
|
|
71
|
+
|
|
72
|
+
A successful `restart()` completes loading for any newly created Grid Walk surfaces before rendering the replacement session. Restarted project banners must show their entry content rather than stale startup placeholders.
|
|
73
|
+
|
|
74
|
+
Grid Walk shares existing renderer/asset startup and teardown ownership. A failed `mountF1Simulator()` rejects and releases its root; invoke the mount function again to retry. For a composable controller, await the rejected `start()` and call `start()` again on the mounted components. If `destroy()` cancelled a pending start, await settlement, remount required surfaces and then call `start()`.
|
|
75
|
+
|
|
76
|
+
Hosts must restore Retry in a `finally` block and explicitly call their mount/start operation. Importing a cached module does not execute its side effects again. That host-loader wiring is outside this package. Test unpublished changes in the package showcase or a temporary packed consumer.
|
|
77
|
+
|
|
78
|
+
## Verification surface
|
|
79
|
+
|
|
80
|
+
Run `npm run site:dev` and open `/guide/grid-walk.html`, or use Grid Walk in the main demo at `/`. The main demo uses the single Start/Stop button; the Guide retains the default two-button switch. Host size controls change actual component dimensions without replacing the simulation. Normal and full browser gates exercise keyboard focus, canvas mouse/touch selection and dragging, diagonal wheel and cross-axis keyboard input, label overlap and selection continuity, opaque light surfaces, every entry, live resizing, state preservation, reduced motion, shared banner content/links and destroy/remount with cached assets. Focused tests cover deterministic progress equivalence, display-only staging and camera hysteresis.
|
package/docs/index.md
CHANGED
|
@@ -7,6 +7,8 @@ Code snippets in the documentation are illustrative host integration fragments u
|
|
|
7
7
|
Start with the consumer task docs:
|
|
8
8
|
|
|
9
9
|
- [Getting Started](getting-started.md): install, browser mount, package CSS, bundled assets, host responsibilities, and container sizing.
|
|
10
|
+
- [Startup Loading](loading.md): pre-JS placeholders, package loading overlays, static-host build-time integration, and unsupported-size placeholders.
|
|
11
|
+
- [Grid Walk](grid_walk.md): opt-in starting-grid discovery, camera-based resizing, entry selection and race pause/resume.
|
|
10
12
|
- [Composable Layouts](composable-layouts.md): building host layouts from package-owned simulator surfaces.
|
|
11
13
|
- [Headless Environment](headless-environment.md): browser-free environment imports, expert-mode boundary, and basic stepping.
|
|
12
14
|
- [Data Helpers](data-helpers.md): CSS-free data/tooling imports and procedural track helpers.
|
|
@@ -25,16 +27,17 @@ Reference docs:
|
|
|
25
27
|
- [Custom Model Controller Guide](custom_model_controller.md): how to wrap a trained model as a batched driver controller for browser playback or headless loops.
|
|
26
28
|
- [Architecture](architecture.md): module ownership and data/control flow.
|
|
27
29
|
- [Component Inventory](component_inventory.md): package-owned UI surfaces, shared bases, derived/mixed variants, and remaining intentional duplication.
|
|
30
|
+
- [Testing Reliability Guide](testing-reliability-guide.md): risk-driven test selection, behavior maps, required gates, and audit reporting standards.
|
|
28
31
|
- [Data Model Notes](data_model.md): lower-level simulator data model notes kept with package docs.
|
|
29
32
|
- [Learnings](learnings.md): implementation lessons and layout verification rules learned from simulator integration issues.
|
|
30
33
|
|
|
31
34
|
Common paths:
|
|
32
35
|
|
|
33
|
-
- Installing PaddockJS into a browser host: read [Getting Started](getting-started.md), then [Data Contract](data_contract.md) for full option shapes.
|
|
36
|
+
- Installing PaddockJS into a browser host: read [Getting Started](getting-started.md), [Startup Loading](loading.md), then [Data Contract](data_contract.md) for full option shapes.
|
|
34
37
|
- Upgrading an existing browser host from 2.x-era usage: read [Upgrading Hosts to 4.x](upgrading-hosts-to-3.md), then [Troubleshooting](troubleshooting.md) if the host build fails.
|
|
35
38
|
- Building a package-owned composable layout: read [Composable Layouts](composable-layouts.md), then the sizing and UI options in [Data Contract](data_contract.md).
|
|
36
39
|
- Enabling pits, penalties, tire requirements, safety car, or stalled off-track DNF: read [Rules](rules.md) first. Pit stops require `rules.modules.pitStops.enabled`; `tireStrategy` alone only controls compounds, tire-requirement stewarding, and pit target choices.
|
|
37
40
|
- Connecting a trained model: read [Headless Environment](headless-environment.md), [Bring Your Own Model](training.md), then [Custom Model Controller Guide](custom_model_controller.md). PaddockJS owns observations, actions, specs, scenarios, and browser playback; the host owns the model, reward, storage, and training framework.
|
|
38
|
-
- Changing internals: read [Architecture](architecture.md) before editing source so new code lands in the feature-owned module instead of a facade or compatibility barrel.
|
|
41
|
+
- Changing internals: read [Architecture](architecture.md) before editing source so new code lands in the feature-owned module instead of a facade or compatibility barrel. Read [Testing Reliability Guide](testing-reliability-guide.md) before adding or changing tests.
|
|
39
42
|
|
|
40
43
|
When simulator behavior changes, update the relevant doc in this folder in the same change.
|