@inventure71/paddockjs 4.2.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 (249) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/INSTALL_AND_UPDATE.md +23 -4
  3. package/README.md +31 -2
  4. package/docs/advanced_ai_pace.md +131 -0
  5. package/docs/advanced_physics_rebuild.md +166 -0
  6. package/docs/architecture.md +109 -30
  7. package/docs/cleanup_coverage.md +284 -0
  8. package/docs/component_inventory.md +31 -15
  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/grid_walk.md +80 -0
  16. package/docs/index.md +1 -0
  17. package/docs/keyboard_driving_fix.md +118 -0
  18. package/docs/limit_driving_2026-10-14.md +88 -0
  19. package/docs/maintainability_audit_2026-09-08.md +146 -0
  20. package/docs/racing_ai_improvements.md +145 -0
  21. package/docs/rules.md +43 -13
  22. package/docs/sense_contract.md +4 -1
  23. package/docs/system_specs.md +36 -11
  24. package/docs/testing-reliability-guide.md +5 -0
  25. package/docs/v11_improvements.md +108 -0
  26. package/package.json +23 -13
  27. package/src/api/MountedSimulatorComponents.js +101 -0
  28. package/src/api/PaddockSimulatorController.js +56 -81
  29. package/src/app/BrowserExpertAdapter.js +0 -6
  30. package/src/app/BrowserExpertExternalRenderer.js +72 -50
  31. package/src/app/ExternalTrackSnapshot.js +50 -0
  32. package/src/app/F1SimulatorApp.js +242 -297
  33. package/src/app/HostLifecycleEmitter.js +93 -0
  34. package/src/app/banners/raceDataBannerController.js +9 -4
  35. package/src/app/camera/cameraConstants.js +1 -1
  36. package/src/app/camera/cameraController.js +10 -13
  37. package/src/app/camera/cameraGeometry.js +1 -1
  38. package/src/app/controls/SimulatorViewControls.js +45 -0
  39. package/src/app/controls/workbenchBindings.js +64 -0
  40. package/src/app/controls/workbenchDisclosure.js +41 -0
  41. package/src/app/domBindings.js +21 -13
  42. package/src/app/gridWalk/GridWalkController.js +180 -0
  43. package/src/app/gridWalk/gridWalkCamera.js +88 -0
  44. package/src/app/gridWalk/gridWalkControls.js +24 -0
  45. package/src/app/gridWalk/gridWalkCrew.js +246 -0
  46. package/src/app/gridWalk/gridWalkLabels.js +104 -0
  47. package/src/app/gridWalk/gridWalkLayout.js +46 -0
  48. package/src/app/gridWalk/gridWalkNavigation.js +99 -0
  49. package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
  50. package/src/app/gridWalk/gridWalkSession.js +50 -0
  51. package/src/app/layoutSupport.js +2 -0
  52. package/src/app/raceOverlayClearanceSupport.js +21 -7
  53. package/src/app/readouts/carOverviewRenderer.js +9 -4
  54. package/src/app/readouts/readoutFormatters.js +1 -8
  55. package/src/app/readouts/telemetryRenderer.js +1 -3
  56. package/src/app/readouts/timingTowerRenderer.js +48 -50
  57. package/src/app/rendering/appAssets.js +8 -10
  58. package/src/app/rendering/carRenderer.js +27 -79
  59. package/src/app/rendering/displayUtils.js +0 -6
  60. package/src/app/rendering/expertSensorRenderer.js +1 -1
  61. package/src/app/rendering/replayGhostRenderer.js +4 -1
  62. package/src/app/rendering/trackRenderer.js +2 -2
  63. package/src/app/runtime/frameLoop.js +18 -17
  64. package/src/app/runtime/pixiStartupLifecycle.js +94 -0
  65. package/src/app/runtime/runtimeTiming.js +4 -1
  66. package/src/app/runtime/runtimeVisibility.js +7 -2
  67. package/src/config/cssValues.js +1 -1
  68. package/src/config/defaultOptions.js +12 -173
  69. package/src/config/restartOptions.js +1 -0
  70. package/src/config/runtimeSeed.js +10 -0
  71. package/src/config/simulatorDefaults.js +106 -0
  72. package/src/config/telemetryModules.js +20 -0
  73. package/src/config/theme/cssTokens.js +85 -0
  74. package/src/config/theme/themeCss.js +72 -0
  75. package/src/config/theme/themeInputs.js +155 -0
  76. package/src/config/theme/themeResolver.js +172 -0
  77. package/src/config/theme/themeSelectors.js +69 -0
  78. package/src/config/theme/themeTokens.js +257 -0
  79. package/src/config/theme/themeUtils.js +15 -0
  80. package/src/config/themeOptions.js +14 -830
  81. package/src/config/timingGapMode.js +0 -2
  82. package/src/config/uiOptions.js +53 -0
  83. package/src/data/championship.js +16 -158
  84. package/src/data/championshipDefaults.js +87 -0
  85. package/src/data/driverData.js +10 -20
  86. package/src/data/entryIdentity.js +67 -0
  87. package/src/data/entryPresentation.js +23 -0
  88. package/src/data/entryTypes.d.ts +160 -0
  89. package/src/data/index.d.ts +13 -259
  90. package/src/data/ratingScale.js +14 -0
  91. package/src/data/teamTypes.d.ts +23 -0
  92. package/src/data/trackTypes.d.ts +101 -0
  93. package/src/data/vehicleData.js +12 -23
  94. package/src/environment/actions.js +3 -2
  95. package/src/environment/controllerLoop.js +57 -44
  96. package/src/environment/controllerScheduler.js +30 -0
  97. package/src/environment/driverObservations.js +260 -0
  98. package/src/environment/driverReset.js +51 -0
  99. package/src/environment/episode.js +0 -1
  100. package/src/environment/evaluation.js +6 -90
  101. package/src/environment/evaluationTracker.js +100 -0
  102. package/src/environment/events.js +23 -0
  103. package/src/environment/index.d.ts +21 -140
  104. package/src/environment/metrics.js +3 -17
  105. package/src/environment/observationSchema.js +104 -0
  106. package/src/environment/observationVector.js +16 -115
  107. package/src/environment/observations.js +16 -316
  108. package/src/environment/options.js +5 -24
  109. package/src/environment/renderFrames.js +21 -0
  110. package/src/environment/resetOptions.js +41 -0
  111. package/src/environment/results.js +125 -0
  112. package/src/environment/rewards.js +10 -16
  113. package/src/environment/runtime.js +17 -300
  114. package/src/environment/scenarioOptions.js +90 -0
  115. package/src/environment/scenarioPlacements.js +70 -0
  116. package/src/environment/scenarioPresets.js +78 -0
  117. package/src/environment/scenarios.js +5 -236
  118. package/src/environment/scratch.js +18 -0
  119. package/src/environment/sensorOptions.js +20 -0
  120. package/src/environment/sensors/index.js +2 -556
  121. package/src/environment/sensors/indexedRayBands.js +0 -18
  122. package/src/environment/sensors/nearbyCars.js +18 -15
  123. package/src/environment/sensors/opponentRadar.js +3 -2
  124. package/src/environment/sensors/pitConnectorProximity.js +20 -0
  125. package/src/environment/sensors/rayBandTrace.js +1 -37
  126. package/src/environment/sensors/rayBatch.js +81 -0
  127. package/src/environment/sensors/rayConfig.js +26 -0
  128. package/src/environment/sensors/rayGeometry.js +0 -8
  129. package/src/environment/sensors/rayGuards.js +0 -6
  130. package/src/environment/sensors/rayOutputs.js +75 -0
  131. package/src/environment/sensors/rayQuery.js +110 -0
  132. package/src/environment/sensors/rayScratch.js +146 -0
  133. package/src/environment/sensors/sensorTargets.js +12 -4
  134. package/src/environment/sensors/surfaceRays.js +6 -61
  135. package/src/environment/sensors/trackRays.js +7 -80
  136. package/src/environment/simulationSetup.js +30 -0
  137. package/src/environment/specs.js +15 -109
  138. package/src/index.d.ts +34 -259
  139. package/src/index.js +28 -6
  140. package/src/placeholder/index.js +1 -8
  141. package/src/rendering/displayLifecycle.js +6 -0
  142. package/src/rendering/proceduralTrackAsset.js +1 -1
  143. package/src/rendering/renderSnapshot.js +3 -0
  144. package/src/rendering/track/finishLineRenderer.js +1 -1
  145. package/src/rendering/track/gridRenderer.js +1 -1
  146. package/src/rendering/track/offsetStrokeSafety.js +2 -2
  147. package/src/rendering/track/pitLaneRenderer.js +1 -1
  148. package/src/rendering/track/trackMaterialRenderer.js +4 -4
  149. package/src/rendering/track/trackRenderConstants.js +0 -1
  150. package/src/rendering/track/trackRenderGeometry.js +1 -7
  151. package/src/simulation/collisionGeometry.js +5 -11
  152. package/src/simulation/driver/advancedPathControls.js +53 -0
  153. package/src/simulation/driver/advancedRacePace.js +112 -0
  154. package/src/simulation/driver/advancedRacingControls.js +61 -0
  155. package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
  156. package/src/simulation/driver/advancedRacingSteering.js +76 -0
  157. package/src/simulation/driver/advancedRacingTiming.js +16 -0
  158. package/src/simulation/driver/arcadeRacePace.js +83 -0
  159. package/src/simulation/driver/arcadeRacingControls.js +111 -0
  160. package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
  161. package/src/simulation/driver/driverControlConstants.js +0 -2
  162. package/src/simulation/driver/driverController.js +14 -3
  163. package/src/simulation/driver/edgeRecovery.js +0 -1
  164. package/src/simulation/driver/racingControls.js +10 -406
  165. package/src/simulation/driver/racingDriverProfile.js +48 -0
  166. package/src/simulation/driver/racingLinePlan.js +48 -15
  167. package/src/simulation/driver/rejoinControls.js +58 -111
  168. package/src/simulation/driver/safetyCarControls.js +12 -0
  169. package/src/simulation/driver/trafficScan.js +89 -6
  170. package/src/simulation/pit/pitCarPlacement.js +29 -0
  171. package/src/simulation/pit/pitFlow.js +8 -30
  172. package/src/simulation/pit/pitIntent.js +4 -3
  173. package/src/simulation/pit/pitOccupancy.js +2 -2
  174. package/src/simulation/pit/pitQueue.js +2 -18
  175. package/src/simulation/pit/pitRouteMovement.js +1 -36
  176. package/src/simulation/pit/pitRouting.js +5 -81
  177. package/src/simulation/pit/pitService.js +3 -32
  178. package/src/simulation/pit/pitServiceConstants.js +1 -0
  179. package/src/simulation/pit/pitServiceProgress.js +34 -0
  180. package/src/simulation/pit/pitSnapshots.js +8 -4
  181. package/src/simulation/pit/pitState.js +1 -5
  182. package/src/simulation/race/classification.js +1 -8
  183. package/src/simulation/race/raceFinish.js +3 -8
  184. package/src/simulation/race/raceOrder.js +2 -13
  185. package/src/simulation/race/raceProgress.js +6 -20
  186. package/src/simulation/race/raceSetup.js +1 -1
  187. package/src/simulation/race/raceStep.js +1 -1
  188. package/src/simulation/race/redFlag.js +2 -4
  189. package/src/simulation/race/safetyCar.js +3 -8
  190. package/src/simulation/race/stalledDnf.js +2 -4
  191. package/src/simulation/raceSimulation.js +6 -38
  192. package/src/simulation/replay/replayGhosts.js +44 -32
  193. package/src/simulation/rules/collisionSteward.js +0 -14
  194. package/src/simulation/rules/penaltyActions.js +37 -0
  195. package/src/simulation/rules/penaltyLedger.js +8 -1
  196. package/src/simulation/rules/rulesReview.js +1 -7
  197. package/src/simulation/simMath.js +4 -0
  198. package/src/simulation/snapshots/raceSnapshots.js +30 -11
  199. package/src/simulation/timing/drsState.js +8 -0
  200. package/src/simulation/timing/drsTiming.js +2 -4
  201. package/src/simulation/timing/lapTelemetry.js +7 -4
  202. package/src/simulation/timing/sectorPerformance.js +3 -3
  203. package/src/simulation/timing/timingLines.js +12 -6
  204. package/src/simulation/track/pitLaneAccess.js +2 -2
  205. package/src/simulation/track/pitLaneLayout.js +4 -135
  206. package/src/simulation/track/pitLaneModel.js +70 -0
  207. package/src/simulation/track/pitLaneState.js +0 -1
  208. package/src/simulation/track/pitServiceLayout.js +121 -0
  209. package/src/simulation/track/proceduralTrack.js +77 -0
  210. package/src/simulation/track/spatialQueries.js +1 -25
  211. package/src/simulation/track/trackBuilder.js +54 -0
  212. package/src/simulation/track/trackConstants.js +0 -22
  213. package/src/simulation/track/trackMath.js +4 -29
  214. package/src/simulation/track/trackModel.js +4 -241
  215. package/src/simulation/track/trackModelCache.js +105 -0
  216. package/src/simulation/track/trackNearestQueries.js +1218 -0
  217. package/src/simulation/track/trackQueryConstants.js +3 -0
  218. package/src/simulation/track/trackQueryConstruction.js +368 -0
  219. package/src/simulation/track/trackQueryIndex.js +12 -1933
  220. package/src/simulation/track/trackQuerySegments.js +174 -0
  221. package/src/simulation/track/trackQueryStats.js +12 -0
  222. package/src/simulation/track/trackRayQueries.js +165 -0
  223. package/src/simulation/vehicle/advancedContactResponse.js +89 -0
  224. package/src/simulation/vehicle/advancedTireForces.js +92 -0
  225. package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
  226. package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
  227. package/src/simulation/vehicle/contactResolution.js +20 -6
  228. package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
  229. package/src/simulation/vehicle/pitWheelSurface.js +0 -12
  230. package/src/simulation/vehicle/runoffResponse.js +2 -4
  231. package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
  232. package/src/simulation/vehicle/vehicleGeometry.js +0 -56
  233. package/src/simulation/vehicle/vehicleKinematics.js +10 -0
  234. package/src/simulation/vehicle/vehiclePhysics.js +35 -194
  235. package/src/simulation/vehicle/vehicleSnapshots.js +13 -4
  236. package/src/simulation/vehicle/vehicleState.js +2 -1
  237. package/src/simulation/vehicle/wheelSurface.js +13 -432
  238. package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
  239. package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
  240. package/src/styles.css +173 -19
  241. package/src/ui/bannerTemplates.js +4 -2
  242. package/src/ui/cameraControlsTemplate.js +4 -2
  243. package/src/ui/gridWalkTemplate.js +29 -0
  244. package/src/ui/htmlEscaping.js +8 -0
  245. package/src/ui/shellTemplate.js +3 -0
  246. package/src/ui/telemetryDrawerTemplate.js +4 -2
  247. package/src/ui/telemetryTemplates.js +3 -22
  248. package/src/ui/templateUtils.js +2 -8
  249. package/src/ui/viewControlsTemplate.js +21 -0
