@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,199 +1,16 @@
1
- export type TireCompound = 'S' | 'M' | 'H';
2
- export type PaddockProceduralTrackProfile = 'race' | 'training-short' | 'training-medium' | 'training-technical';
1
+ import type { TeamData } from './teamTypes.js';
3
2
 
4
- export interface PaddockProceduralTrackOptions {
5
- profile?: PaddockProceduralTrackProfile;
6
- minLengthMeters?: number;
7
- maxLengthMeters?: number;
8
- startStraightMeters?: number;
9
- includePitLane?: boolean;
10
- length?: {
11
- minMeters?: number;
12
- maxMeters?: number;
13
- };
14
- startStraight?: {
15
- gridMeters?: number;
16
- exitMeters?: number;
17
- blendMeters?: number;
18
- lockExtraMeters?: number;
19
- };
20
- pitLane?: {
21
- enabled?: boolean;
22
- };
23
- shape?: {
24
- scale?: number;
25
- cornerDensity?: number;
26
- variation?: number;
27
- };
28
- validation?: {
29
- minClearanceMultiplier?: number;
30
- minShapeVariation?: number;
31
- minNonAdjacentArcMeters?: number;
32
- maxLocalTurnRadians?: number;
33
- maxSampleHeadingDeltaRadians?: number;
34
- };
35
- attempts?: {
36
- primary?: number;
37
- fallback?: number;
38
- };
39
- }
3
+ export type {
4
+ PaddockPitCrewStats,
5
+ PaddockThemeSelector,
6
+ TeamData,
7
+ } from './teamTypes.js';
40
8
 
