@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.
Files changed (257) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/INSTALL_AND_UPDATE.md +31 -5
  3. package/README.md +34 -3
  4. package/docs/advanced_ai_pace.md +131 -0
  5. package/docs/advanced_physics_rebuild.md +166 -0
  6. package/docs/architecture.md +111 -30
  7. package/docs/cleanup_coverage.md +284 -0
  8. package/docs/component_inventory.md +32 -16
  9. package/docs/composable-layouts.md +2 -0
  10. package/docs/concepts.md +12 -1
  11. package/docs/custom_model_controller.md +1 -1
  12. package/docs/data_contract.md +33 -10
  13. package/docs/data_model.md +4 -2
  14. package/docs/efficiency_cleanup_2026-10-13.md +80 -0
  15. package/docs/getting-started.md +3 -0
  16. package/docs/grid_walk.md +80 -0
  17. package/docs/index.md +5 -2
  18. package/docs/keyboard_driving_fix.md +118 -0
  19. package/docs/limit_driving_2026-10-14.md +88 -0
  20. package/docs/loading.md +141 -0
  21. package/docs/maintainability_audit_2026-09-08.md +146 -0
  22. package/docs/racing_ai_improvements.md +145 -0
  23. package/docs/rules.md +43 -13
  24. package/docs/sense_contract.md +4 -1
  25. package/docs/system_specs.md +40 -12
  26. package/docs/testing-reliability-guide.md +67 -0
  27. package/docs/v11_improvements.md +108 -0
  28. package/package.json +23 -15
  29. package/src/api/MountedSimulatorComponents.js +101 -0
  30. package/src/api/PaddockSimulatorController.js +56 -81
  31. package/src/app/BrowserExpertAdapter.js +0 -6
  32. package/src/app/BrowserExpertExternalRenderer.js +72 -50
  33. package/src/app/ExternalTrackSnapshot.js +50 -0
  34. package/src/app/F1SimulatorApp.js +242 -297
  35. package/src/app/HostLifecycleEmitter.js +93 -0
  36. package/src/app/banners/raceDataBannerController.js +9 -4
  37. package/src/app/camera/cameraConstants.js +1 -1
  38. package/src/app/camera/cameraController.js +10 -13
  39. package/src/app/camera/cameraGeometry.js +1 -1
  40. package/src/app/controls/SimulatorViewControls.js +45 -0
  41. package/src/app/controls/workbenchBindings.js +64 -0
  42. package/src/app/controls/workbenchDisclosure.js +41 -0
  43. package/src/app/domBindings.js +21 -13
  44. package/src/app/gridWalk/GridWalkController.js +180 -0
  45. package/src/app/gridWalk/gridWalkCamera.js +88 -0
  46. package/src/app/gridWalk/gridWalkControls.js +24 -0
  47. package/src/app/gridWalk/gridWalkCrew.js +246 -0
  48. package/src/app/gridWalk/gridWalkLabels.js +104 -0
  49. package/src/app/gridWalk/gridWalkLayout.js +46 -0
  50. package/src/app/gridWalk/gridWalkNavigation.js +99 -0
  51. package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
  52. package/src/app/gridWalk/gridWalkSession.js +50 -0
  53. package/src/app/layoutSupport.js +2 -0
  54. package/src/app/raceOverlayClearanceSupport.js +21 -7
  55. package/src/app/readouts/carOverviewRenderer.js +9 -4
  56. package/src/app/readouts/readoutFormatters.js +1 -8
  57. package/src/app/readouts/telemetryRenderer.js +1 -3
  58. package/src/app/readouts/timingTowerRenderer.js +48 -50
  59. package/src/app/rendering/appAssets.js +8 -10
  60. package/src/app/rendering/carRenderer.js +27 -79
  61. package/src/app/rendering/displayUtils.js +0 -6
  62. package/src/app/rendering/expertSensorRenderer.js +1 -1
  63. package/src/app/rendering/replayGhostRenderer.js +4 -1
  64. package/src/app/rendering/trackRenderer.js +2 -2
  65. package/src/app/runtime/frameLoop.js +18 -17
  66. package/src/app/runtime/pixiStartupLifecycle.js +94 -0
  67. package/src/app/runtime/runtimeTiming.js +4 -1
  68. package/src/app/runtime/runtimeVisibility.js +7 -2
  69. package/src/config/cssValues.js +1 -1
  70. package/src/config/defaultOptions.js +12 -173
  71. package/src/config/restartOptions.js +1 -0
  72. package/src/config/runtimeSeed.js +10 -0
  73. package/src/config/simulatorDefaults.js +106 -0
  74. package/src/config/telemetryModules.js +20 -0
  75. package/src/config/theme/cssTokens.js +85 -0
  76. package/src/config/theme/themeCss.js +72 -0
  77. package/src/config/theme/themeInputs.js +155 -0
  78. package/src/config/theme/themeResolver.js +172 -0
  79. package/src/config/theme/themeSelectors.js +69 -0
  80. package/src/config/theme/themeTokens.js +257 -0
  81. package/src/config/theme/themeUtils.js +15 -0
  82. package/src/config/themeOptions.js +14 -830
  83. package/src/config/timingGapMode.js +0 -2
  84. package/src/config/uiOptions.js +53 -0
  85. package/src/data/championship.js +16 -158
  86. package/src/data/championshipDefaults.js +87 -0
  87. package/src/data/driverData.js +10 -20
  88. package/src/data/entryIdentity.js +67 -0
  89. package/src/data/entryPresentation.js +23 -0
  90. package/src/data/entryTypes.d.ts +160 -0
  91. package/src/data/index.d.ts +12 -195
  92. package/src/data/ratingScale.js +14 -0
  93. package/src/data/teamTypes.d.ts +23 -0
  94. package/src/data/trackTypes.d.ts +101 -0
  95. package/src/data/vehicleData.js +12 -23
  96. package/src/environment/actions.js +3 -2
  97. package/src/environment/controllerLoop.js +57 -44
  98. package/src/environment/controllerScheduler.js +30 -0
  99. package/src/environment/driverObservations.js +260 -0
  100. package/src/environment/driverReset.js +51 -0
  101. package/src/environment/episode.js +0 -1
  102. package/src/environment/evaluation.js +6 -90
  103. package/src/environment/evaluationTracker.js +100 -0
  104. package/src/environment/events.js +23 -0
  105. package/src/environment/index.d.ts +35 -91
  106. package/src/environment/metrics.js +3 -17
  107. package/src/environment/observationSchema.js +104 -0
  108. package/src/environment/observationVector.js +16 -115
  109. package/src/environment/observations.js +16 -316
  110. package/src/environment/options.js +5 -24
  111. package/src/environment/renderFrames.js +21 -0
  112. package/src/environment/resetOptions.js +41 -0
  113. package/src/environment/results.js +125 -0
  114. package/src/environment/rewards.js +10 -16
  115. package/src/environment/runtime.js +17 -300
  116. package/src/environment/scenarioOptions.js +90 -0
  117. package/src/environment/scenarioPlacements.js +70 -0
  118. package/src/environment/scenarioPresets.js +78 -0
  119. package/src/environment/scenarios.js +5 -236
  120. package/src/environment/scratch.js +18 -0
  121. package/src/environment/sensorOptions.js +20 -0
  122. package/src/environment/sensors/index.js +2 -556
  123. package/src/environment/sensors/indexedRayBands.js +0 -18
  124. package/src/environment/sensors/nearbyCars.js +18 -15
  125. package/src/environment/sensors/opponentRadar.js +3 -2
  126. package/src/environment/sensors/pitConnectorProximity.js +20 -0
  127. package/src/environment/sensors/rayBandTrace.js +1 -37
  128. package/src/environment/sensors/rayBatch.js +81 -0
  129. package/src/environment/sensors/rayConfig.js +26 -0
  130. package/src/environment/sensors/rayGeometry.js +0 -8
  131. package/src/environment/sensors/rayGuards.js +0 -6
  132. package/src/environment/sensors/rayOutputs.js +75 -0
  133. package/src/environment/sensors/rayQuery.js +110 -0
  134. package/src/environment/sensors/rayScratch.js +146 -0
  135. package/src/environment/sensors/sensorTargets.js +12 -4
  136. package/src/environment/sensors/surfaceRays.js +6 -61
  137. package/src/environment/sensors/trackRays.js +7 -80
  138. package/src/environment/simulationSetup.js +30 -0
  139. package/src/environment/specs.js +15 -109
  140. package/src/index.d.ts +43 -202
  141. package/src/index.js +28 -6
  142. package/src/placeholder/index.js +1 -8
  143. package/src/rendering/displayLifecycle.js +6 -0
  144. package/src/rendering/proceduralTrackAsset.js +1 -1
  145. package/src/rendering/renderSnapshot.js +3 -0
  146. package/src/rendering/track/finishLineRenderer.js +1 -1
  147. package/src/rendering/track/gridRenderer.js +1 -1
  148. package/src/rendering/track/offsetStrokeSafety.js +2 -2
  149. package/src/rendering/track/pitLaneRenderer.js +1 -1
  150. package/src/rendering/track/trackMaterialRenderer.js +4 -4
  151. package/src/rendering/track/trackRenderConstants.js +0 -1
  152. package/src/rendering/track/trackRenderGeometry.js +1 -7
  153. package/src/simulation/collisionGeometry.js +12 -20
  154. package/src/simulation/driver/advancedPathControls.js +53 -0
  155. package/src/simulation/driver/advancedRacePace.js +112 -0
  156. package/src/simulation/driver/advancedRacingControls.js +61 -0
  157. package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
  158. package/src/simulation/driver/advancedRacingSteering.js +76 -0
  159. package/src/simulation/driver/advancedRacingTiming.js +16 -0
  160. package/src/simulation/driver/arcadeRacePace.js +83 -0
  161. package/src/simulation/driver/arcadeRacingControls.js +111 -0
  162. package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
  163. package/src/simulation/driver/driverControlConstants.js +0 -2
  164. package/src/simulation/driver/driverController.js +16 -5
  165. package/src/simulation/driver/edgeRecovery.js +2 -3
  166. package/src/simulation/driver/racingControls.js +10 -396
  167. package/src/simulation/driver/racingDriverProfile.js +48 -0
  168. package/src/simulation/driver/racingLinePlan.js +54 -20
  169. package/src/simulation/driver/rejoinControls.js +59 -112
  170. package/src/simulation/driver/safetyCarControls.js +12 -0
  171. package/src/simulation/driver/trafficScan.js +136 -27
  172. package/src/simulation/participants/participantInteractions.js +24 -15
  173. package/src/simulation/pit/pitCarPlacement.js +29 -0
  174. package/src/simulation/pit/pitFlow.js +8 -30
  175. package/src/simulation/pit/pitIntent.js +4 -3
  176. package/src/simulation/pit/pitOccupancy.js +2 -2
  177. package/src/simulation/pit/pitQueue.js +2 -18
  178. package/src/simulation/pit/pitRouteMovement.js +1 -36
  179. package/src/simulation/pit/pitRouting.js +5 -81
  180. package/src/simulation/pit/pitService.js +3 -32
  181. package/src/simulation/pit/pitServiceConstants.js +1 -0
  182. package/src/simulation/pit/pitServiceProgress.js +34 -0
  183. package/src/simulation/pit/pitSnapshots.js +8 -4
  184. package/src/simulation/pit/pitState.js +1 -5
  185. package/src/simulation/race/classification.js +1 -8
  186. package/src/simulation/race/raceFinish.js +3 -8
  187. package/src/simulation/race/raceOrder.js +2 -13
  188. package/src/simulation/race/raceProgress.js +6 -20
  189. package/src/simulation/race/raceSetup.js +1 -1
  190. package/src/simulation/race/raceStep.js +20 -11
  191. package/src/simulation/race/redFlag.js +2 -4
  192. package/src/simulation/race/safetyCar.js +5 -9
  193. package/src/simulation/race/stalledDnf.js +2 -4
  194. package/src/simulation/raceSimulation.js +6 -38
  195. package/src/simulation/replay/replayGhosts.js +44 -32
  196. package/src/simulation/rules/collisionSteward.js +0 -14
  197. package/src/simulation/rules/penaltyActions.js +37 -0
  198. package/src/simulation/rules/penaltyLedger.js +8 -1
  199. package/src/simulation/rules/pitLaneSpeedingSteward.js +1 -1
  200. package/src/simulation/rules/rulesReview.js +20 -16
  201. package/src/simulation/rules/trackLimitsSteward.js +2 -2
  202. package/src/simulation/simMath.js +4 -0
  203. package/src/simulation/snapshots/raceSnapshots.js +30 -11
  204. package/src/simulation/timing/drsState.js +8 -0
  205. package/src/simulation/timing/drsTiming.js +2 -4
  206. package/src/simulation/timing/lapTelemetry.js +7 -4
  207. package/src/simulation/timing/sectorPerformance.js +14 -6
  208. package/src/simulation/timing/timingLines.js +12 -6
  209. package/src/simulation/track/pitLaneAccess.js +2 -2
  210. package/src/simulation/track/pitLaneLayout.js +4 -135
  211. package/src/simulation/track/pitLaneModel.js +70 -0
  212. package/src/simulation/track/pitLaneState.js +0 -1
  213. package/src/simulation/track/pitServiceLayout.js +121 -0
  214. package/src/simulation/track/proceduralTrack.js +77 -0
  215. package/src/simulation/track/spatialQueries.js +76 -31
  216. package/src/simulation/track/trackBuilder.js +54 -0
  217. package/src/simulation/track/trackConstants.js +0 -22
  218. package/src/simulation/track/trackMath.js +4 -29
  219. package/src/simulation/track/trackModel.js +12 -240
  220. package/src/simulation/track/trackModelCache.js +105 -0
  221. package/src/simulation/track/trackNearestQueries.js +1218 -0
  222. package/src/simulation/track/trackQueryConstants.js +3 -0
  223. package/src/simulation/track/trackQueryConstruction.js +368 -0
  224. package/src/simulation/track/trackQueryIndex.js +12 -1933
  225. package/src/simulation/track/trackQuerySegments.js +174 -0
  226. package/src/simulation/track/trackQueryStats.js +12 -0
  227. package/src/simulation/track/trackRayQueries.js +165 -0
  228. package/src/simulation/vehicle/advancedContactResponse.js +89 -0
  229. package/src/simulation/vehicle/advancedTireForces.js +92 -0
  230. package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
  231. package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
  232. package/src/simulation/vehicle/contactResolution.js +23 -9
  233. package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
  234. package/src/simulation/vehicle/pitWheelSurface.js +0 -12
  235. package/src/simulation/vehicle/runoffResponse.js +2 -4
  236. package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
  237. package/src/simulation/vehicle/vehicleGeometry.js +85 -103
  238. package/src/simulation/vehicle/vehicleKinematics.js +10 -0
  239. package/src/simulation/vehicle/vehiclePhysics.js +42 -196
  240. package/src/simulation/vehicle/vehicleSnapshots.js +18 -6
  241. package/src/simulation/vehicle/vehicleState.js +5 -3
  242. package/src/simulation/vehicle/wheelSurface.js +13 -432
  243. package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
  244. package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
  245. package/src/styles.css +173 -19
  246. package/src/ui/bannerTemplates.js +4 -2
  247. package/src/ui/cameraControlsTemplate.js +4 -2
  248. package/src/ui/gridWalkTemplate.js +29 -0
  249. package/src/ui/htmlEscaping.js +8 -0
  250. package/src/ui/raceCanvasTemplate.js +3 -2
  251. package/src/ui/shellTemplate.js +3 -0
  252. package/src/ui/telemetryDrawerTemplate.js +4 -2
  253. package/src/ui/telemetryTemplates.js +3 -22
  254. package/src/ui/templateUtils.js +2 -8
  255. package/src/ui/viewControlsTemplate.js +21 -0
  256. package/examples/train-basic-policy.mjs +0 -276
  257. package/examples/trainingData.mjs +0 -133
