@inventure71/paddockjs 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -1
- package/INSTALL_AND_UPDATE.md +10 -1
- package/README.md +65 -15
- package/docs/architecture.md +17 -13
- package/docs/component_inventory.md +72 -0
- package/docs/concepts.md +2 -2
- package/docs/custom_model_controller.md +2 -2
- package/docs/data_contract.md +96 -21
- package/docs/index.md +2 -1
- package/docs/learnings.md +6 -6
- package/docs/rules.md +7 -7
- package/docs/sense_contract.md +1 -1
- package/docs/system_specs.md +25 -18
- package/docs/training.md +13 -12
- package/package.json +1 -1
- package/src/api/PaddockSimulatorController.js +74 -4
- package/src/app/F1SimulatorApp.js +191 -28
- package/src/app/banners/raceDataBannerController.js +71 -36
- package/src/app/banners/stewardMessageController.js +5 -4
- package/src/app/camera/cameraConstants.js +2 -0
- package/src/app/camera/cameraController.js +86 -29
- package/src/app/camera/cameraGeometry.js +12 -1
- package/src/app/domBindings.js +89 -9
- package/src/app/layoutScheduler.js +27 -0
- package/src/app/layoutSupport.js +165 -0
- package/src/app/raceOverlayClearanceSupport.js +158 -0
- package/src/app/readouts/carOverviewRenderer.js +16 -8
- package/src/app/readouts/controlStateRenderer.js +1 -1
- package/src/app/readouts/raceStatusRenderer.js +30 -21
- package/src/app/readouts/readoutFormatters.js +2 -0
- package/src/app/readouts/telemetryRenderer.js +17 -8
- package/src/app/readouts/timingTowerRenderer.js +62 -10
- package/src/app/runtime/runtimeVisibility.js +66 -5
- package/src/config/cssValues.js +16 -0
- package/src/config/defaultOptions.js +51 -38
- package/src/config/themeOptions.js +825 -0
- package/src/config/timingGapMode.js +13 -0
- package/src/config/urlOptions.js +25 -0
- package/src/data/championship.js +14 -9
- package/src/environment/controllerLoop.js +17 -3
- package/src/environment/index.d.ts +1 -2
- package/src/environment/options.js +1 -1
- package/src/environment/runtime.js +3 -11
- package/src/environment/scenarios.js +1 -1
- package/src/environment/sensors/surfaceRays.js +41 -33
- package/src/environment/sensors/trackRays.js +44 -49
- package/src/index.d.ts +110 -9
- package/src/index.js +25 -1
- package/src/simulation/driver/driverController.js +1 -1
- package/src/simulation/driver/edgeRecovery.js +1 -1
- package/src/simulation/driver/racingControls.js +2 -2
- package/src/simulation/driver/rejoinControls.js +2 -2
- package/src/simulation/pit/pitKinematics.js +2 -2
- package/src/simulation/pit/pitRouting.js +2 -4
- package/src/simulation/race/raceSetup.js +2 -6
- package/src/simulation/race/raceStep.js +1 -1
- package/src/simulation/race/safetyCar.js +1 -1
- package/src/simulation/race/stalledDnf.js +1 -1
- package/src/simulation/timing/lapTelemetry.js +28 -9
- package/src/simulation/track/pitLaneLayout.js +7 -3
- package/src/simulation/track/spatialQueries.js +5 -28
- package/src/simulation/track/trackQueryIndex.js +180 -135
- package/src/simulation/track/trackStatePolicy.js +0 -1
- package/src/simulation/vehicle/contactResolution.js +3 -3
- package/src/simulation/vehicle/vehicleKinematics.js +2 -2
- package/src/simulation/vehicle/vehiclePhysics.js +3 -3
- package/src/simulation/vehicle/vehicleSnapshots.js +12 -1
- package/src/simulation/vehicle/vehicleState.js +1 -1
- package/src/simulation/warmup/runtimeWarmup.js +0 -1
- package/src/styles.css +1185 -136
- package/src/ui/bannerTemplates.js +38 -28
- package/src/ui/cameraControlsTemplate.js +16 -5
- package/src/ui/carOverviewTemplate.js +12 -5
- package/src/ui/raceCanvasTemplate.js +31 -8
- package/src/ui/raceControlsTemplate.js +27 -10
- package/src/ui/shellTemplate.js +3 -1
- package/src/ui/telemetryDrawerTemplate.js +23 -10
- package/src/ui/telemetryTemplates.js +58 -32
- package/src/ui/templateUtils.js +48 -0
- package/src/ui/timingTowerTemplate.js +29 -12
package/docs/concepts.md
CHANGED
|
@@ -81,13 +81,13 @@ Entry responsibilities:
|
|
|
81
81
|
- Timing name.
|
|
82
82
|
- Driver ratings.
|
|
83
83
|
- Vehicle ratings.
|
|
84
|
-
- Optional team object with `id`, `name`, `color`, and `
|
|
84
|
+
- Optional team object with `id`, `name`, `color`, `icon`, and `theme`.
|
|
85
85
|
- Driver and vehicle rating components for the car/driver overview panel.
|
|
86
86
|
- Optional driver and vehicle `customFields` for extra overview metadata.
|
|
87
87
|
|
|
88
88
|
## Team
|
|
89
89
|
|
|
90
|
-
A team is entry-level metadata used for race identity and current pit-lane behavior. The timing tower uses the team icon in its team column. Team color defaults to the car color when omitted. When pit stops are enabled, team color marks the shared service area, queue point, and garage boxes, and optional `team.pitCrew` stats can affect service-time variability.
|
|
90
|
+
A team is entry-level metadata used for race identity, theme selection, and current pit-lane behavior. The timing tower uses the team icon in its team column. Team color defaults to the car color when omitted. `team.theme` can point to a named package theme for team-aware component selectors. When pit stops are enabled, team color marks the shared service area, queue point, and garage boxes, and optional `team.pitCrew` stats can affect service-time variability.
|
|
91
91
|
|
|
92
92
|
## Driver Ratings
|
|
93
93
|
|
|
@@ -137,7 +137,7 @@ const env = createPaddockEnvironment({
|
|
|
137
137
|
drivers,
|
|
138
138
|
entries,
|
|
139
139
|
controlledDrivers: ['agent-1', 'agent-2'],
|
|
140
|
-
physicsMode: '
|
|
140
|
+
physicsMode: 'advanced',
|
|
141
141
|
participantInteractions: { defaultProfile: 'batch-training' },
|
|
142
142
|
observation: {
|
|
143
143
|
profile: 'physical-driver',
|
|
@@ -177,7 +177,7 @@ import {
|
|
|
177
177
|
const simulator = await mountF1Simulator(root, {
|
|
178
178
|
drivers,
|
|
179
179
|
entries,
|
|
180
|
-
physicsMode: '
|
|
180
|
+
physicsMode: 'advanced',
|
|
181
181
|
expert: {
|
|
182
182
|
enabled: true,
|
|
183
183
|
controlledDrivers: ['agent-1'],
|
package/docs/data_contract.md
CHANGED
|
@@ -15,7 +15,6 @@ mountF1Simulator(root, {
|
|
|
15
15
|
seed,
|
|
16
16
|
trackSeed,
|
|
17
17
|
trackGeneration,
|
|
18
|
-
trackQueryIndex,
|
|
19
18
|
warmup,
|
|
20
19
|
totalLaps,
|
|
21
20
|
physicsMode,
|
|
@@ -53,7 +52,6 @@ const simulator = createPaddockSimulator({
|
|
|
53
52
|
seed,
|
|
54
53
|
trackSeed,
|
|
55
54
|
trackGeneration,
|
|
56
|
-
trackQueryIndex,
|
|
57
55
|
warmup,
|
|
58
56
|
totalLaps,
|
|
59
57
|
physicsMode,
|
|
@@ -138,7 +136,7 @@ result = env.step({
|
|
|
138
136
|
`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.
|
|
139
137
|
`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.
|
|
140
138
|
`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.
|
|
141
|
-
`physicsMode` is optional and accepts `'arcade'` or `'
|
|
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 stricter 2D velocity/yaw 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 mixed wheel surfaces are averaged for physics while wheel snapshots still report each contact patch. 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`.
|
|
142
140
|
`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:
|
|
143
141
|
|
|
144
142
|
```js
|
|
@@ -348,7 +346,7 @@ const actionSpec = env.getActionSpec();
|
|
|
348
346
|
const observationSpec = env.getObservationSpec();
|
|
349
347
|
```
|
|
350
348
|
|
|
351
|
-
`actionSpec` describes controlled drivers, normalized action ranges, and the optional pit intent values. `observationSpec` describes object observation fields, ray layout, nearby-car limits, track lookahead fields, and the versioned vector schema. `observation.lookaheadMeters` is sanitized to a finite numeric array; invalid or empty values fall back to the default `[20, 50, 100, 150]`. The opt-in `observation.profile: 'physical-driver'` profile defaults lookahead to `[]` so policies can use local driver-like senses without receiving privileged future track curvature. Realistic training runs should pair this profile with `physicsMode: '
|
|
349
|
+
`actionSpec` describes controlled drivers, normalized action ranges, and the optional pit intent values. `observationSpec` describes object observation fields, ray layout, nearby-car limits, track lookahead fields, and the versioned vector schema. `observation.lookaheadMeters` is sanitized to a finite numeric array; invalid or empty values fall back to the default `[20, 50, 100, 150]`. The opt-in `observation.profile: 'physical-driver'` profile defaults lookahead to `[]` so policies can use local driver-like senses without receiving privileged future track curvature. Realistic training runs should pair this profile with `physicsMode: 'advanced'`; that keeps the model's observed yaw, grip, contact-patch, kerb, and runoff behavior aligned with the physics it is learning to control.
|
|
352
350
|
|
|
353
351
|
Observation output can be compacted for training throughput:
|
|
354
352
|
|
|
@@ -365,6 +363,17 @@ The default remains `output: 'full'`, `includeSchema: true`, and `vectorType: 'a
|
|
|
365
363
|
|
|
366
364
|
The executable model-sense contract lives in `src/__tests__/environmentSenseContract.test.js` and is summarized in [Model Sense Contract](sense_contract.md). It treats the object observation as a policy-facing contract, checks it against simulator snapshot facts through independent unit and geometry formulas, then decodes each vector schema entry back to the object contract. A new vector field is not considered contract-covered until it has an oracle there.
|
|
367
365
|
|
|
366
|
+
Episode limits are configured separately from race length:
|
|
367
|
+
|
|
368
|
+
```js
|
|
369
|
+
episode: {
|
|
370
|
+
maxSteps: 2400,
|
|
371
|
+
endOnRaceFinish: true,
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`episode.maxSteps` is a safety horizon for controlled-driver episodes, not the number of race laps. If all reported controlled drivers reach it, the result is `truncated: true`, `done: true`, and `endReason: 'max-steps'`. The default is intentionally high (`1_000_000` environment/expert steps) so normal browser expert sessions are unlikely to hit it; bounded training, evaluation, and smoke loops should pass a smaller explicit value. `episode.endOnRaceFinish` defaults to `true`, so race finish remains terminal unless a host explicitly disables that behavior.
|
|
376
|
+
|
|
368
377
|
Result state output can also be compacted:
|
|
369
378
|
|
|
370
379
|
```js
|
|
@@ -571,7 +580,7 @@ snapshot.cars = physics-driven race participants
|
|
|
571
580
|
snapshot.replayGhosts = trajectory-driven replay/reference entities
|
|
572
581
|
```
|
|
573
582
|
|
|
574
|
-
Observation objects use physical units such as kph, meters/second, meters, and radians. Optional `vector` values use fixed documented scaling from `schema`; they do not use hidden per-car normalization. Full simulator truth remains available under `state.snapshot`. Internally, the environment avoids rebuilding full snapshots during each `frameSkip` substep and
|
|
583
|
+
Observation objects use physical units such as kph, meters/second, meters, and radians. Optional `vector` values use fixed documented scaling from `schema`; they do not use hidden per-car normalization. Full simulator truth remains available under `state.snapshot`. Internally, the environment avoids rebuilding full snapshots during each `frameSkip` substep and always uses the non-public track query index for track, surface, pit-lane, and ray queries, but the returned `state.snapshot`, reward callback `previous` snapshot, and reward callback `state.snapshot` keep the same public shape.
|
|
575
584
|
|
|
576
585
|
The environment observation now exposes local physical driver senses separately from full snapshot truth. `self.yawRateRadiansPerSecond` is the car's current yaw rate. `self.appliedControls` reports the normalized controls that actually drove the latest physics step: `steering` in `-1..1`, `steeringRadians` in simulator radians, `throttle` in `0..1`, and `brake` in `0..1`. For controlled cars this mirrors the accepted environment action; in `actionPolicy: 'report'` mode, a missing or invalid vehicle action releases stale manual controls, lets the built-in AI drive that step, and exposes its exact controls for auditing or clean local imitation datasets. `trackRelation` gives immediate local road relationship: lateral offset, heading error, legal width, left/right boundary distance, legal-surface state, and current surface. `contactPatches` exposes the four wheel/contact-patch surface readings as stable public observation data. Compact physical-driver vectors include each patch's `surfaceCode`, `signedOffsetMeters`, and `crossTrackErrorMeters` so vector-only training loops do not lose a contact-patch sense that exists in object observations.
|
|
577
586
|
|
|
@@ -627,9 +636,9 @@ sensors: {
|
|
|
627
636
|
|
|
628
637
|
Ray precision defaults to `driver`. Driver precision is the active model-facing sensor contract and uses the normal sampled ray step without extra refinement. `precision: 'debug'` is available for clearly labeled diagnostics with additional edge refinement, but debug precision must not be displayed as model senses unless the policy is also running with that exact sensor config.
|
|
629
638
|
|
|
630
|
-
Surface channels are computed only when requested. `kerb` is legal racing surface. `illegalSurface` reports the first grass or gravel surface crossed by the ray. Requested surface-ray channels are part of the active object observation and compact vector schema for both default and physical-driver profiles.
|
|
639
|
+
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. `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. Ambiguous pit-connector and unusual geometry cases may 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.
|
|
631
640
|
|
|
632
|
-
Nearby-car observations are car-relative. When
|
|
641
|
+
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:
|
|
633
642
|
|
|
634
643
|
```js
|
|
635
644
|
{
|
|
@@ -731,9 +740,11 @@ Each `car.lapTelemetry` snapshot includes current/last/best lap and sector timin
|
|
|
731
740
|
|
|
732
741
|
Profiles are presets, not separate generators. `race` preserves the default full-length circuit with pit lane; `training-short`, `training-medium`, and `training-technical` generate smaller pitless circuits for training or demos. Resolution is `race` defaults, selected profile defaults, then explicit overrides. Procedural tracks are generated by tracing, smoothing, and warping package-owned seeded region masks, then validating the resulting centerline instead of falling back to pure ovals. Invalid candidates are rejected for length, world bounds, non-adjacent clearance, turn sharpness, self-intersections, and weak shape variation. The start/finish area is normalized into an explicit straight window so the starting grid, pit-entry approach, and immediate pit-exit merge area are straight even when the rest of the circuit is generated from curved controls.
|
|
733
742
|
|
|
734
|
-
|
|
743
|
+
Indexed track/surface queries are the canonical runtime path for browser/expert simulator mounts, headless environments, direct race-simulation construction, Policy Runner playback, training/evaluation runs, and model-sense visualization. The index is internal infrastructure and does not change public snapshot or observation shapes.
|
|
744
|
+
|
|
745
|
+
`backLinkHref` is optional and controls the package-owned top-bar back link in the all-in-one shell and race-controls component. It accepts relative URLs, hash URLs, and absolute `http:` / `https:` URLs. Unsafe schemes such as `javascript:` and malformed URL values are replaced with the package default. Driver, team, snapshot, and radio colors are validated before they are written as runtime CSS values; unsafe color strings fall back to package defaults.
|
|
735
746
|
|
|
736
|
-
`initialCameraMode` is optional and accepts `'overview'`, `'leader'`, `'selected'`, `'show-all'`, or `'pit'`. Invalid values fall back to `'leader'`. The `overview` camera frames the generated track bounds with package-owned padding and pit-lane extent. The `pit` camera frames the active track's `pitLane` geometry, zooms out when needed to keep the full pit lane inside the active race-view safe area, and falls back to `leader` when no pit lane is available. Camera zoom controls and wheel zoom apply to every mode, with zoom-out bounded by the active track frame.
|
|
747
|
+
`initialCameraMode` is optional and accepts `'overview'`, `'leader'`, `'selected'`, `'driver'`, `'show-all'`, or `'pit'`. Invalid values fall back to `'leader'`. The `overview` camera frames the generated track bounds with package-owned padding and pit-lane extent. The `driver` camera follows the selected car from a lower screen anchor and rotates the world so the selected car points upward; its control is opt-in through `ui.driverCamera: true`, and setting `initialCameraMode: 'driver'` enables that control automatically. The `pit` camera frames the active track's `pitLane` geometry, zooms out when needed to keep the full pit lane inside the active race-view safe area, and falls back to `leader` when no pit lane is available. Camera zoom controls and wheel zoom apply to every mode, with zoom-out bounded by the active track frame.
|
|
737
748
|
|
|
738
749
|
Each driver must have:
|
|
739
750
|
|
|
@@ -815,6 +826,7 @@ Entries are optional. If omitted, defaults are used.
|
|
|
815
826
|
name: 'Ledger Racing',
|
|
816
827
|
color: '#00ff84',
|
|
817
828
|
icon: 'LR',
|
|
829
|
+
theme: 'trackside',
|
|
818
830
|
pitCrew: {
|
|
819
831
|
speed: 0.72,
|
|
820
832
|
consistency: 0.81,
|
|
@@ -870,6 +882,8 @@ Optional lifecycle callbacks:
|
|
|
870
882
|
|
|
871
883
|
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.
|
|
872
884
|
|
|
885
|
+
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.
|
|
886
|
+
|
|
873
887
|
Penalty status values are `issued`, `served`, `applied`, and `cancelled`. Time, position-drop, grid-drop, and disqualification consequences are immediate `applied` penalties. Drive-through and stop-go consequences are service penalties: they start as `issued`, can be completed with `servePenalty(penaltyId)`, and convert to applied time if unserved when final classification is calculated. Pit stops also serve eligible penalties before tire work starts: applied time penalties add their seconds as a hold, stop-go penalties add their configured service seconds, and drive-through penalties are marked served by the pit-lane traversal without extra stationary hold time.
|
|
874
888
|
|
|
875
889
|
## Asset Overrides
|
|
@@ -935,6 +949,9 @@ ui: {
|
|
|
935
949
|
raceDataBannerSize: 'custom',
|
|
936
950
|
raceDataTelemetryDetail: false,
|
|
937
951
|
timingTowerVerticalFit: 'expand-race-view',
|
|
952
|
+
timingGapMode: 'interval',
|
|
953
|
+
timingGapModeToggle: true,
|
|
954
|
+
timingEntryVerticalPadding: 5,
|
|
938
955
|
},
|
|
939
956
|
debug: {
|
|
940
957
|
physicsModeIndicator: false,
|
|
@@ -942,11 +959,11 @@ debug: {
|
|
|
942
959
|
```
|
|
943
960
|
|
|
944
961
|
- `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.
|
|
945
|
-
- `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. 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.
|
|
962
|
+
- `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.
|
|
946
963
|
- `showFps`: controls whether the race canvas renders the FPS readout.
|
|
947
964
|
- `showRaceDataPanel`: controls whether the precombined shell includes the project/radio lower-third inside the race window.
|
|
948
965
|
- `showTimingTower`, `showTelemetry`: reserved component visibility flags for host layout decisions.
|
|
949
|
-
- `debug.physicsModeIndicator`: when `true`, renders a small top-left square in the race canvas. Blue means `physicsMode: 'arcade'`; red means `physicsMode: '
|
|
966
|
+
- `debug.physicsModeIndicator`: when `true`, renders a small top-left square in the race canvas. Blue means `physicsMode: 'arcade'`; red means `physicsMode: 'advanced'`. It defaults to `false` and is intended only for debug/development use.
|
|
950
967
|
- `telemetryIncludesOverview`: controls whether the telemetry stack template embeds the car/driver overview. Composable hosts can also pass `mountTelemetryPanel(root, { includeOverview: false })`.
|
|
951
968
|
- `telemetryModules`: controls optional telemetry surfaces inside stack/drawer templates. The default object enables `core` scalar readouts, `sectors` progress bars, `lapTimes`, and `sectorTimes`. It can also be `false` to disable all telemetry modules, or an array such as `['sectors', 'lapTimes']` to render only named modules. These modules are also individually mountable with `mountTelemetryCore`, `mountTelemetrySectors`, `mountTelemetryLapTimes`, and `mountTelemetrySectorTimes`.
|
|
952
969
|
- `raceDataBanners.initial`: `'project'`, `'radio'`, or `'hidden'`. This controls which lower-third appears first in the precombined shell.
|
|
@@ -957,29 +974,84 @@ debug: {
|
|
|
957
974
|
- `penaltyBanners`: when `true`, the race view shows a top steward message for track-limit warnings and new steward penalty decisions. Time penalties show a large `+10s` style chip in the left block, with the affected car and rule/reason beside it. Warning-only messages use warning colors and remain separate from penalty decisions. It does not replace the project/radio lower-third.
|
|
958
975
|
- `timingPenaltyBadges`: when `true`, timing rows for penalized drivers show a red `!` badge with an accessible penalty label. Warning-only events do not count as penalties and do not show the badge.
|
|
959
976
|
- `timingTowerVerticalFit`: `'expand-race-view'` lets the combined race window grow to contain the timing tower. `'scroll'` keeps the race window height and scrolls the timing list inside the cropped tower. The same values can be passed to `mountRaceCanvas(root, { includeTimingTower: true, timingTowerVerticalFit })` for an embedded composable timing tower.
|
|
977
|
+
- `timingGapMode`: `'interval'` or `'leader'`. The default `'interval'` starts the tower in `Int` mode and shows the interval to the car ahead; `'leader'` starts the tower in `Gap` mode and shows the total gap to P1. Invalid values fall back to `'interval'`.
|
|
978
|
+
- `timingGapModeToggle`: defaults to `true`. When `true`, the timing tower renders one compact `Int`/`Gap` header toggle in the timing header's gap column. The toggle is package-owned UI: it shares the header alignment grid with `POS`, `TEAM`, `PROJECT`, and `TYRE`, preserves the timing tower's narrow broadcast proportions, keeps a practical `44px` target, and draws selected/focus styling inside the header instead of expanding the column. Set it to `false` when the host should control the mode only through `setTimingGapMode()`, `getTimingGapMode()`, or `toggleTimingGapMode()`.
|
|
979
|
+
- `timingEntryVerticalPadding`: defaults to `5`. This single non-negative number is applied in pixels above and below every timing row entry, letting hosts make the timing tower denser or looser without per-entry styling.
|
|
980
|
+
- `responsiveNarrowLayout`: defaults to `true`. Package templates use it to enable the narrow timing reveal and narrow drawer behavior when their container is too small for the wide layout. In responsive narrow race canvases, PaddockJS measures timing-row entries and the lower-third before adding bottom clearance, so extra race height is reserved only when the banner would cover timing entries. Set it to `false`, or pass `responsiveNarrowLayout: false` to `mountRaceCanvas()` / `mountRaceTelemetryDrawer()`, only when a host intentionally wants the non-responsive stacked behavior.
|
|
960
981
|
|
|
961
982
|
No UI option exists for raw timing-tower width, max width, or horizontal ratio. The timing tower is capped by the package CSS variable `--timing-board-max-width` because very wide timing boards read poorly. Host pages can scale the whole simulator by changing the mount container, but package-owned layout presets keep their internal proportions inside PaddockJS. For standalone timing towers, give the mount root a fixed height when a fixed vertical footprint is needed; the package keeps the frame inside that height and scrolls only the timing entries. Narrow hosts are handled internally: side-gutter timing towers become stacked/full-width, embedded timing towers stop behaving like desktop side overlays, and the camera safe area stops reserving a left gutter when the measured timing board is effectively full-width.
|
|
962
983
|
|
|
984
|
+
Timing gap display is runtime state on the simulator/controller, not a property of the rendered button. `ui.timingGapMode` only chooses the initial value. After mounting, `setTimingGapMode('interval' | 'leader')` updates all mounted timing towers immediately, `getTimingGapMode()` reports the current value, and `toggleTimingGapMode()` switches to the other supported value. Hiding the manual toggle does not disable those methods; it only removes the package-owned header affordance so a host can provide its own external control.
|
|
985
|
+
|
|
963
986
|
## Theme And Sizing Contract
|
|
964
987
|
|
|
965
988
|
```js
|
|
966
989
|
theme: {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
990
|
+
mode: 'system',
|
|
991
|
+
use: 'trackside',
|
|
992
|
+
tokens: {
|
|
993
|
+
primary: { light: '#c90400', dark: '#e10600' },
|
|
994
|
+
primaryText: '#ffffff',
|
|
995
|
+
pitLane: '#7c3aed',
|
|
996
|
+
},
|
|
997
|
+
themes: {
|
|
998
|
+
trackside: {
|
|
999
|
+
extends: 'default',
|
|
1000
|
+
tokens: {
|
|
1001
|
+
yellowFlag: { dark: '#ffcc00' },
|
|
1002
|
+
},
|
|
1003
|
+
components: {
|
|
1004
|
+
button: {
|
|
1005
|
+
background: 'pitLane',
|
|
1006
|
+
text: 'primaryText',
|
|
1007
|
+
border: 'primary',
|
|
1008
|
+
},
|
|
1009
|
+
},
|
|
1010
|
+
},
|
|
1011
|
+
},
|
|
1012
|
+
componentThemes: {
|
|
1013
|
+
'race-controls': 'trackside',
|
|
1014
|
+
'timing-tower': 'selectedTeam',
|
|
1015
|
+
},
|
|
1016
|
+
teamThemes: {
|
|
1017
|
+
ferrari: 'trackside',
|
|
1018
|
+
},
|
|
970
1019
|
timingTowerMaxWidth: '390px',
|
|
971
1020
|
raceViewMinHeight: '620px',
|
|
972
1021
|
}
|
|
973
1022
|
```
|
|
974
1023
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
- `
|
|
1024
|
+
Themes may be partial, but resolved themes are always complete. The package `default` theme owns the token schema; named themes in `theme.themes` are derivative packages that can override only those known tokens. Unknown theme tokens and unknown component slots are ignored instead of becoming new CSS variables. If a token is supplied only for `light` or only for `dark`, PaddockJS generates and caches the opposite-mode value in the resolved theme. `theme.use` selects the active named package. `theme.componentThemes` maps `data-paddock-component` names such as `race-controls`, `camera-controls`, `timing-tower`, `race-canvas`, and `race-data-panel` to `default`, `active`, `selectedTeam`, `team`, `team:<id>`, or a named theme package. Component theme keys may also use camelCase aliases such as `raceControls` and `timingTower`; `selectedDriverPanel` targets both selected-driver package surfaces. `theme.teamThemes` maps team ids to selectors for team-aware surfaces; `entries[*].team.theme` feeds the same map, and explicit `theme.teamThemes` values override team metadata when both are supplied. Selected-driver surfaces such as the car/driver overview and race-data panel use the selected team's theme by default when one is available; structural controls keep the active global theme unless explicitly overridden. Theme and driver/team colors are validated before they are written to CSS variables.
|
|
1025
|
+
|
|
1026
|
+
Supported theme tokens are applied as package CSS variables:
|
|
1027
|
+
|
|
1028
|
+
- `primary` -> `--paddock-color-primary`
|
|
1029
|
+
- `primaryText` -> `--paddock-color-primary-text`
|
|
1030
|
+
- `secondary` -> `--paddock-color-secondary`
|
|
1031
|
+
- `secondaryText` -> `--paddock-color-secondary-text`
|
|
1032
|
+
- `surface` -> `--paddock-color-surface`
|
|
1033
|
+
- `surfaceRaised` -> `--paddock-color-surface-raised`
|
|
1034
|
+
- `surfacePanel` -> `--paddock-color-surface-panel`
|
|
1035
|
+
- `text` -> `--paddock-color-text`
|
|
1036
|
+
- `mutedText` -> `--paddock-color-muted-text`
|
|
1037
|
+
- `border` -> `--paddock-color-border`
|
|
1038
|
+
- `success` -> `--paddock-color-success`
|
|
1039
|
+
- `warning` -> `--paddock-color-warning`
|
|
1040
|
+
- `danger` -> `--paddock-color-danger`
|
|
1041
|
+
- `info` -> `--paddock-color-info`
|
|
1042
|
+
- `yellowFlag` -> `--paddock-color-yellow-flag`
|
|
1043
|
+
- `greenFlag` -> `--paddock-color-green-flag`
|
|
1044
|
+
- `redFlag` -> `--paddock-color-red-flag`
|
|
1045
|
+
- `safetyCar` -> `--paddock-color-safety-car`
|
|
1046
|
+
- `drsActive` -> `--paddock-color-drs-active`
|
|
1047
|
+
- `pitLane` -> `--paddock-color-pit-lane`
|
|
1048
|
+
- `track` -> `--paddock-color-track`
|
|
1049
|
+
- `trackEdge` -> `--paddock-color-track-edge`
|
|
980
1050
|
- `timingTowerMaxWidth` -> `--paddock-timing-tower-max-width`
|
|
981
1051
|
- `raceViewMinHeight` -> `--paddock-race-view-min-height`
|
|
982
1052
|
|
|
1053
|
+
The old `accentColor`, `greenColor`, `yellowColor`, and related `*Color` fields remain compatibility aliases that feed the semantic tokens and legacy CSS variables such as `--paddock-accent-color`.
|
|
1054
|
+
|
|
983
1055
|
Prefer these fields over host CSS overrides. They are the stable styling surface for reusable embeds.
|
|
984
1056
|
|
|
985
1057
|
## Race Completion Snapshot
|
|
@@ -1104,6 +1176,9 @@ Controller methods:
|
|
|
1104
1176
|
- `getPitIntent(driverId)`: reads the current pit intent.
|
|
1105
1177
|
- `getPitTargetCompound(driverId)`: reads the current pit target tire.
|
|
1106
1178
|
- `getSimulationSpeed()`: returns the active browser playback multiplier from the package-owned simulation-speed control, defaulting to `1`.
|
|
1179
|
+
- `setTimingGapMode(mode)`: sets the timing tower gap mode to `'interval'` or `'leader'` and immediately re-renders mounted timing towers. Invalid values fall back to `'interval'`.
|
|
1180
|
+
- `getTimingGapMode()`: returns the current timing tower gap mode.
|
|
1181
|
+
- `toggleTimingGapMode()`: switches between `'interval'` and `'leader'` and returns the active mode.
|
|
1107
1182
|
- `servePenalty(penaltyId)`: marks an issued drive-through or stop-go penalty as served.
|
|
1108
1183
|
- `cancelPenalty(penaltyId)`: cancels a penalty so it no longer affects service, timing, grid, or classification.
|
|
1109
1184
|
- `getSnapshot()`: returns the latest simulation snapshot.
|
|
@@ -1114,13 +1189,13 @@ Composable controllers additionally expose:
|
|
|
1114
1189
|
- `mountCameraControls(root)`: renders package-owned camera mode, zoom, and project/radio banner mute controls outside the race canvas.
|
|
1115
1190
|
- `mountSafetyCarControl(root)`: renders a package-owned safety-car button that binds to the same race-control state as other safety buttons.
|
|
1116
1191
|
- `mountTimingTower(root)`: renders the timing tower component. The tower includes one hidden race-control status banner slot above the timing rows; `raceControl.mode: 'safety-car'` shows the yellow safety-car status, and `raceControl.mode: 'red-flag'` shows the red red-flag status.
|
|
1117
|
-
- `mountRaceCanvas(root, { includeRaceDataPanel, includeTimingTower, includeTelemetrySectorBanner, timingTowerVerticalFit })`: renders the PixiJS canvas host, optional FPS, start lights, and the top steward message. Camera controls are external by default and render inside the race canvas only when `ui.cameraControls: 'embedded'` is explicitly requested. Pass `includeRaceDataPanel: true` to place the project/radio lower-third inside the race window so it shares race-canvas clipping and layering. Pass `includeTelemetrySectorBanner: true` only when the host intentionally wants the independent sector lower-third in addition to the project/radio banner. Pass `includeTimingTower: true` to place the timing tower inside the race canvas; `timingTowerVerticalFit: 'expand-race-view'` grows the canvas to the tower height, while `'scroll'` keeps the canvas height and scrolls timing rows inside the tower frame. This is required before `start()`.
|
|
1192
|
+
- `mountRaceCanvas(root, { includeRaceDataPanel, includeTimingTower, includeTelemetrySectorBanner, timingTowerVerticalFit, responsiveNarrowLayout })`: renders the PixiJS canvas host, optional FPS, start lights, and the top steward message. Camera controls are external by default and render inside the race canvas only when `ui.cameraControls: 'embedded'` is explicitly requested. Pass `includeRaceDataPanel: true` to place the project/radio lower-third inside the race window so it shares race-canvas clipping and layering. Pass `includeTelemetrySectorBanner: true` only when the host intentionally wants the independent sector lower-third in addition to the project/radio banner. Pass `includeTimingTower: true` to place the timing tower inside the race canvas; `timingTowerVerticalFit: 'expand-race-view'` grows the canvas to the tower height, while `'scroll'` keeps the canvas height and scrolls timing rows inside the tower frame. Responsive narrow timing reveal behavior is enabled by default and can be disabled with `responsiveNarrowLayout: false`. This is required before `start()`.
|
|
1118
1193
|
- `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`.
|
|
1119
1194
|
- `mountTelemetryCore(root)`: renders selected-car scalar telemetry only.
|
|
1120
1195
|
- `mountTelemetrySectors(root)`: renders the live sector progress graph only.
|
|
1121
1196
|
- `mountTelemetryLapTimes(root)`: renders current, last, and best lap timing only.
|
|
1122
1197
|
- `mountTelemetrySectorTimes(root)`: renders last and best sector timing only.
|
|
1123
|
-
- `mountRaceTelemetryDrawer(root, { timingTowerVerticalFit, drawerInitiallyOpen, raceDataTelemetryDetail })`: renders a template that combines an external top control row, race canvas, embedded timing tower, the project/radio lower-third, top steward message, safety-car control, and a right-side telemetry drawer. The control row contains camera controls, the `1x..10x` simulation-speed toggle, banner mute, the safety-car button, and the telemetry toggle so those controls do not cover the race view. Pass `raceDataTelemetryDetail: true` when this template should put compact S1/S2/S3 detail in the project lower-third instead of mounting a second sector popup. The drawer embeds the same package-owned telemetry stack used by `mountTelemetryPanel()` and takes layout space from the race window when opened
|
|
1198
|
+
- `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`.
|
|
1124
1199
|
- `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.
|
|
1125
1200
|
- `mountRaceDataPanel(root)`: renders the project/race-data lower-third as a separate component for hosts that intentionally want it outside the race canvas.
|
|
1126
1201
|
- `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.
|
package/docs/index.md
CHANGED
|
@@ -12,13 +12,14 @@ Use these docs as the package source of truth:
|
|
|
12
12
|
- [Model Sense Contract](sense_contract.md): executable proof boundary for policy-facing observation senses.
|
|
13
13
|
- [Custom Model Controller Guide](custom_model_controller.md): how to wrap a trained model as a batched driver controller for browser playback or headless loops.
|
|
14
14
|
- [Architecture](architecture.md): module ownership and data/control flow.
|
|
15
|
+
- [Component Inventory](component_inventory.md): package-owned UI surfaces, shared bases, derived/mixed variants, and remaining intentional duplication.
|
|
15
16
|
- [Data Model Notes](data_model.md): lower-level simulator data model notes kept with package docs.
|
|
16
17
|
- [Learnings](learnings.md): implementation lessons and layout verification rules learned from simulator integration issues.
|
|
17
18
|
|
|
18
19
|
Common paths:
|
|
19
20
|
|
|
20
21
|
- Installing PaddockJS into a browser host: read [System Specs](system_specs.md), then [Data Contract](data_contract.md), then [INSTALL_AND_UPDATE.md](../INSTALL_AND_UPDATE.md).
|
|
21
|
-
- Building a package-owned composable layout: read the public API
|
|
22
|
+
- Building a package-owned composable layout: read the public API, returned-controller, and layout support contract sections in [System Specs](system_specs.md), then the sizing and UI options in [Data Contract](data_contract.md).
|
|
22
23
|
- Enabling pits, penalties, tire requirements, safety car, or stalled off-track DNF: read [Rules](rules.md) first. Pit stops require `rules.modules.pitStops.enabled`; `tireStrategy` alone only controls compounds, tire-requirement stewarding, and pit target choices.
|
|
23
24
|
- Connecting a trained model: read [Bring Your Own Model](training.md), then [Custom Model Controller Guide](custom_model_controller.md). PaddockJS owns observations, actions, specs, scenarios, and browser playback; the host owns the model, reward, storage, and training framework.
|
|
24
25
|
- 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.
|
package/docs/learnings.md
CHANGED
|
@@ -48,7 +48,7 @@ Unit tests should cover the contract, but browser geometry checks catch CSS inte
|
|
|
48
48
|
|
|
49
49
|
## Indexed Track Queries Must Reach Every Runtime Path
|
|
50
50
|
|
|
51
|
-
The track query index is internal performance infrastructure, not a separate sensor contract.
|
|
51
|
+
The track query index is internal performance infrastructure, not a separate sensor contract. Every simulation instance used by a runtime must own an index:
|
|
52
52
|
|
|
53
53
|
- Headless environments created by `createPaddockEnvironment()`.
|
|
54
54
|
- Browser expert mode through `F1SimulatorApp.createRaceSimulation()`.
|
|
@@ -56,14 +56,14 @@ The track query index is internal performance infrastructure, not a separate sen
|
|
|
56
56
|
- Local preview remount/restart paths.
|
|
57
57
|
- Any benchmark or smoke path used to validate training behavior.
|
|
58
58
|
|
|
59
|
-
Do not assume that
|
|
59
|
+
Do not assume that a high-level preview or environment config controls the query implementation. The runtime must always build a non-enumerable `track.queryIndex` for live simulations. The index must stay out of public JSON contracts: it may exist on the in-memory track object, but `Object.keys(snapshot.track)` must not expose `queryIndex`.
|
|
60
60
|
|
|
61
|
-
The failure mode to avoid: headless training uses indexed queries and looks fast, while the browser visual/expert simulation
|
|
61
|
+
The failure mode to avoid: headless training uses indexed queries and looks fast, while the browser visual/expert simulation bypasses the canonical index near barriers, recovery starts, or far-off-track positions. This can make the Policy Runner lag even when the model, heuristic controller, and ray contract are correct.
|
|
62
62
|
|
|
63
63
|
Verification for index-related changes should include both layers:
|
|
64
64
|
|
|
65
|
-
- A focused unit test proving the app or environment path
|
|
66
|
-
- A browser reproduction or smoke check against the Policy Runner or relevant visual runtime, especially near barrier/recovery geometry where
|
|
67
|
-
- A profile or timing check when the bug is performance-related. If
|
|
65
|
+
- A focused unit test proving the app or environment path owns a live `track.queryIndex`.
|
|
66
|
+
- A browser reproduction or smoke check against the Policy Runner or relevant visual runtime, especially near barrier/recovery geometry where broad nearest-sample scans are most expensive.
|
|
67
|
+
- A profile or timing check when the bug is performance-related. If broad nearest-sample scans are still hot in a browser path, another code path is bypassing the index.
|
|
68
68
|
|
|
69
69
|
Do not fix browser performance by changing model-facing sensor precision, hiding observations, or showing debug-only values. The active observation contract remains the source of truth: policies receive it, Policy Runner visualizes it, and the index only changes how the same values are computed.
|
package/docs/rules.md
CHANGED
|
@@ -99,11 +99,11 @@ The current implementation normalizes and exposes all module config, records a p
|
|
|
99
99
|
|
|
100
100
|
`tireDegradation.enabled: false` freezes each car's current `tireEnergy` instead of updating wear from throttle, braking, and lateral load. This is separate from `tireStrategy.enabled`: tire strategy controls compounds, tire-requirement stewarding, and pit-stop tire choices, while tire degradation controls the physics wear curve.
|
|
101
101
|
|
|
102
|
-
`stalledDnf` defaults to `enabled: false`. When a host sets `rules.modules.stalledDnf.enabled: true`, a car that is off legal racing or pit surfaces and remains below `speedThresholdKph` for `maxStoppedSeconds` is marked out of race with `dnfReason: 'stalled-off-track'`, emits a `car-dnf` event, freezes controls/motion, and leaves active collision, sensor, pit-blocking, and race-order participation. The freeze clears
|
|
102
|
+
`stalledDnf` defaults to `enabled: false`. When a host sets `rules.modules.stalledDnf.enabled: true`, a car that is off legal racing or pit surfaces and remains below `speedThresholdKph` for `maxStoppedSeconds` is marked out of race with `dnfReason: 'stalled-off-track'`, emits a `car-dnf` event, freezes controls/motion, and leaves active collision, sensor, pit-blocking, and race-order participation. The freeze clears advanced-mode velocity, acceleration, slip, grip, and manual controls together, so a retired car cannot keep a stale motion vector in snapshots or model observations. The timer does not run during pre-start, red flag, after race completion, for already-finished cars, or while pit entry/queue/service/exit owns the car. Returning to legal surface or moving above the threshold resets the timer.
|
|
103
103
|
|
|
104
104
|
Participant interaction profiles can opt real cars out of specific interaction systems without turning them into replay ghosts. A non-colliding profile skips vehicle collision resolution and collision stewarding for that car pair, but the car still uses normal steering/throttle/brake physics. A non-blocking pit profile is ignored by pit service occupancy and queue-blocking checks. A car with `affectsRaceOrder: false` remains in `snapshot.cars` but is excluded from ranking, DRS references, finish order, and final classification. Replay ghosts are separate trajectory overlays in `snapshot.replayGhosts`; they never participate in rules, collisions, timing, pit stops, or penalties.
|
|
105
105
|
|
|
106
|
-
Automatic pit routing owns car motion while a car is entering, queued, servicing, or exiting. During those phases the route/service logic keeps scalar speed, throttle/brake, `appliedControls`, and
|
|
106
|
+
Automatic pit routing owns car motion while a car is entering, queued, servicing, or exiting. During those phases the route/service logic keeps scalar speed, throttle/brake, `appliedControls`, and advanced telemetry synchronized so advanced-mode observations and the next physics-owned step do not inherit stale pre-pit motion, grip, slip, or instability state. `velocityX/Y` is synchronized in advanced mode and cleared from arcade snapshots because arcade physics does not own a world-velocity state.
|
|
107
107
|
|
|
108
108
|
## Steward Strictness
|
|
109
109
|
|
|
@@ -130,7 +130,7 @@ Pit-lane surfaces are legal road for track-limit purposes. `pit-entry`, `pit-lan
|
|
|
130
130
|
|
|
131
131
|
Penalty decisions are recorded in `snapshot.penalties` and emitted as `penalty` events in the same step. Each entry includes the penalty type, driver id, strictness, status, penalty seconds, pending service conversion seconds, lap, timestamp, and rule-specific context. Multiple time penalties for the same driver are additive: two separate +5s entries produce `penaltySeconds: 10` on that car's snapshot and classification adjustment.
|
|
132
132
|
|
|
133
|
-
Collision response and collision-steward impact speed use simulator `velocityX/Y` when available. In
|
|
133
|
+
Collision response and collision-steward impact speed use simulator `velocityX/Y` when available. In advanced mode this keeps slip, sideways recovery, pit-adjacent motion, and post-contact damping aligned with actual physical motion instead of assuming every car moves exactly in its heading direction. If a collision pushes against a pit-route-owned car, the pit-owned car stays fixed and the movable car's scalar speed and velocity vector are damped together so the next physics step and model-facing speed senses remain consistent.
|
|
134
134
|
|
|
135
135
|
Each penalty subsection can define `consequences`. Supported consequences are:
|
|
136
136
|
|
|
@@ -194,7 +194,7 @@ The race engine uses simulator units internally. `src/simulation/units.js` conve
|
|
|
194
194
|
|
|
195
195
|
The default `physicsMode` is `'arcade'`, which preserves the existing forgiving vehicle behavior for current browser hosts and demos.
|
|
196
196
|
|
|
197
|
-
`physicsMode: '
|
|
197
|
+
`physicsMode: 'advanced'` is opt-in for more believable F1-style limits. It still accepts only normal steering, throttle, brake, and pit-intent inputs, but the vehicle integrator keeps a real 2D world velocity, derives speed from that velocity, and applies a traction budget between longitudinal and lateral demand. Steering changes yaw through a rate-limited response, while body slip is derived from the difference between the car heading and actual velocity direction instead of being an artificial movement offset. External state placement through simulator APIs rejects non-finite numeric values and resynchronizes `velocityX/Y` whenever speed or heading is changed without an explicit velocity vector, so scenario/reset placement cannot leave stale advanced-mode velocity behind. Kerbs remain legal racing surface, but high-load kerb use is less stable and slower than track asphalt. Gravel and grass are averaged per wheel so one wheel over the edge creates partial grip/drag loss, while all-wheel runoff still costs major time. The rendered barrier wall is the hard outer runoff boundary in both physics modes; cars whose footprint reaches the wall's inner face are marked `destroyed`, stopped, removed from active collision and sensor participation, and placed in DNF timing order. Barrier destruction, stalled DNF, pre-start grid hold, red-flag hold, and pit-queue hold freeze scalar speed and advanced-mode velocity together; clearing a red flag resynchronizes active cars to the release speed and heading before the next observation while DNF/out-of-race cars remain frozen. `physicsMode` changes vehicle integration and grip behavior, not barrier consequences. Advanced mode does not teleport cars, magnetize them to the track, or bypass physics with hidden pose correction.
|
|
198
198
|
|
|
199
199
|
Simulator snapshots and environment observations expose additional vehicle telemetry: `lateralG`, `longitudinalG`, `gripUsage`, `slipAngleRadians`, `tractionLimited`, and `stabilityState`. These fields are optional public telemetry; existing `speedKph`, wheel-surface, steering, throttle, and brake fields remain unchanged.
|
|
200
200
|
|
|
@@ -274,13 +274,13 @@ Green-flag behavior uses:
|
|
|
274
274
|
|
|
275
275
|
The built-in racing controller still drives through normal vehicle inputs only. It chooses a forward racing target, blends steering from the target angle, upcoming track heading, lateral offset, edge recovery pressure, outward edge motion, and a corner-apex racing-line offset, then sends clamped steering/throttle/brake values into the vehicle physics integrator. It is expected to use the available track width and kerbs on generated circuits, including sharp generated turns. When heading alignment, steering load, or outward edge motion shows the car cannot yet carry throttle through a corner, throttle is cut before braking is requested so the controller does not fight itself. It does not snap car position, override heading, or bypass steering-rate/yaw-rate limits.
|
|
276
276
|
|
|
277
|
-
In
|
|
277
|
+
In advanced mode, the built-in controller uses separate tuning: earlier lookahead for braking, smaller high-speed steering requests, stronger edge recovery before the legal limit, throttle reduction when grip and slip telemetry show the car is overloaded, a higher legal-road speed floor so the baseline AI does not crawl through generated bends, and lower rejoin targets from kerb, grass, or gravel states. Extra stability braking is applied only when upcoming corner load, edge pressure, outward drift, high straight speed, or near-target speed makes it useful; ordinary below-target straight-line correction should not trigger brake pulses. This is intentionally a baseline driver, not a trained policy.
|
|
278
278
|
|
|
279
279
|
Traffic decisions use real meter-scaled gaps and lateral spacing, not raw simulator-size constants. A trailing car inside the attack window commits toward a visible passing lane instead of sitting on the centerline. A leading car with a close rear threat can defend the threatened side before the rival is fully alongside. Side-by-side overlap still creates spacing pressure so defensive and attacking moves are expressed as target-line choices and normal steering/brake/throttle inputs, not as collision avoidance teleports or artificial position overrides.
|
|
280
280
|
|
|
281
|
-
Rejoin behavior also remains physics-driven. When a car leaves the legal racing surface, the controller enters a short recovery hold and keeps using rejoin controls after the center point first reaches track or kerb until heading and edge margin are stable. That prevents the car from immediately returning to full racing throttle while still pointed across the track. Recovery targets look forward along the racing surface, bias inward from the edge, and request bounded surface-specific speeds for track, kerb, grass, and gravel states. In
|
|
281
|
+
Rejoin behavior also remains physics-driven. When a car leaves the legal racing surface, the controller enters a short recovery hold and keeps using rejoin controls after the center point first reaches track or kerb until heading and edge margin are stable. That prevents the car from immediately returning to full racing throttle while still pointed across the track. Recovery targets look forward along the racing surface, bias inward from the edge, and request bounded surface-specific speeds for track, kerb, grass, and gravel states. In advanced mode, rejoin controls also watch signed outward velocity relative to the track normal: a car sliding farther into runoff cuts throttle or brakes before trying to accelerate out, while very low-speed gravel/grass recovery limits steering lock and caps throttle when the heading is still outward so steering scrub does not pin the car in place. Barrier contact is not a recovery state; it destroys the car.
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
Advanced-mode steering scrub is dissipative in both forward and reverse motion. It opposes the signed forward velocity instead of always applying negative forward acceleration, so a car sliding backward with zero throttle cannot gain speed from steering scrub.
|
|
284
284
|
|
|
285
285
|
Tire energy can degrade to 1%. The vehicle physics layer converts tire energy into a nonlinear grip factor, so degradation has a visible performance cost across the full 100% to 1% range while still leaving a damaged car controllable enough to return to the pits.
|
|
286
286
|
|
package/docs/sense_contract.md
CHANGED
|
@@ -38,7 +38,7 @@ while the vector oracle uses schema names and object fields rather than calling
|
|
|
38
38
|
`rear-left`, `rear-right`.
|
|
39
39
|
- `nearbyCars` are sorted by car-relative distance, with deterministic ordering
|
|
40
40
|
for equal distances.
|
|
41
|
-
- Opponent radar fields use actual
|
|
41
|
+
- Opponent radar fields use actual advanced-mode velocity vectors when present.
|
|
42
42
|
- Ray object values, vector values, schemas, and visualizations must describe
|
|
43
43
|
the same active observation values. Debug-only precision may exist only when
|
|
44
44
|
clearly labeled and must not be presented as model input.
|