41
- export interface CustomField {
42
- label: string;
43
- value: string;
44
- }
45
-
46
- export type CustomFieldInput = CustomField[] | Record<string, string>;
47
- export type PaddockThemeSelector = 'default' | 'active' | 'selectedTeam' | 'team' | `team:${string}` | string;
48
-
49
- export interface PaddockPitCrewStats {
50
- speed?: number;
51
- consistency?: number;
52
- reliability?: number;
53
- }
54
-
55
- export interface TeamData {
56
- id?: string;
57
- name?: string;
58
- color?: string;
59
- icon?: string;
60
- theme?: PaddockThemeSelector;
61
- pitCrew?: PaddockPitCrewStats;
62
- pitCrewStats?: PaddockPitCrewStats;
63
- }
64
-
65
- export interface SimulatorDriver {
66
- id: string;
67
- name: string;
68
- color: string;
69
- link?: string;
70
- icon?: string;
71
- code?: string;
72
- timingName?: string;
73
- tire?: TireCompound;
74
- raceData?: string[];
75
- customFields?: CustomFieldInput;
76
- team?: TeamData | null;
77
- driverNumber?: number;
78
- }
79
-
80
- export interface DriverRatings {
81
- pace: number;
82
- racecraft: number;
83
- aggression: number;
84
- riskTolerance: number;
85
- patience: number;
86
- consistency: number;
87
- }
88
-
89
- export interface VehicleRatings {
90
- power: number;
91
- braking: number;
92
- aero: number;
93
- dragEfficiency: number;
94
- mechanicalGrip: number;
95
- weightControl: number;
96
- tireCare: number;
97
- }
98
-
99
- export interface DriverBlueprint extends Partial<DriverRatings> {
100
- customFields?: CustomFieldInput;
101
- }
102
-
103
- export interface VehicleBlueprint extends Partial<VehicleRatings> {
104
- id?: string | null;
105
- name?: string | null;
106
- customFields?: CustomFieldInput;
107
- }
108
-
109
- export interface DriverConstructorArgs {
110
- ratings: DriverRatings;
111
- customFields: CustomField[];
112
- pace: number;
113
- racecraft: number;
114
- consistency: number;
115
- personality: {
116
- aggression: number;
117
- riskTolerance: number;
118
- patience: number;
119
- };
120
- }
121
-
122
- export interface VehicleConstructorArgs {
123
- id: string | null;
124
- name: string | null;
125
- ratings: VehicleRatings;
126
- customFields: CustomField[];
127
- powerNewtons: number;
128
- brakeNewtons: number;
129
- downforceCoefficient: number;
130
- dragCoefficient: number;
131
- tireGrip: number;
132
- mass: number;
133
- tireCare: number;
134
- }
135
-
136
- export class DriverData {
137
- constructor(input?: DriverBlueprint);
138
- readonly pace: number;
139
- readonly racecraft: number;
140
- readonly aggression: number;
141
- readonly riskTolerance: number;
142
- readonly patience: number;
143
- readonly consistency: number;
144
- readonly customFields: CustomField[];
145
- ratings(): DriverRatings;
146
- toConstructorArgs(): DriverConstructorArgs;
147
- }
148
-
149
- export class VehicleData {
150
- constructor(input?: VehicleBlueprint);
151
- readonly id: string | null;
152
- readonly name: string | null;
153
- readonly power: number;
154
- readonly braking: number;
155
- readonly aero: number;
156
- readonly dragEfficiency: number;
157
- readonly mechanicalGrip: number;
158
- readonly weightControl: number;
159
- readonly tireCare: number;
160
- readonly customFields: CustomField[];
161
- ratings(): VehicleRatings;
162
- toConstructorArgs(): VehicleConstructorArgs;
163
- }
164
-
165
- export interface ChampionshipEntryBlueprint {
166
- driverId: string;
167
- driverNumber?: number;
168
- timingName?: string;
169
- driver?: DriverData | DriverBlueprint;
170
- vehicle?: VehicleData | VehicleBlueprint;
171
- team?: TeamData;
172
- }
173
-
174
- export interface NormalizedSimulatorDriver extends SimulatorDriver {
175
- code: string;
176
- timingCode: string;
177
- raceName: string;
178
- driverNumber: number;
179
- team: TeamData | null;
180
- pace: number;
181
- racecraft: number;
182
- consistency: number;
183
- personality: DriverConstructorArgs['personality'];
184
- vehicle: VehicleConstructorArgs;
185
- constructorArgs: {
186
- driver: DriverConstructorArgs;
187
- vehicle: VehicleConstructorArgs;
188
- };
189
- championship: {
190
- id: string;
191
- name: string;
192
- season: number;
193
- entryIndex: number;
194
- vehicleId: string | null;
195
- };
196
- }
9
+ import type { PaddockProceduralTrackProfile, PaddockProceduralTrackOptions, PaddockTrackPoint, PaddockDrsZone, PaddockResolvedProceduralTrackOptions, PaddockProceduralTrack } from './trackTypes.js';
10
+ import type { TireCompound, CustomField, CustomFieldInput, EntryPresentation, SimulatorDriver, DriverRatings, VehicleRatings, DriverBlueprint, VehicleBlueprint, DriverConstructorArgs, VehicleConstructorArgs, DriverData, VehicleData, ChampionshipEntryBlueprint, NormalizedSimulatorDriver } from './entryTypes.js';
11
+ export type { PaddockProceduralTrackProfile, PaddockProceduralTrackOptions, PaddockTrackPoint, PaddockDrsZone, PaddockResolvedProceduralTrackOptions, PaddockProceduralTrack } from './trackTypes.js';
12
+ export type { TireCompound, CustomField, CustomFieldInput, EntryPresentation, SimulatorDriver, DriverRatings, VehicleRatings, DriverBlueprint, VehicleBlueprint, DriverConstructorArgs, VehicleConstructorArgs, ChampionshipEntryBlueprint, NormalizedSimulatorDriver } from './entryTypes.js';
13
+ export { DriverData, VehicleData } from './entryTypes.js';
197
14
 
198
15
  export const CHAMPIONSHIP_ENTRY_BLUEPRINTS: ChampionshipEntryBlueprint[];
199
16
  export const DEMO_PROJECT_DRIVERS: SimulatorDriver[];
@@ -207,6 +24,6 @@ export function normalizeSimulatorDrivers(
207
24
  drivers: SimulatorDriver[],
208
25
  options?: { entries?: ChampionshipEntryBlueprint[]; caller?: string },
209
26
  ): NormalizedSimulatorDriver[];