@@ -1,43 +1,17 @@
1
- export type TireCompound = 'S' | 'M' | 'H';
1
+ import type { PaddockProceduralTrackProfile, PaddockProceduralTrackOptions, PaddockTrackPoint, PaddockDrsZone, PaddockResolvedProceduralTrackOptions, PaddockProceduralTrack } from '../data/trackTypes.js';
2
+ import type { TireCompound, SimulatorDriver, ChampionshipEntryBlueprint } from '../data/entryTypes.js';
3
+ export type { PaddockProceduralTrackProfile, PaddockProceduralTrackOptions, PaddockTrackPoint, PaddockDrsZone, PaddockResolvedProceduralTrackOptions, PaddockProceduralTrack } from '../data/trackTypes.js';
4
+ export type { TireCompound, SimulatorDriver, ChampionshipEntryBlueprint } from '../data/entryTypes.js';
5
+
6
+ import type { TeamData } from '../data/teamTypes.js';
7
+
8
+ export type {
9
+ PaddockPitCrewStats,
10
+ PaddockThemeSelector,
11
+ TeamData,
12
+ } from '../data/teamTypes.js';
13
+
2
14
  export type PaddockPhysicsMode = 'arcade' | 'advanced';
3
- export type PaddockProceduralTrackProfile = 'race' | 'training-short' | 'training-medium' | 'training-technical';
4
-
5
- export interface PaddockProceduralTrackOptions {
6
- profile?: PaddockProceduralTrackProfile;
7
- minLengthMeters?: number;
8
- maxLengthMeters?: number;
9
- startStraightMeters?: number;
10
- includePitLane?: boolean;
11
- length?: {
12
- minMeters?: number;
13
- maxMeters?: number;
14
- };
15
- startStraight?: {
16
- gridMeters?: number;
17
- exitMeters?: number;
18
- blendMeters?: number;
19
- lockExtraMeters?: number;
20
- };
21
- pitLane?: {
22
- enabled?: boolean;
23
- };
24
- shape?: {
25
- scale?: number;
26
- cornerDensity?: number;
27
- variation?: number;
28
- };
29
- validation?: {
30
- minClearanceMultiplier?: number;
31
- minShapeVariation?: number;
32
- minNonAdjacentArcMeters?: number;
33
- maxLocalTurnRadians?: number;
34
- maxSampleHeadingDeltaRadians?: number;
35
- };
36
- attempts?: {
37
- primary?: number;
38
- fallback?: number;
39
- };
40
- }
41
15
 