package/src/index.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ import type { PaddockProceduralTrackProfile, PaddockProceduralTrackOptions, PaddockTrackPoint, PaddockDrsZone, PaddockResolvedProceduralTrackOptions, PaddockProceduralTrack } from './data/trackTypes.js';
2
+ import type { TireCompound, CustomField, CustomFieldInput, EntryPresentation, SimulatorDriver, DriverRatings, VehicleRatings, DriverBlueprint, VehicleBlueprint, DriverConstructorArgs, VehicleConstructorArgs, DriverData, VehicleData, ChampionshipEntryBlueprint, NormalizedSimulatorDriver } from './data/entryTypes.js';
3
+ export type { PaddockProceduralTrackProfile, PaddockProceduralTrackOptions, PaddockTrackPoint, PaddockDrsZone, PaddockResolvedProceduralTrackOptions, PaddockProceduralTrack } from './data/trackTypes.js';
4
+ export type { TireCompound, CustomField, CustomFieldInput, EntryPresentation, SimulatorDriver, DriverRatings, VehicleRatings, DriverBlueprint, VehicleBlueprint, DriverConstructorArgs, VehicleConstructorArgs, ChampionshipEntryBlueprint, NormalizedSimulatorDriver } from './data/entryTypes.js';
5
+ export { DriverData, VehicleData } from './data/entryTypes.js';
6
+
1
7
  import type {
2
8
  PaddockActionSpec,
3
9
  PaddockDriverControllerLoop,
@@ -13,6 +19,11 @@ import type {
13
19
  PaddockReplayGhostSnapshot as EnvPaddockReplayGhostSnapshot,
14
20
  PaddockReplayGhostTrajectorySample as EnvPaddockReplayGhostTrajectorySample,
15
21
  } from './environment/index.js';
22
+ import type {
23
+ PaddockPitCrewStats,
24
+ PaddockThemeSelector,
25
+ TeamData,
26
+ } from './data/teamTypes.js';
16
27
 
17
28
  export type {
18
29
  PaddockActionSpec,
@@ -25,11 +36,14 @@ export type {
25
36
  PaddockEnvironmentSnapshotResult,
26
37
  PaddockObservationSpec,
27
38
  } from './environment/index.js';
39
+ export type {
40
+ PaddockPitCrewStats,
41
+ PaddockThemeSelector,
42
+ TeamData,
43
+ } from './data/teamTypes.js';
28
44
 
29
- export type TireCompound = 'S' | 'M' | 'H';
30
45
  export type PaddockPhysicsMode = 'arcade' | 'advanced';
31
46
  export type PaddockWarmupPolicy = 'config-change' | 'always' | 'never';
32
- export type PaddockProceduralTrackProfile = 'race' | 'training-short' | 'training-medium' | 'training-technical';
33
47
 
34
48
  export interface PaddockWarmupOptions {
35
49
  enabled?: boolean;
@@ -37,106 +51,6 @@ export interface PaddockWarmupOptions {
37
51
  steps?: number;
38
52
  }
39
53
 
40
- export interface PaddockProceduralTrackOptions {
41
- profile?: PaddockProceduralTrackProfile;
42
- minLengthMeters?: number;
43
- maxLengthMeters?: number;
44
- startStraightMeters?: number;
45
- includePitLane?: boolean;
46
- length?: {
47
- minMeters?: number;
48
- maxMeters?: number;
49
- };
50
- startStraight?: {
51
- gridMeters?: number;
52
- exitMeters?: number;
53
- blendMeters?: number;
54
- lockExtraMeters?: number;
55
- };
56
- pitLane?: {
57
- enabled?: boolean;
58
- };
59
- shape?: {
60
- scale?: number;
61
- cornerDensity?: number;
62
- variation?: number;
63
- };
64
- validation?: {
65
- minClearanceMultiplier?: number;
66
- minShapeVariation?: number;
67
- minNonAdjacentArcMeters?: number;
68
- maxLocalTurnRadians?: number;
69
- maxSampleHeadingDeltaRadians?: number;
70
- };
71
- attempts?: {
72
- primary?: number;
73
- fallback?: number;
74
- };
75
- }
76
-
77
- export interface PaddockTrackPoint {
78
- x: number;
79
- y: number;
80
- }
81
-
82
- export interface PaddockDrsZone {
83
- id: string;
84
- startRatio: number;
85
- endRatio: number;
86
- }
87
-
88
- export interface PaddockResolvedProceduralTrackOptions {
89
- profile: PaddockProceduralTrackProfile;
90
- length: {
91
- min: number;
92
- max: number;
93
- };
94
- startStraight: {
95
- grid: number;
96
- exit: number;
97
- blend: number;
98
- lockExtra: number;
99
- };
100
- pitLane: {
101
- enabled: boolean;
102
- };
103
- shape: {
104
- scale: number;
105
- cornerDensity: number;
106
- variation: number;
107
- };
108
- validation: {
109
- minClearanceMultiplier: number;
110
- minShapeVariation: number;
111
- minNonAdjacentArcDistance: number;
112
- maxLocalTurnRadians: number;
113
- maxSampleHeadingDeltaRadians: number;
114
- };
115
- attempts: {
116
- primary: number;
117
- fallback: number;
118
- };
119
- cacheKey: string;
120
- [key: string]: unknown;
121
- }
122
-
123
- export interface PaddockProceduralTrack {
124
- name: string;
125
- width: number;
126
- kerbWidth: number;
127
- gravelWidth: number;
128
- runoffWidth: number;
129
- barrierWidth: number;
130
- sampleCount: number;
131
- seed?: number;
132
- curveInterpolation?: string;
133
- centerlineControls?: readonly PaddockTrackPoint[];
134
- drsZones?: readonly PaddockDrsZone[] | null;
135
- pitLane?: { enabled?: boolean } | null;
136
- generationOptions?: PaddockResolvedProceduralTrackOptions;
137
- [key: string]: unknown;
138
- }
139
-
140
54
  export type PaddockStabilityState = 'stable' | 'understeer' | 'oversteer' | 'spin-risk' | 'destroyed';
141
55
  export type PaddockPitIntent = 0 | 1 | 2;
142
56
  export type PaddockPitIntentRequest = PaddockPitIntent | {
@@ -156,6 +70,7 @@ export type TimingTowerVerticalFit = 'expand-race-view' | 'scroll';
156
70
  export type TimingGapMode = 'interval' | 'leader';
157
71
  export type LayoutPreset = 'standard' | 'left-tower-overlay';
158
72
  export type CameraControlsMode = 'embedded' | 'external' | false;
73
+ export type ViewControlsMode = 'switch' | 'toggle';
159
74
  export type PaddockPresetName = 'dashboard' | 'timing-overlay' | 'compact-race' | 'full-dashboard';
160
75
  export type TelemetryModuleName = 'core' | 'sectors' | 'lapTimes' | 'sectorTimes';
161
76
  export type SectorPerformanceStatus = 'overall-best' | 'personal-best' | 'slower';
@@ -167,164 +82,7 @@ export type PaddockReplayGhostTrajectorySample = EnvPaddockReplayGhostTrajectory
167
82
  export type PaddockReplayGhostOptions = EnvPaddockReplayGhostOptions;
168
83
  export type PaddockReplayGhostSnapshot = EnvPaddockReplayGhostSnapshot;
169
84
 
170
- export interface CustomField {
171
- label: string;
172
- value: string;
173
- }
174
-
175
- export type CustomFieldInput = CustomField[] | Record<string, string>;
176
-
177
- export interface TeamData {
178
- id?: string;
179
- name?: string;
180
- color?: string;
181
- icon?: string;
182
- theme?: PaddockThemeSelector;
183
- pitCrew?: PaddockPitCrewStats;
184
- pitCrewStats?: PaddockPitCrewStats;
185
- }
186
-
187
- export interface PaddockPitCrewStats {
188
- speed?: number;
189
- consistency?: number;
190
- reliability?: number;
191
- }
192
-
193
- export interface SimulatorDriver {
194
- id: string;
195
- name: string;
196
- color: string;
197
- link?: string;
198
- icon?: string;
199
- code?: string;
200
- timingName?: string;
201
- tire?: TireCompound;
202
- raceData?: string[];
203
- customFields?: CustomFieldInput;
204
- team?: TeamData | null;
205
- driverNumber?: number;
206
- }
207
-
208
- export interface DriverRatings {
209
- pace: number;
210
- racecraft: number;
211
- aggression: number;
212
- riskTolerance: number;
213
- patience: number;
214
- consistency: number;
215
- }
216
-
217
- export interface VehicleRatings {
218
- power: number;
219
- braking: number;
220
- aero: number;
221
- dragEfficiency: number;
222
- mechanicalGrip: number;
223
- weightControl: number;
224
- tireCare: number;
225
- }
226
-
227
- export interface DriverBlueprint extends Partial<DriverRatings> {
228
- customFields?: CustomFieldInput;
229
- }
230
-
231
- export interface VehicleBlueprint extends Partial<VehicleRatings> {
232
- id?: string | null;
233
- name?: string | null;
234
- customFields?: CustomFieldInput;
235
- }
236
-
237
- export interface DriverConstructorArgs {
238
- ratings: DriverRatings;
239
- customFields: CustomField[];
240
- pace: number;
241
- racecraft: number;
242
- consistency: number;
243
- personality: {
244
- aggression: number;
245
- riskTolerance: number;
246
- patience: number;
247
- };
248
- }
249
-
250
- export interface VehicleConstructorArgs {
251
- id: string | null;
252
- name: string | null;
253
- ratings: VehicleRatings;
254
- customFields: CustomField[];
255
- powerNewtons: number;
256
- brakeNewtons: number;
257
- downforceCoefficient: number;
258
- dragCoefficient: number;
259
- tireGrip: number;
260
- mass: number;
261
- tireCare: number;
262
- }
263
-
264
- export class DriverData {
265
- constructor(input?: DriverBlueprint);
266
- readonly pace: number;
267
- readonly racecraft: number;
268
- readonly aggression: number;
269
- readonly riskTolerance: number;
270
- readonly patience: number;
271
- readonly consistency: number;
272
- readonly customFields: CustomField[];
273
- ratings(): DriverRatings;
274
- toConstructorArgs(): DriverConstructorArgs;
275
- }
276
-
277
- export class VehicleData {
278
- constructor(input?: VehicleBlueprint);
279
- readonly id: string | null;
280
- readonly name: string | null;
281
- readonly power: number;
282
- readonly braking: number;
283
- readonly aero: number;
284
- readonly dragEfficiency: number;
285
- readonly mechanicalGrip: number;
286
- readonly weightControl: number;
287
- readonly tireCare: number;
288
- readonly customFields: CustomField[];
289
- ratings(): VehicleRatings;
290
- toConstructorArgs(): VehicleConstructorArgs;
291
- }
292
-
293
- export interface ChampionshipEntryBlueprint {
294
- driverId: string;
295
- driverNumber?: number;
296
- timingName?: string;
297
- driver?: DriverData | DriverBlueprint;
298
- vehicle?: VehicleData | VehicleBlueprint;
299
- team?: TeamData;
300
- }
301
-
302
- export interface NormalizedSimulatorDriver extends SimulatorDriver {
303
- code: string;
304
- timingCode: string;
305
- raceName: string;
306
- driverNumber: number;
307
- team: TeamData | null;
308
- pace: number;
309
- racecraft: number;
310
- consistency: number;
311
- personality: DriverConstructorArgs['personality'];
312
- vehicle: VehicleConstructorArgs;
313
- constructorArgs: {
314
- driver: DriverConstructorArgs;
315
- vehicle: VehicleConstructorArgs;
316
- };
317
- championship: {
318
- id: string;
319
- name: string;
320
- season: number;
321
- entryIndex: number;
322
- vehicleId: string | null;
323
- };
324
- }
325
-
326
85
  export type PaddockThemeMode = 'dark' | 'light' | 'system';
327
- export type PaddockThemeSelector = 'default' | 'active' | 'selectedTeam' | 'team' | `team:${string}` | string;
328
86
  export type PaddockThemeTokenValue = string | { light?: string; dark?: string };
329
87
  export type PaddockThemeTokenName =
330
88
  | 'primary'
@@ -432,6 +190,8 @@ export interface PaddockThemeSyncOptions {
432
190
  export interface F1SimulatorUiOptions {
433
191
  layoutPreset?: LayoutPreset;
434
192
  cameraControls?: CameraControlsMode;
193
+ /** Two mode buttons by default; toggle uses one Start race / Stop race action. */
194
+ viewControls?: ViewControlsMode;
435
195
  showFps?: boolean;
436
196
  showTimingTower?: boolean;
437
197
  showTelemetry?: boolean;
@@ -1041,7 +801,16 @@ export interface F1SimulatorExpertApi {
1041
801
  destroy(): void;
1042
802
  }
1043
803
 
804
+ export type PaddockView = 'grid-walk' | 'race';
805
+
806
+ export interface GridWalkOptions {
807
+ enabled?: boolean;
808
+ initialView?: PaddockView;
809
+ ambientMotion?: boolean;
810
+ }
811
+
1044
812
  export interface F1SimulatorOptions extends F1SimulatorCallbacks {
813
+ gridWalk?: GridWalkOptions;
1045
814
  preset?: PaddockPresetName;
1046
815
  drivers: SimulatorDriver[];
1047
816
  entries?: ChampionshipEntryBlueprint[];
@@ -1097,6 +866,8 @@ export interface F1MountedSimulator {
1097
866
  getTheme(): ResolvedPaddockTheme;
1098
867
  syncThemeFrom(source: Element, options?: PaddockThemeSyncOptions): () => void;
1099
868
  selectDriver(driverId: string): void;
869
+ setView(view: PaddockView): boolean;
870
+ getView(): PaddockView;
1100
871
  setSafetyCarDeployed(deployed: boolean): void;
1101
872
  setRedFlagDeployed(deployed: boolean): void;
1102
873
  setPitLaneOpen(open: boolean): void;
@@ -1119,6 +890,7 @@ export interface PaddockSimulatorController {
1119
890
  readonly expert: F1SimulatorExpertApi | null;
1120
891
  mountRaceControls<T extends Element>(root: T): T;
1121
892
  mountCameraControls<T extends Element>(root: T): T;
893
+ mountViewControls<T extends Element>(root: T): T;
1122
894
  mountSafetyCarControl<T extends Element>(root: T): T;
1123
895
  mountTimingTower<T extends Element>(root: T): T;
1124
896
  mountRaceCanvas<T extends Element>(root: T, options?: MountRaceCanvasOptions): T;
@@ -1141,6 +913,8 @@ export interface PaddockSimulatorController {
1141
913
  getTheme(): ResolvedPaddockTheme;
1142
914
  syncThemeFrom(source: Element, options?: PaddockThemeSyncOptions): () => void;
1143
915
  selectDriver(driverId: string): void;
916
+ setView(view: PaddockView): boolean;
917
+ getView(): PaddockView;
1144
918
  setSafetyCarDeployed(deployed: boolean): void;
1145
919
  setRedFlagDeployed(deployed: boolean): void;
1146
920
  setPitLaneOpen(open: boolean): void;
@@ -1204,6 +978,7 @@ export function createPaddockDriverControllerLoop(options: PaddockDriverControll
1204
978
 
1205
979
  export function mountRaceControls<T extends Element>(root: T, simulator: PaddockSimulatorController): T;
1206
980
  export function mountCameraControls<T extends Element>(root: T, simulator: PaddockSimulatorController): T;
981
+ export function mountViewControls<T extends Element>(root: T, simulator: PaddockSimulatorController): T;
1207
982
  export function mountSafetyCarControl<T extends Element>(root: T, simulator: PaddockSimulatorController): T;
1208
983
  export function mountTimingTower<T extends Element>(root: T, simulator: PaddockSimulatorController): T;
1209
984
  export function mountRaceCanvas<T extends Element>(
package/src/index.js CHANGED
@@ -8,6 +8,7 @@ export {
8
8
  createPaddockSimulator,
9
9
  mountCarDriverOverview,
10
10
  mountCameraControls,
11
+ mountViewControls,
11
12
  mountRaceCanvas,
12
13
  mountRaceControls,
13
14
  mountRaceDataPanel,
@@ -60,14 +61,30 @@ function assertMountRoot(root) {
60
61
  }
61
62
  }
62
63
 
64
+ const activeSimulatorRoots = new WeakMap();
65
+
63
66
  export async function mountF1Simulator(root, options = {}) {
64
67
  assertMountRoot(root);
68
+ if (activeSimulatorRoots.has(root)) {
69
+ throw new Error('mountF1Simulator root already has an active simulator. Destroy it before mounting again.');
70
+ }
65
71
 
66
- let resolvedOptions = resolveF1SimulatorOptions(options);
67
- root.innerHTML = createF1SimulatorShell(resolvedOptions);
68
- const shell = root.querySelector('[data-f1-simulator-shell]');
69
- const app = new F1SimulatorApp(shell, resolvedOptions);
70
- await app.init();
72
+ const mountToken = {};
73
+ activeSimulatorRoots.set(root, mountToken);
74
+ let app = null;
75
+ let resolvedOptions;
76
+ try {
77
+ resolvedOptions = resolveF1SimulatorOptions(options);
78
+ root.innerHTML = createF1SimulatorShell(resolvedOptions);
79
+ const shell = root.querySelector('[data-f1-simulator-shell]');
80
+ app = new F1SimulatorApp(shell, resolvedOptions);
81
+ await app.init();
82
+ } catch (error) {
83
+ app?.destroy();
84
+ if (activeSimulatorRoots.get(root) === mountToken) activeSimulatorRoots.delete(root);
85
+ root.innerHTML = '';
86
+ throw error;
87
+ }
71
88
 
72
89
  const syncResolvedTimingGapMode = (timingGapMode) => {
73
90
  resolvedOptions = {
@@ -90,7 +107,10 @@ export async function mountF1Simulator(root, options = {}) {
90
107
  },
91
108
  destroy() {
92
109
  app.destroy();
93
- root.innerHTML = '';
110
+ if (activeSimulatorRoots.get(root) === mountToken) {
111
+ activeSimulatorRoots.delete(root);
112
+ root.innerHTML = '';
113
+ }
94
114
  },
95
115
  restart(nextOptions = {}) {
96
116
  const nextResolvedOptions = resolveF1SimulatorOptions(mergeRestartOptions(syncResolvedRuntimeOptions(), nextOptions));
@@ -116,6 +136,8 @@ export async function mountF1Simulator(root, options = {}) {
116
136
  selectDriver(driverId) {
117
137
  app.selectCar(driverId, { focus: true });
118
138
  },
139
+ setView(view) { return app.setView(view); },
140
+ getView() { return app.getView(); },
119
141
  setSafetyCarDeployed(deployed) {
120
142
  app.setSafetyCarDeployed(deployed);
121
143
  },
@@ -1,11 +1,4 @@
1
- function escapeHtml(value) {
2
- return String(value ?? '')
3
- .replaceAll('&', '&amp;')
4
- .replaceAll('<', '&lt;')
5
- .replaceAll('>', '&gt;')
6
- .replaceAll('"', '&quot;')
7
- .replaceAll("'", '&#39;');
8
- }
1
+ import { escapeHtml } from '../ui/htmlEscaping.js';
9
2
 
10
3
  function normalizeVariant(value) {
11
4
  return value === 'custom' ? 'custom' : 'lights';
@@ -0,0 +1,6 @@
1
+ // Display objects own their children, while simulator textures remain shared.
2
+ export function destroyDisplayChildren(container) {
3
+ container?.removeChildren?.().forEach((child) => {
4
+ child.destroy?.({ children: true, texture: false, textureSource: false });
5
+ });
6
+ }
@@ -1,6 +1,6 @@
1
1
  import { Container } from 'pixi.js';
2
2
  import { WORLD } from '../simulation/track/trackModel.js';
3
- import { destroyDisplayChildren } from './track/trackRenderGeometry.js';
3
+ import { destroyDisplayChildren } from './displayLifecycle.js';
4
4
  import { addAsphalt, addBarriers, addBorders, addBoundaryUnderlay, addGrass, addGravelRunoff, addKerbs } from './track/trackMaterialRenderer.js';
5
5
  import { addPitLane, addPitLaneRunoff } from './track/pitLaneRenderer.js';
6
6
  import { addStartingGrid } from './track/gridRenderer.js';
@@ -11,6 +11,9 @@ function interpolateAngle(previous, current, amount) {
11
11
 
12
12
  function interpolateMovingEntityInto(target, entity, alpha) {
13
13
  Object.assign(target, entity);
14
+ // Retirement stops pose integration, so its previous endpoint may be from
15
+ // the last moving step. A stationary wreck must not replay that movement.
16
+ if (entity.destroyed || entity.outOfRace) return target;
14
17
  target.x = lerp(entity.previousX ?? entity.x, entity.x, alpha);
15
18
  target.y = lerp(entity.previousY ?? entity.y, entity.y, alpha);
16
19
  target.heading = interpolateAngle(entity.previousHeading ?? entity.heading, entity.heading, alpha);
@@ -1,5 +1,5 @@
1
1
  import { Graphics } from 'pixi.js';
2
- import { offsetTrackPoint, pointAt } from '../../simulation/trackModel.js';
2
+ import { offsetTrackPoint, pointAt } from '../../simulation/track/trackModel.js';
3
3
  import { FINISH_LINE_COLUMNS, FINISH_LINE_DEPTH } from './trackRenderConstants.js';
4
4
 
5
5
  export function addFinishLine(asset, track) {
@@ -1,5 +1,5 @@
1
1
  import { Graphics } from 'pixi.js';
2
- import { offsetTrackPoint, pointAt } from '../../simulation/trackModel.js';
2
+ import { offsetTrackPoint, pointAt } from '../../simulation/track/trackModel.js';
3
3
  import { START_GRID_BOX_LENGTH, START_GRID_BOX_WIDTH, START_GRID_FIRST_DISTANCE, START_GRID_LATERAL_OFFSET, START_GRID_SLOT_COUNT, START_GRID_SLOT_SPACING } from './trackRenderConstants.js';
4
4
 
5
5
  export function addStartingGrid(asset, track) {
@@ -1,7 +1,7 @@
1
- import { nearestTrackState, offsetTrackPoint } from '../../simulation/trackModel.js';
1
+ import { nearestTrackState, offsetTrackPoint } from '../../simulation/track/trackModel.js';
2
2
  import { queryNearbyTrackProjections } from '../../simulation/track/trackQueryIndex.js';
3
3
  import { metersToSimUnits } from '../../simulation/units.js';
4
- import { NON_LOCAL_SAMPLE_STEP, OFFSET_GAP_SAMPLE_COUNT, OFFSET_SEGMENT_SAMPLE_COUNT } from './trackRenderConstants.js';
4
+ import { NON_LOCAL_SAMPLE_STEP, OFFSET_GAP_SAMPLE_COUNT, OFFSET_SEGMENT_SAMPLE_COUNT, SEGMENTED_STROKE_STEP } from './trackRenderConstants.js';
5
5
  import { arcDistance, interpolatedSegmentPoint, pointDistance } from './trackRenderGeometry.js';
6
6
 
7
7
  export function offsetPointIsLocal(track, source, point, offset) {
@@ -1,5 +1,5 @@
1
1
  import { Graphics } from 'pixi.js';
2
- import { nearestTrackState } from '../../simulation/trackModel.js';
2
+ import { nearestTrackState } from '../../simulation/track/trackModel.js';
3
3
  import { ASPHALT_COLOR, GRAVEL_COLOR, PIT_ASPHALT_COLOR, PIT_BOX_COLOR, PIT_CONNECTOR_WIDTH, PIT_LINE_COLOR, PIT_SPEED_LINE_COLOR } from './trackRenderConstants.js';
4
4
  import { colorToNumber, createTeamPitGroupCorners, drawPolyline, offsetVectorPoint } from './trackRenderGeometry.js';
5
5
 
@@ -1,5 +1,5 @@
1
1
  import { Graphics, TilingSprite } from 'pixi.js';
2
- import { offsetTrackPoint } from '../../simulation/trackModel.js';
2
+ import { offsetTrackPoint } from '../../simulation/track/trackModel.js';
3
3
  import { ASPHALT_COLOR, BARRIER_WALL_WIDTH, EDGE_REVEAL_OFFSET, EDGE_REVEAL_WIDTH, GRASS_COLOR, GRAVEL_COLOR, KERB_CURVATURE_THRESHOLD, KERB_OFFSET, KERB_STEP, KERB_WIDTH, MATERIAL_TILE_SCALE, OUTER_BOUNDARY_OFFSET, OUTER_BOUNDARY_WIDTH, RUNOFF_GRASS_COLOR, SEGMENTED_STROKE_STEP, WORLD_BACKGROUND_PADDING_MULTIPLIER } from './trackRenderConstants.js';
4
4
  import { makeTrackPath, textureOrWhite } from './trackRenderGeometry.js';
5
5
  import { getOffsetGapBridges, getOffsetStrokeSegments, offsetSegmentIsSafe } from './offsetStrokeSafety.js';
@@ -149,7 +149,7 @@ export function addBoundaryUnderlay(asset, track) {
149
149
  });
150
150
  });
151
151
 
152
- drawKerbStripes(asset, boundary, track, { clipUnsafeSegments: false });
152
+ drawKerbStripes(boundary, track, { clipUnsafeSegments: false });
153
153
 
154
154
  [-1, 1].forEach((side) => {
155
155
  drawContinuousOffsetStroke(boundary, track, {
@@ -214,7 +214,7 @@ export function getTrackMaterialBands(track) {
214
214
  };
215
215
  }
216
216
 
217
- export function drawKerbStripes(asset, kerbs, track, { clipUnsafeSegments }) {
217
+ export function drawKerbStripes(kerbs, track, { clipUnsafeSegments }) {
218
218
  const samples = track.samples.slice(0, -1);
219
219
 
220
220
  for (let index = 0; index < samples.length; index += KERB_STEP) {
@@ -320,7 +320,7 @@ export function addBorders(asset, track) {
320
320
  export function addKerbs(asset, track) {
321
321
  const kerbs = new Graphics();
322
322
  kerbs.label = 'track-kerbs';
323
- drawKerbStripes(asset, kerbs, track, { clipUnsafeSegments: true });
323
+ drawKerbStripes(kerbs, track, { clipUnsafeSegments: true });
324
324
  asset.container.addChild(kerbs);
325
325
 
326
326
  }
@@ -14,7 +14,6 @@ export const PIT_BOX_COLOR = 0x242831;
14
14
  export const PIT_LINE_COLOR = 0xf8fafc;
15
15
  export const PIT_SPEED_LINE_COLOR = 0xffd166;
16
16
  export const PIT_CONNECTOR_WIDTH = metersToSimUnits(12);
17
- export const PIT_EDGE_WIDTH = metersToSimUnits(0.35);
18
17
  export const EDGE_REVEAL_OFFSET = metersToSimUnits(0.35);
19
18
  export const EDGE_REVEAL_WIDTH = metersToSimUnits(0.5);
20
19
  export const OUTER_BOUNDARY_OFFSET = metersToSimUnits(1.1);
@@ -1,5 +1,5 @@
1
1
  import { Graphics, Texture } from 'pixi.js';
2
- import { offsetTrackPoint } from '../../simulation/trackModel.js';
2
+ import { offsetTrackPoint } from '../../simulation/track/trackModel.js';
3
3
  import { PIT_LINE_COLOR } from './trackRenderConstants.js';
4
4
 
5
5
  export function colorToNumber(color, fallback = PIT_LINE_COLOR) {
@@ -29,12 +29,6 @@ export function textureOrWhite(texture) {
29
29
  return texture ?? Texture.WHITE;
30
30
  }
31
31
 
32
- export function destroyDisplayChildren(container) {
33
- container.removeChildren().forEach((child) => {
34
- child.destroy?.({ children: true, texture: false, textureSource: false });
35
- });
36
- }
37
-
38
32
  export function arcDistance(track, first, second) {
39
33
  const delta = Math.abs(first - second);
40
34
  return Math.min(delta, track.length - delta);
@@ -7,10 +7,6 @@ const DEFAULT_DISTANCE_WINDOW = 150;
7
7
  const COLLISION_PAIR_FIRST_INDEX = Symbol('collisionPairFirstIndex');
8
8
  const COLLISION_PAIR_SECOND_INDEX = Symbol('collisionPairSecondIndex');
9
9
 
10
- function dot(a, b) {
11
- return a.x * b.x + a.y * b.y;
12
- }
13
-
14
10
  function projectShapeInto(target, shape, axis) {
15
11
  let min = Infinity;
16
12
  let max = -Infinity;
@@ -88,10 +84,6 @@ function aabbsOverlap(first, second) {
88
84
  );
89
85
  }
90
86
 
91
- function createSweptAabb(car) {
92
- return getVehicleGeometryState(car).sweptBodyAabb;
93
- }
94
-
95
87
  function writeShapeCollisionResult(target, firstShape, secondShape, axisX, axisY, depth) {
96
88
  target.axis ??= {};
97
89
  target.axis.x = axisX;
@@ -120,10 +112,12 @@ function withCollisionMetadata(collision, timeOfImpact, swept, scratch) {
120
112
  function detectVehicleCollisionWithScratch(first, second, sweepSteps, scratch) {
121
113
  const firstState = getVehicleGeometryState(first);
122
114
  const secondState = getVehicleGeometryState(second);
123
- const current = detectShapeCollision(firstState.current.body, secondState.current.body, scratch);
124
- if (current) return withCollisionMetadata(current, 1, false, scratch);
115
+ if (aabbsOverlap(firstState.bodyAabb, secondState.bodyAabb)) {
116
+ const current = detectShapeCollision(firstState.current.body, secondState.current.body, scratch);
117
+ if (current) return withCollisionMetadata(current, 1, false, scratch);
118
+ }
125
119
 
126
- if (!aabbsOverlap(createSweptAabb(first), createSweptAabb(second))) return null;
120
+ if (!aabbsOverlap(firstState.sweptBodyAabb, secondState.sweptBodyAabb)) return null;
127
121
 
128
122
  const sweepShapes = ensureSweepShapes(scratch, firstState.current.body, secondState.current.body);
129
123
  for (let step = 1; step < sweepSteps; step += 1) {
@@ -0,0 +1,53 @@
1
+ import { clamp, normalizeAngle } from '../simMath.js';
2
+ import { simUnitsToMeters } from '../units.js';
3
+ import { VEHICLE_LIMITS, tirePerformanceFactor } from '../vehicle/vehiclePhysics.js';
4
+ import { ADVANCED_VEHICLE_MODEL, advancedDriveForce } from '../vehicle/advancedVehicleModel.js';
5
+ import { advancedLateralAccelerationLimit, advancedRearForceCapacity, advancedRollingResistanceForce } from '../vehicle/advancedTireForces.js';
6
+
7
+ export function advancedSteeringToPoint(car, target) {
8
+ const speed = simUnitsToMeters(car.speed);
9
+ const dx = simUnitsToMeters(target.x - car.x);
10
+ const dy = simUnitsToMeters(target.y - car.y);
11
+ const angle = normalizeAngle(Math.atan2(dy, dx) - car.heading);
12
+ // Pure pursuit gives a geometric curvature in 1/m, then a physical wheel
13
+ // angle. Yaw feedback damps the inertial response without moving the car.
14
+ const curvature = 2 * Math.sin(angle) / Math.max(Math.hypot(dx, dy), 1);
15
+ const desiredYaw = speed * curvature;
16
+ const slip = car.slipAngleRadians ?? 0;
17
+ const countersteer = Math.sign(slip) * Math.max(0, Math.abs(slip) - 0.08) * 0.35;
18
+ return {
19
+ curvature,
20
+ steering: Math.atan(simUnitsToMeters(VEHICLE_LIMITS.wheelbase) * curvature) +
21
+ (desiredYaw - (car.yawRate ?? 0)) * 0.04 + countersteer,
22
+ };
23
+ }
24
+
25
+ export function advancedLongitudinalLimits(car) {
26
+ const speed = simUnitsToMeters(car.speed);
27
+ const tireFactor = tirePerformanceFactor(car.tireEnergy ?? 100);
28
+ const capacity = advancedLateralAccelerationLimit(car, speed, tireFactor);
29
+ const lateralUsage = clamp(Math.abs(speed * (car.yawRate ?? 0)) / capacity, 0, 0.95);
30
+ // Static axle capacity is an estimate: keep a margin for load transfer and
31
+ // reserve the lateral share before requesting rear drive or brake force.
32
+ const longitudinalFraction = Math.sqrt(1 - lateralUsage ** 2) * 0.65;
33
+ const force = capacity * car.mass * longitudinalFraction;
34
+ let driveForce = force * (1 - ADVANCED_VEHICLE_MODEL.frontWeightFraction);
35
+ if (speed < 5 && !car.trackState.onTrack) {
36
+ // A crawling car must overcome all four wheels' rolling resistance. Use
37
+ // the rear tires' remaining adhesion, not an artificial minimum throttle.
38
+ const rearCapacity = advancedRearForceCapacity(car, speed, tireFactor) * Math.sqrt(1 - lateralUsage ** 2);
39
+ const rollingCompensation = advancedRollingResistanceForce(car, speed) * (1 - speed / 5);
40
+ driveForce = Math.min(rearCapacity, driveForce + rollingCompensation);
41
+ }
42
+ const throttleLimit = driveForce / advancedDriveForce(car, speed);
43
+ const brakeLimit = force / car.brakeNewtons;
44
+ return { speed, throttleLimit, brakeLimit };
45
+ }
46
+
47
+ export function advancedSpeedControls(car, targetSpeed) {
48
+ const { speed, throttleLimit, brakeLimit } = advancedLongitudinalLimits(car);
49
+ const error = targetSpeed - speed;
50
+ const brake = clamp(-error / 8, 0, Math.min(0.85, brakeLimit));
51
+ const throttle = brake > 0.03 ? 0 : clamp(error / 8, 0, Math.min(1, throttleLimit));
52
+ return { throttle, brake };
53
+ }