210
- export function createProceduralTrack(seed?: number | string, options?: PaddockProceduralTrackOptions): unknown;
27
+ export function createProceduralTrack(seed?: number | string, options?: PaddockProceduralTrackOptions): PaddockProceduralTrack;
211
28
  export function kphToSimSpeed(kph: number): number;
212
29
  export function simSpeedToKph(simSpeed: number): number;
@@ -0,0 +1,14 @@
1
+ export const RATING_MINIMUM = 0;
2
+ export const RATING_NEUTRAL = 50;
3
+ export const RATING_MAXIMUM = 100;
4
+
5
+ export function clampRating(value, label, domain) {
6
+ const rating = Number(value);
7
+ if (!Number.isFinite(rating)) throw new Error(`Invalid ${domain} rating for ${label}: ${value}`);
8
+ return Math.min(Math.max(rating, RATING_MINIMUM), RATING_MAXIMUM);
9
+ }
10
+
11
+ export function applyRating(definition, value, direction = 1) {
12
+ const normalized = (value - definition.neutral) / (definition.maximum - definition.neutral);
13
+ return definition.base + normalized * definition.variance * direction;
14
+ }
@@ -0,0 +1,23 @@
1
+ export type PaddockThemeSelector =
2
+ | 'default'
3
+ | 'active'
4
+ | 'selectedTeam'
5
+ | 'team'
6
+ | `team:${string}`
7
+ | string;
8
+
9
+ export interface PaddockPitCrewStats {
10
+ speed?: number;
11
+ consistency?: number;
12
+ reliability?: number;
13
+ }
14
+
15
+ export interface TeamData {
16
+ id?: string;
17
+ name?: string;
18
+ color?: string;
19
+ icon?: string;
20
+ theme?: PaddockThemeSelector;
21
+ pitCrew?: PaddockPitCrewStats;
22
+ pitCrewStats?: PaddockPitCrewStats;
23
+ }
@@ -0,0 +1,101 @@
1
+ export type PaddockProceduralTrackProfile = 'race' | 'training-short' | 'training-medium' | 'training-technical';
2
+
3
+ export interface PaddockProceduralTrackOptions {
4
+ profile?: PaddockProceduralTrackProfile;
5
+ minLengthMeters?: number;
6
+ maxLengthMeters?: number;
7
+ startStraightMeters?: number;
8
+ includePitLane?: boolean;
9
+ length?: {
10
+ minMeters?: number;
11
+ maxMeters?: number;
12
+ };
13
+ startStraight?: {
14
+ gridMeters?: number;
15
+ exitMeters?: number;
16
+ blendMeters?: number;
17
+ lockExtraMeters?: number;
18
+ };
19
+ pitLane?: {
20
+ enabled?: boolean;
21
+ };
22
+ shape?: {
23
+ scale?: number;
24
+ cornerDensity?: number;
25
+ variation?: number;
26
+ };
27
+ validation?: {
28
+ minClearanceMultiplier?: number;
29
+ minShapeVariation?: number;
30
+ minNonAdjacentArcMeters?: number;
31
+ maxLocalTurnRadians?: number;
32
+ maxSampleHeadingDeltaRadians?: number;
33
+ };
34
+ attempts?: {
35
+ primary?: number;
36
+ fallback?: number;
37
+ };
38
+ }
39
+
40
+ export interface PaddockTrackPoint {
41
+ x: number;
42
+ y: number;
43
+ }
44
+
45
+ export interface PaddockDrsZone {
46
+ id: string;
47
+ startRatio: number;
48
+ endRatio: number;
49
+ }
50
+
51
+ export interface PaddockResolvedProceduralTrackOptions {
52
+ profile: PaddockProceduralTrackProfile;
53
+ length: {
54
+ min: number;
55
+ max: number;
56
+ };
57
+ startStraight: {
58
+ grid: number;
59
+ exit: number;
60
+ blend: number;
61
+ lockExtra: number;
62
+ };
63
+ pitLane: {
64
+ enabled: boolean;
65
+ };
66
+ shape: {
67
+ scale: number;
68
+ cornerDensity: number;
69
+ variation: number;
70
+ };
71
+ validation: {
72
+ minClearanceMultiplier: number;
73
+ minShapeVariation: number;
74
+ minNonAdjacentArcDistance: number;
75
+ maxLocalTurnRadians: number;
76
+ maxSampleHeadingDeltaRadians: number;
77
+ };
78
+ attempts: {
79
+ primary: number;
80
+ fallback: number;
81
+ };
82
+ cacheKey: string;
83
+ [key: string]: unknown;
84
+ }
85
+
86
+ export interface PaddockProceduralTrack {
87
+ name: string;
88
+ width: number;
89
+ kerbWidth: number;
90
+ gravelWidth: number;
91
+ runoffWidth: number;
92
+ barrierWidth: number;
93
+ sampleCount: number;
94
+ seed?: number;
95
+ curveInterpolation?: string;
96
+ centerlineControls?: readonly PaddockTrackPoint[];
97
+ drsZones?: readonly PaddockDrsZone[] | null;
98
+ pitLane?: { enabled?: boolean } | null;
99
+ generationOptions?: PaddockResolvedProceduralTrackOptions;
100
+ [key: string]: unknown;
101
+ }
@@ -1,8 +1,6 @@
1
1
  import { normalizeCustomFields } from './customFields.js';