42
16
  export type PaddockStabilityState = 'stable' | 'understeer' | 'oversteer' | 'spin-risk' | 'destroyed';
43
17
  export type PaddockPitIntent = 0 | 1 | 2;
@@ -109,45 +83,6 @@ export interface PaddockReplayGhostSnapshot {
109
83
  };
110
84
  }
111
85
 
112
- export interface TeamData {
113
- id?: string;
114
- name?: string;
115
- color?: string;
116
- icon?: string;
117
- pitCrew?: PaddockPitCrewStats;
118
- pitCrewStats?: PaddockPitCrewStats;
119
- }
120
-
121
- export interface PaddockPitCrewStats {
122
- speed?: number;
123
- consistency?: number;
124
- reliability?: number;
125
- }
126
-
127
- export interface SimulatorDriver {
128
- id: string;
129
- name: string;
130
- color: string;
131
- link?: string;
132
- icon?: string;
133
- code?: string;
134
- timingName?: string;
135
- tire?: TireCompound;
136
- raceData?: string[];
137
- customFields?: Array<{ label: string; value: string }> | Record<string, string>;
138
- team?: TeamData | null;
139
- driverNumber?: number;
140
- }
141
-
142
- export interface ChampionshipEntryBlueprint {
143
- driverId: string;
144
- driverNumber?: number;
145
- timingName?: string;
146
- driver?: unknown;
147
- vehicle?: unknown;
148
- team?: TeamData;
149
- }
150
-
151
86
  export interface RaceEvent {
152
87
  type: string;
153
88
  at?: number;
@@ -668,14 +603,16 @@ export type PaddockPolicyLike =
668
603
  | { predict(observation: PaddockDriverObservation, context: Record<string, unknown>): PaddockAction };
669
604
 
670
605
  export interface PaddockDriverControllerContext {
606
+ /** Generation cancellation signal; Paddock-owned loops always supply it. */
607
+ signal?: AbortSignal;
671
608
  runtime: PaddockControllerRuntime;
672
609
  mode: string;
673
610
  actionRepeat: number;
674
611
  controlledDrivers: string[];
675
612
  actionSpec: PaddockActionSpec;
676
613
  observationSpec: PaddockObservationSpec;
677
- result: PaddockEnvironmentResult | unknown;
678
- previousResult: PaddockEnvironmentResult | unknown | null;
614
+ result: PaddockEnvironmentResult;
615
+ previousResult: PaddockEnvironmentResult | null;
679
616
  observation: Record<string, PaddockDriverObservation>;
680
617
  orderedObservations: Array<{
681
618
  driverId: string;
@@ -685,7 +622,7 @@ export interface PaddockDriverControllerContext {
685
622
  }>;
686
623
  metrics: Record<string, PaddockEnvironmentDriverMetrics>;
687
624
  events: RaceEvent[];
688
- info: PaddockEnvironmentResult['info'] | unknown | null;
625
+ info: PaddockEnvironmentResult['info'] | null;
689
626
  previousActions: PaddockActionMap;
690
627
  orderedPreviousActions: Array<PaddockAction | null>;
691
628
  actions: PaddockActionMap | null;
@@ -721,14 +658,14 @@ export interface PaddockDriverController {
721
658
  }
722
659
 
723
660
  export interface PaddockControllerRuntime {
724
- reset(options?: unknown): PaddockEnvironmentResult | unknown;
725
- step(actions: PaddockActionMap): PaddockEnvironmentResult | unknown;
661
+ reset(options?: unknown): PaddockEnvironmentResult;
662
+ step(actions: PaddockActionMap): PaddockEnvironmentResult;
726
663
  resetDrivers?(
727
664
  placements: Record<string, PaddockScenarioPlacement>,
728
665
  resultOptions?: PaddockEnvironmentResultOptions
729
- ): PaddockEnvironmentResult | unknown;
666
+ ): PaddockEnvironmentResult;
730
667
  getObservation(): Record<string, PaddockDriverObservation>;
731
- getState?(options?: { output?: 'full' | 'minimal' | 'none' }): PaddockEnvironmentResult['state'] | unknown;
668
+ getState?(options?: { output?: 'full' | 'minimal' | 'none' }): PaddockEnvironmentResult['state'];
732
669
  getActionSpec(): PaddockActionSpec;
733
670
  getObservationSpec(): PaddockObservationSpec;
734
671
  destroy?(): void;
@@ -743,7 +680,7 @@ export interface PaddockDriverControllerLoopOptions {
743
680
  }
744
681
 
745
682
  export interface PaddockDriverControllerLoop {
746
- readonly result: PaddockEnvironmentResult | unknown | null;
683
+ readonly result: PaddockEnvironmentResult | null;
747
684
  readonly actionSpec: PaddockActionSpec | null;
748
685
  readonly observationSpec: PaddockObservationSpec | null;
749
686
  readonly stats: {
@@ -756,13 +693,13 @@ export interface PaddockDriverControllerLoop {
756
693
  running: boolean;
757
694
  lastError: unknown | null;
758
695
  };
759
- reset(options?: unknown): Promise<PaddockEnvironmentResult | unknown>;
696
+ reset(options?: unknown): Promise<PaddockEnvironmentResult>;
760
697
  resetDrivers(
761
698
  placements: Record<string, PaddockScenarioPlacement>,
762
699
  resultOptions?: PaddockEnvironmentResultOptions
763
- ): Promise<PaddockEnvironmentResult | unknown>;
764
- step(): Promise<PaddockEnvironmentResult | unknown>;
765
- stepFrame(): Promise<PaddockEnvironmentResult | unknown>;
700
+ ): Promise<PaddockEnvironmentResult>;
701
+ step(): Promise<PaddockEnvironmentResult>;
702
+ stepFrame(): Promise<PaddockEnvironmentResult>;
766
703
  start(): void;
767
704
  stop(): void;
768
705
  }
@@ -869,7 +806,13 @@ export function createRolloutTransition(
869
806
  ): PaddockRolloutTransition;
870
807
  export const ENVIRONMENT_SCENARIO_PRESETS: readonly PaddockScenarioPreset[];
871
808
  export const DEFAULT_EVALUATION_CASES: readonly PaddockEvaluationCase[];
872
- export function createEvaluationTracker(initialResult: PaddockEnvironmentSnapshotResult): {
809
+ export type PaddockEvaluationLapScope = 'since-start' | 'include-history';
810
+
811
+ export interface PaddockEvaluationTrackerOptions {
812
+ lapScope?: PaddockEvaluationLapScope;
813
+ }
814
+
815
+ export function createEvaluationTracker(initialResult: PaddockEnvironmentSnapshotResult, options?: PaddockEvaluationTrackerOptions): {
873
816
  update(result: PaddockEnvironmentSnapshotResult): void;
874
817
  finish(): Record<string, PaddockEvaluationDriverMetrics>;
875
818
  };
@@ -877,6 +820,7 @@ export function runEnvironmentEvaluation(options: {
877
820
  baseOptions: PaddockEnvironmentOptions;
878
821
  policy: PaddockPolicyLike;
879
822
  cases?: PaddockEvaluationCase[];
823
+ lapScope?: PaddockEvaluationLapScope;
880
824
  createEnvironment?: (options: PaddockEnvironmentOptions) => PaddockEnvironment;
881
825
  }): PaddockEvaluationReport;
882
826
  export function createEnvironmentWorkerProtocol(env: PaddockEnvironment): PaddockEnvironmentWorkerProtocol;
@@ -1,3 +1,5 @@
1
+ import { isEnvironmentContactEvent, writeEventDriverIds } from './events.js';
2
+ export { isEnvironmentContactEvent } from './events.js';
1
3
  import { normalizeAngle } from '../simulation/simMath.js';
2
4
  import { pointAt } from '../simulation/track/trackModel.js';
3
5
  import { simUnitsToMeters } from '../simulation/units.js';
@@ -26,10 +28,6 @@ export function isEnvironmentCarOffTrack(car) {
26
28
  return isOffTrack(car, classifyWheels(car));
27
29
  }
28
30
 
29
- export function isEnvironmentContactEvent(event) {
30
- return ['collision', 'contact', 'car-contact'].includes(event?.type);
31
- }
32
-
33
31
  export function buildDriverMetrics({ snapshot, previousSnapshot = null, options, events = [], scratch = null }) {
34
32
  const previousById = carsByIdForMetrics(previousSnapshot, scratch, 'previousCarsById');
35
33
  const currentById = carsByIdForMetrics(snapshot, scratch, 'currentCarsById');
@@ -179,14 +177,7 @@ function contactCountsByDriver(events, scratch = null) {
179
177
  counts.clear();
180
178
  events.forEach((event) => {
181
179
  if (!isEnvironmentContactEvent(event)) return;
182
- eventDriverIds.length = 0;
183
- pushEventDriverId(eventDriverIds, event.driverId);
184
- pushEventDriverId(eventDriverIds, event.carId);
185
- pushEventDriverId(eventDriverIds, event.otherCarId);
186
- const extraDriverIds = event.driverIds ?? [];
187
- for (let index = 0; index < extraDriverIds.length; index += 1) {
188
- pushEventDriverId(eventDriverIds, extraDriverIds[index]);
189
- }
180
+ writeEventDriverIds(eventDriverIds, event);
190
181
  for (let index = 0; index < eventDriverIds.length; index += 1) {
191
182
  const driverId = eventDriverIds[index];
192
183
  counts.set(driverId, (counts.get(driverId) ?? 0) + 1);
@@ -195,8 +186,3 @@ function contactCountsByDriver(events, scratch = null) {
195
186
  eventDriverIds.length = 0;
196
187
  return counts;
197
188
  }
198
-
199
- function pushEventDriverId(target, driverId) {
200
- if (!driverId || target.includes(driverId)) return;
201
- target.push(driverId);
202
- }
@@ -0,0 +1,104 @@
1
+ export const MODEL_RAY_SURFACE_CHANNELS = Object.freeze(['kerb', 'illegalSurface']);
2
+
3
+ export function buildObservationVectorSchema({
4
+ profile = 'default',
5
+ contactPatchCount = 4,
6
+ lookaheadCount = 0,
7
+ rayCount = 0,
8
+ rayChannels = [],
9
+ nearbyCount = 0,
10
+ }) {
11
+ const includePhysicalDriverSenses = profile === 'physical-driver';
12
+ const schema = [
13
+ { name: 'self.speedKph', unit: 'kph', scale: 'fixed:400' },
14
+ { name: 'self.speedMetersPerSecond', unit: 'm/s', scale: 'fixed:120' },
15
+ { name: 'self.steeringAngleRadians', unit: 'rad', scale: 'fixed:pi' },
16
+ { name: 'self.throttle', scale: '0..1' },
17
+ { name: 'self.brake', scale: '0..1' },
18
+ { name: 'self.lateralG', scale: 'fixed:8g' },
19
+ { name: 'self.longitudinalG', scale: 'fixed:6g' },
20
+ { name: 'self.gripUsage', scale: '0..2' },
21
+ { name: 'self.slipAngleRadians', unit: 'rad', scale: 'fixed:pi' },
22
+ { name: 'self.tractionLimited', scale: 'boolean' },
23
+ { name: 'self.lapProgressRatio', scale: '0..1' },
24
+ { name: 'self.trackOffsetMeters', unit: 'm', scale: 'fixed:meters' },
25
+ { name: 'self.trackHeadingErrorRadians', unit: 'rad', scale: 'fixed:pi' },
26
+ { name: 'self.onTrack', scale: 'boolean' },
27
+ { name: 'self.inPitLane', scale: 'boolean' },
28
+ { name: 'self.tireEnergy', scale: '0..100' },
29
+ { name: 'self.pitIntent', scale: '0..2' },
30
+ { name: 'self.pitStopActive', scale: 'boolean' },
31
+ { name: 'race.positionNormalized', scale: '0..1' },
32
+ { name: 'race.raceModeGreen', scale: 'boolean' },
33
+ { name: 'race.raceModeSafetyCar', scale: 'boolean' },
34
+ { name: 'race.redFlag', scale: 'boolean' },
35
+ { name: 'race.pitLaneOpen', scale: 'boolean' },
36
+ { name: 'track.curvature', scale: 'track-curvature' },
37
+ ];
38
+ if (includePhysicalDriverSenses) {
39
+ schema.push(
40
+ { name: 'self.yawRateRadiansPerSecond', unit: 'rad/s', scale: 'fixed:pi' },
41
+ { name: 'trackRelation.leftBoundaryMeters', unit: 'm', scale: 'fixed:meters' },
42
+ { name: 'trackRelation.rightBoundaryMeters', unit: 'm', scale: 'fixed:meters' },
43
+ { name: 'trackRelation.legalWidthMeters', unit: 'm', scale: 'fixed:meters' },
44
+ );
45
+ for (let index = 0; index < contactPatchCount; index += 1) {
46
+ schema.push(
47
+ { name: `contactPatches[${index}].present`, scale: 'boolean' },
48
+ { name: `contactPatches[${index}].surfaceCode`, scale: 'surface-code' },
49
+ { name: `contactPatches[${index}].onLegalSurface`, scale: 'boolean' },
50
+ { name: `contactPatches[${index}].signedOffsetMeters`, unit: 'm', scale: 'fixed:meters' },
51
+ { name: `contactPatches[${index}].crossTrackErrorMeters`, unit: 'm', scale: 'fixed:meters' },
52
+ );
53
+ }
54
+ }
55
+ for (let index = 0; index < lookaheadCount; index += 1) {
56
+ schema.push(
57
+ { name: `track.lookahead[${index}].curvature`, scale: 'track-curvature' },
58
+ { name: `track.lookahead[${index}].headingDeltaRadians`, unit: 'rad', scale: 'fixed:pi' },
59
+ );
60
+ }
61
+ for (let index = 0; index < rayCount; index += 1) {
62
+ schema.push(
63
+ { name: `rays[${index}].track.distanceRatio`, scale: '0..1' },
64
+ { name: `rays[${index}].track.hit`, scale: 'boolean' },
65
+ { name: `rays[${index}].track.kindExit`, scale: 'boolean' },
66
+ { name: `rays[${index}].track.kindEntry`, scale: 'boolean' },
67
+ { name: `rays[${index}].car.distanceRatio`, scale: '0..1' },
68
+ { name: `rays[${index}].car.hit`, scale: 'boolean' },
69
+ { name: `rays[${index}].car.relativeSpeedKph`, unit: 'kph', scale: 'fixed:200' },
70
+ { name: `rays[${index}].car.targetTypeReplayGhost`, scale: 'boolean' },
71
+ );
72
+ MODEL_RAY_SURFACE_CHANNELS.forEach((channel) => {
73
+ if (rayChannels?.includes?.(channel)) {
74
+ schema.push(
75
+ { name: `rays[${index}].${channel}.distanceRatio`, scale: '0..1' },
76
+ { name: `rays[${index}].${channel}.hit`, scale: 'boolean' },
77
+ );
78
+ }
79
+ });
80
+ }
81
+ for (let index = 0; index < nearbyCount; index += 1) {
82
+ schema.push(
83
+ { name: `nearbyCars[${index}].present`, scale: 'boolean' },
84
+ { name: `nearbyCars[${index}].relativeForwardRatio`, scale: '-1..1' },
85
+ { name: `nearbyCars[${index}].relativeRightRatio`, scale: '-1..1' },
86
+ { name: `nearbyCars[${index}].relativeDistanceRatio`, scale: '0..1' },
87
+ { name: `nearbyCars[${index}].relativeSpeedKph`, unit: 'kph', scale: 'fixed:200' },
88
+ { name: `nearbyCars[${index}].relativeHeadingRadians`, unit: 'rad', scale: 'fixed:pi' },
89
+ { name: `nearbyCars[${index}].ahead`, scale: 'boolean' },
90
+ { name: `nearbyCars[${index}].sameLap`, scale: 'boolean' },
91
+ { name: `nearbyCars[${index}].entityTypeReplayGhost`, scale: 'boolean' },
92
+ );
93
+ if (includePhysicalDriverSenses) {
94
+ schema.push(
95
+ { name: `nearbyCars[${index}].behind`, scale: 'boolean' },
96
+ { name: `nearbyCars[${index}].closingRateMetersPerSecond`, unit: 'm/s', scale: 'fixed:100' },
97
+ { name: `nearbyCars[${index}].timeToContactSeconds`, unit: 's', scale: 'fixed:10' },
98
+ { name: `nearbyCars[${index}].leftOverlap`, scale: 'boolean' },
99
+ { name: `nearbyCars[${index}].rightOverlap`, scale: 'boolean' },
100
+ );
101
+ }
102
+ }
103
+ return schema;
104
+ }
@@ -1,33 +1,8 @@
1
- const MODEL_RAY_SURFACE_CHANNELS = Object.freeze(['kerb', 'illegalSurface']);
1
+ import { appendRayVectorValues } from './sensors/rayOutputs.js';
2
+ import { buildObservationVectorSchema, MODEL_RAY_SURFACE_CHANNELS } from './observationSchema.js';
2
3
 
3
4
  export function buildObservationVector(source, sensors, { includeSchema = true, vectorType = 'array' } = {}) {
4
5
  const includePhysicalDriverSenses = source.profile === 'physical-driver';
5
- const schema = includeSchema ? [
6
- { name: 'self.speedKph', unit: 'kph', scale: 'fixed:400' },
7
- { name: 'self.speedMetersPerSecond', unit: 'm/s', scale: 'fixed:120' },
8
- { name: 'self.steeringAngleRadians', unit: 'rad', scale: 'fixed:pi' },
9
- { name: 'self.throttle', scale: '0..1' },
10
- { name: 'self.brake', scale: '0..1' },
11
- { name: 'self.lateralG', scale: 'fixed:8g' },
12
- { name: 'self.longitudinalG', scale: 'fixed:6g' },
13
- { name: 'self.gripUsage', scale: '0..2' },
14
- { name: 'self.slipAngleRadians', unit: 'rad', scale: 'fixed:pi' },
15
- { name: 'self.tractionLimited', scale: 'boolean' },
16
- { name: 'self.lapProgressRatio', scale: '0..1' },
17
- { name: 'self.trackOffsetMeters', unit: 'm', scale: 'fixed:meters' },
18
- { name: 'self.trackHeadingErrorRadians', unit: 'rad', scale: 'fixed:pi' },
19
- { name: 'self.onTrack', scale: 'boolean' },
20
- { name: 'self.inPitLane', scale: 'boolean' },
21
- { name: 'self.tireEnergy', scale: '0..100' },
22
- { name: 'self.pitIntent', scale: '0..2' },
23
- { name: 'self.pitStopActive', scale: 'boolean' },
24
- { name: 'race.positionNormalized', scale: '0..1' },
25
- { name: 'race.raceModeGreen', scale: 'boolean' },
26
- { name: 'race.raceModeSafetyCar', scale: 'boolean' },
27
- { name: 'race.redFlag', scale: 'boolean' },
28
- { name: 'race.pitLaneOpen', scale: 'boolean' },
29
- { name: 'track.curvature', scale: 'track-curvature' },
30
- ] : null;
31
6
  const vector = [
32
7
  source.self.speedKph / 400,
33
8
  source.self.speedMetersPerSecond / 120,
@@ -55,26 +30,13 @@ export function buildObservationVector(source, sensors, { includeSchema = true,
55
30
  source.track.curvature ?? 0,
56
31
  ];
57
32
  if (includePhysicalDriverSenses) {
58
- pushSchema(schema,
59
- { name: 'self.yawRateRadiansPerSecond', unit: 'rad/s', scale: 'fixed:pi' },
60
- { name: 'trackRelation.leftBoundaryMeters', unit: 'm', scale: 'fixed:meters' },
61
- { name: 'trackRelation.rightBoundaryMeters', unit: 'm', scale: 'fixed:meters' },
62
- { name: 'trackRelation.legalWidthMeters', unit: 'm', scale: 'fixed:meters' },
63
- );
64
33
  vector.push(
65
34
  source.self.yawRateRadiansPerSecond / Math.PI,
66
35
  source.trackRelation.leftBoundaryMeters,
67
36
  source.trackRelation.rightBoundaryMeters,
68
37
  source.trackRelation.legalWidthMeters,
69
38
  );
70
- source.contactPatches.forEach((patch, index) => {
71
- pushSchema(schema,
72
- { name: `contactPatches[${index}].present`, scale: 'boolean' },
73
- { name: `contactPatches[${index}].surfaceCode`, scale: 'surface-code' },
74
- { name: `contactPatches[${index}].onLegalSurface`, scale: 'boolean' },
75
- { name: `contactPatches[${index}].signedOffsetMeters`, unit: 'm', scale: 'fixed:meters' },
76
- { name: `contactPatches[${index}].crossTrackErrorMeters`, unit: 'm', scale: 'fixed:meters' },
77
- );
39
+ source.contactPatches.forEach((patch) => {
78
40
  vector.push(
79
41
  patch.present ? 1 : 0,
80
42
  patch.surfaceCode / 5,
@@ -84,58 +46,21 @@ export function buildObservationVector(source, sensors, { includeSchema = true,
84
46
  );
85
47
  });
86
48
  }
87
- source.track.lookahead.forEach((sample, index) => {
88
- pushSchema(schema,
89
- { name: `track.lookahead[${index}].curvature`, scale: 'track-curvature' },
90
- { name: `track.lookahead[${index}].headingDeltaRadians`, unit: 'rad', scale: 'fixed:pi' },
91
- );
49
+ source.track.lookahead.forEach((sample) => {
92
50
  vector.push(sample.curvature ?? 0, (sample.headingDeltaRadians ?? 0) / Math.PI);
93
51
  });
94
52
  if (typeof source.appendRayVectorValues === 'function') {
95
53
  source.appendRayVectorValues(vector);
96
- } else if (Array.isArray(source.rayVectorValues)) {
97
- source.rayVectorValues.forEach((values, index) => {
98
- pushRaySchema(schema, index, sensors);
99
- vector.push(...values);
100
- });
101
54
  } else {
102
- source.rays.forEach((ray, index) => {
103
- pushRaySchema(schema, index, sensors);
104
- vector.push(
105
- ratio(ray.track.distanceMeters, ray.lengthMeters),
106
- ray.track.hit ? 1 : 0,
107
- ray.track.kind === 'exit' ? 1 : 0,
108
- ray.track.kind === 'entry' ? 1 : 0,
109
- ratio(ray.car.distanceMeters, ray.lengthMeters),
110
- ray.car.hit ? 1 : 0,
111
- ray.car.relativeSpeedKph / 200,
112
- ray.car.targetType === 'replayGhost' ? 1 : 0,
113
- );
114
- MODEL_RAY_SURFACE_CHANNELS.forEach((channel) => {
115
- if (sensors.rays.channels?.includes?.(channel)) {
116
- vector.push(
117
- ratio(ray[channel]?.distanceMeters ?? ray.lengthMeters, ray.lengthMeters),
118
- ray[channel]?.hit ? 1 : 0,
119
- );
120
- }
121
- });
55
+ const surfaceChannels = MODEL_RAY_SURFACE_CHANNELS.filter((channel) => sensors.rays.channels?.includes?.(channel));
56
+ source.rays.forEach((ray) => {
57
+ appendRayVectorValues(vector, ray.lengthMeters, ray.track, ray.car, ray, surfaceChannels);
122
58
  });
123
59
  }
124
60
  const nearbyLimit = sensors.nearbyCars.enabled ? (sensors.nearbyCars.maxCars ?? source.nearbyCars.length) : 0;
125
61
  const nearbyRadius = sensors.nearbyCars.radiusMeters ?? 150;
126
62
  for (let index = 0; index < nearbyLimit; index += 1) {
127
63
  const nearby = source.nearbyCars[index] ?? null;
128
- pushSchema(schema,
129
- { name: `nearbyCars[${index}].present`, scale: 'boolean' },
130
- { name: `nearbyCars[${index}].relativeForwardRatio`, scale: '-1..1' },
131
- { name: `nearbyCars[${index}].relativeRightRatio`, scale: '-1..1' },
132
- { name: `nearbyCars[${index}].relativeDistanceRatio`, scale: '0..1' },
133
- { name: `nearbyCars[${index}].relativeSpeedKph`, unit: 'kph', scale: 'fixed:200' },
134
- { name: `nearbyCars[${index}].relativeHeadingRadians`, unit: 'rad', scale: 'fixed:pi' },
135
- { name: `nearbyCars[${index}].ahead`, scale: 'boolean' },
136
- { name: `nearbyCars[${index}].sameLap`, scale: 'boolean' },
137
- { name: `nearbyCars[${index}].entityTypeReplayGhost`, scale: 'boolean' },
138
- );
139
64
  vector.push(
140
65
  nearby ? 1 : 0,
141
66
  clampRatio((nearby?.relativeForwardMeters ?? 0) / nearbyRadius),
@@ -148,13 +73,6 @@ export function buildObservationVector(source, sensors, { includeSchema = true,
148
73
  nearby?.entityType === 'replayGhost' ? 1 : 0,
149
74
  );
150
75
  if (includePhysicalDriverSenses) {
151
- pushSchema(schema,
152
- { name: `nearbyCars[${index}].behind`, scale: 'boolean' },
153
- { name: `nearbyCars[${index}].closingRateMetersPerSecond`, unit: 'm/s', scale: 'fixed:100' },
154
- { name: `nearbyCars[${index}].timeToContactSeconds`, unit: 's', scale: 'fixed:10' },
155
- { name: `nearbyCars[${index}].leftOverlap`, scale: 'boolean' },
156
- { name: `nearbyCars[${index}].rightOverlap`, scale: 'boolean' },
157
- );
158
76
  vector.push(
159
77
  nearby?.behind ? 1 : 0,
160
78
  (nearby?.closingRateMetersPerSecond ?? 0) / 100,
@@ -164,38 +82,21 @@ export function buildObservationVector(source, sensors, { includeSchema = true,
164
82
  );
165
83
  }
166
84
  }
167
- return { vector: finalizeVector(vector, vectorType), schema: schema ?? [] };
85
+ const schema = includeSchema ? buildObservationVectorSchema({
86
+ profile: source.profile,
87
+ contactPatchCount: source.contactPatches?.length ?? 0,
88
+ lookaheadCount: source.track.lookahead.length,
89
+ rayCount: typeof source.appendRayVectorValues === 'function' ? 0 : source.rays.length,
90
+ rayChannels: sensors.rays.channels,
91
+ nearbyCount: nearbyLimit,
92
+ }) : [];
93
+ return { vector: finalizeVector(vector, vectorType), schema };
168
94
  }
169
95
 
170
96
  function finalizeVector(vector, vectorType = 'array') {
171
97
  return vectorType === 'float32' ? Float32Array.from(vector) : vector;
172
98
  }
173
99
 
174
- function pushSchema(schema, ...entries) {
175
- if (schema) schema.push(...entries);
176
- }
177
-
178
- function pushRaySchema(schema, index, sensors) {
179
- pushSchema(schema,
180
- { name: `rays[${index}].track.distanceRatio`, scale: '0..1' },
181
- { name: `rays[${index}].track.hit`, scale: 'boolean' },
182
- { name: `rays[${index}].track.kindExit`, scale: 'boolean' },
183
- { name: `rays[${index}].track.kindEntry`, scale: 'boolean' },
184
- { name: `rays[${index}].car.distanceRatio`, scale: '0..1' },
185
- { name: `rays[${index}].car.hit`, scale: 'boolean' },
186
- { name: `rays[${index}].car.relativeSpeedKph`, unit: 'kph', scale: 'fixed:200' },
187
- { name: `rays[${index}].car.targetTypeReplayGhost`, scale: 'boolean' },
188
- );
189
- MODEL_RAY_SURFACE_CHANNELS.forEach((channel) => {
190
- if (sensors.rays.channels?.includes?.(channel)) {
191
- pushSchema(schema,
192
- { name: `rays[${index}].${channel}.distanceRatio`, scale: '0..1' },
193
- { name: `rays[${index}].${channel}.hit`, scale: 'boolean' },
194
- );
195
- }
196
- });
197
- }
198
-
199
100
  function normalizeLapProgress(source) {
200
101
  return ratio(source.self.lapProgressMeters, source.track.lengthMeters || 1);
201
102
  }