2
2
 
3
- const RATING_MINIMUM = 0;
4
- const RATING_NEUTRAL = 50;
5
- const RATING_MAXIMUM = 100;
3
+ import { RATING_MINIMUM, RATING_NEUTRAL, RATING_MAXIMUM, clampRating, applyRating } from './ratingScale.js';
6
4
 
7
5
  export const VEHICLE_STAT_DEFINITIONS = {
8
6
  power: { minimum: RATING_MINIMUM, neutral: RATING_NEUTRAL, maximum: RATING_MAXIMUM, base: 43000, variance: 3200, output: 'powerNewtons' },
@@ -14,18 +12,6 @@ export const VEHICLE_STAT_DEFINITIONS = {
14
12
  tireCare: { minimum: RATING_MINIMUM, neutral: RATING_NEUTRAL, maximum: RATING_MAXIMUM, base: 1, variance: 0.12, output: 'tireCare' },
15
13
  };
16
14
 
17
- function clampRating(value, label) {
18
- const rating = Number(value);
19
- if (!Number.isFinite(rating)) throw new Error(`Invalid vehicle rating for ${label}: ${value}`);
20
- return Math.min(Math.max(rating, RATING_MINIMUM), RATING_MAXIMUM);
21
- }
22
-
23
- function applyRating(definition, value) {
24
- const normalized = (value - definition.neutral) / (definition.maximum - definition.neutral);
25
- const direction = definition.direction ?? 1;
26
- return definition.base + normalized * definition.variance * direction;
27
- }
28
-
29
15
  export class VehicleData {
30
16
  constructor({
31
17
  id = null,
@@ -38,17 +24,19 @@ export class VehicleData {
38
24
  weightControl = RATING_NEUTRAL,
39
25
  tireCare = RATING_NEUTRAL,
40
26
  customFields = [],
27
+ driverModel = null,
41
28
  } = {}) {
42
29
  this.id = id;
43
30
  this.name = name;
44
- this.power = clampRating(power, 'power');
45
- this.braking = clampRating(braking, 'braking');
46
- this.aero = clampRating(aero, 'aero');
47
- this.dragEfficiency = clampRating(dragEfficiency, 'dragEfficiency');
48
- this.mechanicalGrip = clampRating(mechanicalGrip, 'mechanicalGrip');
49
- this.weightControl = clampRating(weightControl, 'weightControl');
50
- this.tireCare = clampRating(tireCare, 'tireCare');
31
+ this.power = clampRating(power, 'power', 'vehicle');
32
+ this.braking = clampRating(braking, 'braking', 'vehicle');
33
+ this.aero = clampRating(aero, 'aero', 'vehicle');
34
+ this.dragEfficiency = clampRating(dragEfficiency, 'dragEfficiency', 'vehicle');
35
+ this.mechanicalGrip = clampRating(mechanicalGrip, 'mechanicalGrip', 'vehicle');
36
+ this.weightControl = clampRating(weightControl, 'weightControl', 'vehicle');
37
+ this.tireCare = clampRating(tireCare, 'tireCare', 'vehicle');
51
38
  this.customFields = normalizeCustomFields(customFields);
39
+ this.driverModel = driverModel;
52
40
  }
53
41
 
54
42
  ratings() {
@@ -67,7 +55,7 @@ export class VehicleData {
67
55
  const ratings = this.ratings();
68
56
  const values = Object.fromEntries(Object.entries(VEHICLE_STAT_DEFINITIONS).map(([key, definition]) => [
69
57
  definition.output,
70
- applyRating(definition, ratings[key]),
58
+ applyRating(definition, ratings[key], definition.direction ?? 1),
71
59
  ]));
72
60
 
73
61
  return {
@@ -75,6 +63,7 @@ export class VehicleData {
75
63
  name: this.name,
76
64
  ratings,
77
65
  customFields: this.customFields,
66
+ driverModel: this.driverModel,
78
67
  ...values,
79
68
  };
80
69
  }
@@ -1,5 +1,6 @@
1
1
  import { clamp } from '../simulation/simMath.js';
2
2
  import { VEHICLE_LIMITS } from '../simulation/vehicle/vehiclePhysics.js';
3
+ import { normalizePitIntent } from '../simulation/pit/pitIntent.js';
3
4
 
4
5
  export function resolveActionMap(actions = {}, controlledDrivers = [], { policy = 'strict' } = {}) {
5
6
  const errors = [];
@@ -52,8 +53,8 @@ function finiteActionValue(value, message, policy, errors) {
52
53
 
53
54
  function normalizePitIntentAction(action, driverId, policy, errors) {
54
55
  if (!Object.hasOwn(action, 'pitIntent')) return null;
55
- const number = Number(action.pitIntent);
56
- if (Number.isInteger(number) && number >= 0 && number <= 2) {
56
+ const number = normalizePitIntent(action.pitIntent);
57
+ if (number != null) {
57
58
  const pitCompound = normalizePitCompoundAction(action, driverId, policy, errors);
58
59
  if (pitCompound === false) return null;
59
60
  return pitCompound == null ? number : { intent: number, targetCompound: pitCompound };
@@ -1,3 +1,5 @@
1
+ import { cancelScheduled, defaultScheduler, now } from './controllerScheduler.js';
2
+
1
3
  export function createPaddockDriverControllerLoop({
2
4
  runtime,
3
5
  controller,
@@ -28,6 +30,8 @@ export function createPaddockDriverControllerLoop({
28
30
  let previousActions = {};
29
31
  let lastDecisionMs = 0;
30
32
  let lastError = null;
33
+ let generation = 0;
34
+ let generationController = new AbortController();
31
35
 
32
36
  async function ensureSpecs() {
33
37
  if (!actionSpec) actionSpec = runtime.getActionSpec();
@@ -39,14 +43,24 @@ export function createPaddockDriverControllerLoop({
39
43
  observationSpec = runtime.getObservationSpec();
40
44
  }
41
45
 
46
+ async function runControllerHook(name, context) {
47
+ try {
48
+ return await controller[name]?.(context);
49
+ } catch (error) {
50
+ if (context.signal.aborted) return;
51
+ throw error;
52
+ }
53
+ }
54
+
42
55
  async function ensureInitialized(context) {
43
56
  if (initialized) return;
44
- await controller.init?.(context);
45
- initialized = true;
57
+ await runControllerHook('init', context);
58
+ if (!context.signal.aborted) initialized = true;
46
59
  }
47
60
 
48
61
  async function resetRuntime(options = {}, { stopPlayback = true } = {}) {
49
62
  if (stopPlayback) stop();
63
+ const resetGeneration = generation;
50
64
  result = runtime.reset(options);
51
65
  previousResult = null;
52
66
  heldActions = null;
@@ -57,9 +71,11 @@ export function createPaddockDriverControllerLoop({
57
71
  previousActions = {};
58
72
  lastError = null;
59
73
  await refreshSpecs();
74
+ if (generation !== resetGeneration) return result;
60
75
  const context = buildContext({ resetDriverIds: controlledDrivers() });
61
76
  await ensureInitialized(context);
62
- await controller.reset?.(context);
77
+ if (generation !== resetGeneration) return result;
78
+ await runControllerHook('reset', context);
63
79
  return result;
64
80
  }
65
81
 
@@ -75,6 +91,9 @@ export function createPaddockDriverControllerLoop({
75
91
  if (typeof runtime.resetDrivers !== 'function') {
76
92
  throw new Error('The supplied Paddock runtime does not support resetDrivers().');
77
93
  }
94
+ const resumePlayback = running;
95
+ stop();
96
+ const resetGeneration = generation;
78
97
  const resetDriverIds = Object.keys(placements);
79
98
  previousResult = result;
80
99
  result = runtime.resetDrivers(placements, resultOptions);
@@ -89,29 +108,39 @@ export function createPaddockDriverControllerLoop({
89
108
  delete previousActions[driverId];
90
109
  });
91
110
  await ensureSpecs();
111
+ if (generation !== resetGeneration) return result;
92
112
  const context = buildContext({ resetDriverIds });
93
113
  await ensureInitialized(context);
94
- await controller.reset?.(context);
114
+ if (generation !== resetGeneration) return result;
115
+ await runControllerHook('reset', context);
116
+ if (resumePlayback && generation === resetGeneration) start();
95
117
  return result;
96
118
  }
97
119
 
98
- async function beginDecision() {
120
+ async function beginDecision(expectedGeneration) {
99
121
  await ensureResult();
122
+ if (generation !== expectedGeneration) return false;
100
123
  const context = buildContext();
101
124
  await ensureInitialized(context);
125
+ if (generation !== expectedGeneration) return false;
102
126
  const startedAt = now();
103
- const actions = await controller.decideBatch(context);
127
+ const actions = await runControllerHook('decideBatch', context);
128
+ if (generation !== expectedGeneration) return false;
104
129
  lastDecisionMs = now() - startedAt;
105
130
  heldActions = actions && typeof actions === 'object' ? actions : {};
106
131
  heldFramesRemaining = repeat;
107
132
  policyStep += 1;
133
+ return true;
108
134
  }
109
135
 
110
- async function stepFrame() {
136
+ async function stepFrame(expectedGeneration = generation) {
111
137
  await ensureResult();
138
+ if (generation !== expectedGeneration) return result;
112
139
  if (!heldActions || heldFramesRemaining <= 0) {
113
- await beginDecision();
140
+ const decisionReady = await beginDecision(expectedGeneration);
141
+ if (!decisionReady) return result;
114
142
  }
143
+ if (generation !== expectedGeneration) return result;
115
144
  const actionIndex = repeat - heldFramesRemaining;
116
145
  const actions = heldActions;
117
146
  const previousActionsForStep = previousActions;
@@ -121,7 +150,8 @@ export function createPaddockDriverControllerLoop({
121
150
  heldFramesRemaining -= 1;
122
151
  runtimeStep = Number(result?.info?.step ?? runtimeStep + 1);
123
152
  previousActions = actions;
124
- await controller.onStep?.(buildContext({
153
+ if (generation !== expectedGeneration) return result;
154
+ await runControllerHook('onStep', buildContext({
125
155
  actionIndex,
126
156
  actions,
127
157
  previousActions: previousActionsForStep,
@@ -133,8 +163,10 @@ export function createPaddockDriverControllerLoop({
133
163
  async function step() {
134
164
  let latest = result;
135
165
  const targetPolicyStep = policyStep + 1;
166
+ const expectedGeneration = generation;
136
167
  while (policyStep < targetPolicyStep || heldFramesRemaining > 0) {
137
- latest = await stepFrame();
168
+ latest = await stepFrame(expectedGeneration);
169
+ if (generation !== expectedGeneration) break;
138
170
  if (latest?.done) break;
139
171
  if (policyStep >= targetPolicyStep && heldFramesRemaining <= 0) break;
140
172
  }
@@ -145,18 +177,21 @@ export function createPaddockDriverControllerLoop({
145
177
  if (running) return;
146
178
  lastError = null;
147
179
  running = true;
180
+ const expectedGeneration = generation;
148
181
  const schedule = scheduler ?? defaultScheduler();
149
182
  const tick = async () => {
150
- if (!running) return;
183
+ if (!running || generation !== expectedGeneration) return;
151
184
  scheduled = null;
152
185
  try {
153
- await stepFrame();
186
+ await stepFrame(expectedGeneration);
154
187
  } catch (error) {
188
+ if (generation !== expectedGeneration) return;
155
189
  lastError = error;
156
190
  running = false;
157
191
  scheduled = null;
158
192
  return;
159
193
  }
194
+ if (generation !== expectedGeneration) return;
160
195
  if (!running || result?.done) {
161
196
  running = false;
162
197
  return;
@@ -167,9 +202,17 @@ export function createPaddockDriverControllerLoop({
167
202
  }
168
203
 
169
204
  function stop() {
205
+ const previousController = generationController;
206
+ const previousSchedule = scheduled;
170
207
  running = false;
171
- cancelScheduled(scheduled);
208
+ generation += 1;
209
+ generationController = new AbortController();
172
210
  scheduled = null;
211
+ try {
212
+ cancelScheduled(previousSchedule);
213
+ } finally {
214
+ previousController.abort();
215
+ }
173
216
  }
174
217
 
175
218
  function controlledDrivers() {
@@ -184,6 +227,7 @@ export function createPaddockDriverControllerLoop({
184
227
  };
185
228
  return {
186
229
  runtime,
230
+ signal: generationController.signal,
187
231
  mode,
188
232
  actionRepeat: repeat,
189
233
  controlledDrivers: drivers,
@@ -242,34 +286,3 @@ export function createPaddockDriverControllerLoop({
242
286
  },
243
287
  };
244
288
  }
245
-
246
- function now() {
247
- return typeof performance !== 'undefined' && typeof performance.now === 'function'
248
- ? performance.now()
249
- : Date.now();
250
- }
251
-
252
- function cancelScheduled(handle) {
253
- if (handle == null) return;
254
- if (typeof handle.cancel === 'function') {
255
- handle.cancel();
256
- return;
257
- }
258
- if (typeof handle === 'number') {
259
- if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(handle);
260
- if (typeof clearTimeout === 'function') clearTimeout(handle);
261
- }
262
- }
263
-
264
- function defaultScheduler() {
265
- if (typeof requestAnimationFrame === 'function') {
266
- return (callback) => {
267
- const id = requestAnimationFrame(callback);
268
- return { cancel: () => cancelAnimationFrame(id) };
269
- };
270
- }
271
- return (callback) => {
272
- const id = setTimeout(callback, 0);
273
- return { cancel: () => clearTimeout(id) };
274
- };
275
- }
@@ -0,0 +1,30 @@
1
+ export function now() {
2
+ return typeof performance !== 'undefined' && typeof performance.now === 'function'
3
+ ? performance.now()
4
+ : Date.now();
5
+ }
6
+
7
+ export function cancelScheduled(handle) {
8
+ if (handle == null) return;
9
+ if (typeof handle.cancel === 'function') {
10
+ handle.cancel();
11
+ return;
12
+ }
13
+ if (typeof handle === 'number') {
14
+ if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(handle);
15
+ if (typeof clearTimeout === 'function') clearTimeout(handle);
16
+ }
17
+ }
18
+
19
+ export function defaultScheduler() {
20
+ if (typeof requestAnimationFrame === 'function') {
21
+ return (callback) => {
22
+ const id = requestAnimationFrame(callback);
23
+ return { cancel: () => cancelAnimationFrame(id) };
24
+ };
25
+ }
26
+ return (callback) => {
27
+ const id = setTimeout(callback, 0);
28
+ return { cancel: () => clearTimeout(id) };
29
+ };
30
+ }