@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
@@ -110,24 +110,6 @@ function writeOrientedRectValues(target, {
110
110
  return rect;
111
111
  }
112
112
 
113
- function writeOrientedRect(target, { id, type, center, heading, length, width }) {
114
- const cos = Math.cos(heading);
115
- const sin = Math.sin(heading);
116
- return writeOrientedRectValues(target, {
117
- id,
118
- type,
119
- centerX: center.x,
120
- centerY: center.y,
121
- heading,
122
- length,
123
- width,
124
- forwardX: cos,
125
- forwardY: sin,
126
- rightX: -sin,
127
- rightY: cos,
128
- });
129
- }
130
-
131
113
  function writeVehicleGeometry(target, pose) {
132
114
  const geometry = target ?? {};
133
115
  const forwardX = Math.cos(pose.heading);
@@ -238,10 +220,6 @@ function writeCurrentVehicleGeometry(target, pose) {
238
220
  return geometry;
239
221
  }
240
222
 
241
- export function createOrientedRect({ id, type, center, heading, length, width }) {
242
- return writeOrientedRect(null, { id, type, center, heading, length, width });
243
- }
244
-
245
223
  export function createVehicleGeometry(car, options = {}) {
246
224
  const pose = vehiclePose(car, options);
247
225
  return writeVehicleGeometry(null, pose);
@@ -274,33 +252,22 @@ function sweptPoseSignature(pose) {
274
252
  ].join(':');
275
253
  }
276
254
 
277
- function geometryPose(car) {
278
- const { x, y, heading } = currentPoseState(car);
279
- return {
280
- x,
281
- y,
282
- heading,
283
- previousX: finiteOr(car.previousX, x),
284
- previousY: finiteOr(car.previousY, y),
285
- previousHeading: normalizeAngle(finiteOr(car.previousHeading, heading)),
286
- };
287
- }
288
-
289
255
  function currentGeometryStateMatches(car, state) {
290
256
  const pose = state?.pose;
291
257
  if (!pose) return false;
292
- const currentPose = currentPoseState(car);
293
258
  return (
294
- pose.x === currentPose.x &&
295
- pose.y === currentPose.y &&
296
- pose.heading === currentPose.heading
259
+ pose.x === finiteOr(car.x, 0) &&
260
+ pose.y === finiteOr(car.y, 0) &&
261
+ pose.heading === normalizeAngle(finiteOr(car.heading, 0))
297
262
  );
298
263
  }
299
264
 
300
265
  function geometryStateMatches(car, state) {
301
266
  const pose = state?.pose;
302
267
  if (!pose) return false;
303
- const { x, y, heading } = currentPoseState(car);
268
+ const x = finiteOr(car.x, 0);
269
+ const y = finiteOr(car.y, 0);
270
+ const heading = normalizeAngle(finiteOr(car.heading, 0));
304
271
  return (
305
272
  pose.x === x &&
306
273
  pose.y === y &&
@@ -312,6 +279,10 @@ function geometryStateMatches(car, state) {
312
279
  }
313
280
 
314
281
  export function createVehicleShapeAabb(shape) {
282
+ return writeVehicleShapeAabb({}, shape);
283
+ }
284
+
285
+ function writeVehicleShapeAabb(target, shape) {
315
286
  let minX = Infinity;
316
287
  let maxX = -Infinity;
317
288
  let minY = Infinity;
@@ -323,55 +294,80 @@ export function createVehicleShapeAabb(shape) {
323
294
  if (corner.y < minY) minY = corner.y;
324
295
  if (corner.y > maxY) maxY = corner.y;
325
296
  }
326
- return {
327
- minX,
328
- maxX,
329
- minY,
330
- maxY,
331
- };
297
+ target.minX = minX;
298
+ target.maxX = maxX;
299
+ target.minY = minY;
300
+ target.maxY = maxY;
301
+ return target;
332
302
  }
333
303
 
334
- export function mergeAabbs(aabbs) {
335
- let minX = Infinity;
336
- let maxX = -Infinity;
337
- let minY = Infinity;
338
- let maxY = -Infinity;
339
- for (let index = 0; index < aabbs.length; index += 1) {
340
- const aabb = aabbs[index];
341
- if (aabb.minX < minX) minX = aabb.minX;
342
- if (aabb.maxX > maxX) maxX = aabb.maxX;
343
- if (aabb.minY < minY) minY = aabb.minY;
344
- if (aabb.maxY > maxY) maxY = aabb.maxY;
345
- }
346
- return { minX, maxX, minY, maxY };
304
+ function writeGeometryPose(target, car) {
305
+ const pose = target ?? {};
306
+ const x = finiteOr(car.x, 0);
307
+ const y = finiteOr(car.y, 0);
308
+ const heading = normalizeAngle(finiteOr(car.heading, 0));
309
+ pose.x = x;
310
+ pose.y = y;
311
+ pose.heading = heading;
312
+ pose.previousX = finiteOr(car.previousX, x);
313
+ pose.previousY = finiteOr(car.previousY, y);
314
+ pose.previousHeading = normalizeAngle(finiteOr(car.previousHeading, heading));
315
+ return pose;
347
316
  }
348
317
 
349
- export function createVehicleGeometryState(car) {
350
- const pose = geometryPose(car);
351
- const current = writeVehicleGeometry(null, pose);
352
- const previous = writeVehicleGeometry(null, {
353
- x: pose.previousX,
354
- y: pose.previousY,
355
- heading: pose.previousHeading,
318
+ function createGeometryStateShell() {
319
+ const state = {
320
+ pose: null,
321
+ current: null,
322
+ previous: null,
323
+ body: null,
324
+ wheels: null,
325
+ contactPatches: null,
326
+ bodyAabb: {},
327
+ previousBodyAabb: {},
328
+ sweptBodyAabb: {},
329
+ };
330
+ // Signatures are derived lazily from the pose: nothing on the hot path reads
331
+ // them, so the per-step string formatting cost must not be paid eagerly.
332
+ Object.defineProperties(state, {
333
+ currentSignature: {
334
+ get() { return currentPoseSignature(this.pose); },
335
+ enumerable: true,
336
+ },
337
+ sweptSignature: {
338
+ get() { return sweptPoseSignature(this.pose); },
339
+ enumerable: true,
340
+ },
341
+ signature: {
342
+ get() { return sweptPoseSignature(this.pose); },
343
+ enumerable: true,
344
+ },
356
345
  });
357
- const bodyAabb = createVehicleShapeAabb(current.body);
358
- const previousBodyAabb = createVehicleShapeAabb(previous.body);
359
- const sweptBodyAabb = mergeAabbs([previousBodyAabb, bodyAabb]);
346
+ return state;
347
+ }
360
348
 
361
- return {
362
- pose,
363
- currentSignature: currentPoseSignature(pose),
364
- sweptSignature: sweptPoseSignature(pose),
365
- signature: sweptPoseSignature(pose),
366
- current,
367
- previous,
368
- body: current.body,
369
- wheels: current.wheels,
370
- contactPatches: current.contactPatches,
371
- bodyAabb,
372
- previousBodyAabb,
373
- sweptBodyAabb,
374
- };
349
+ const PREVIOUS_GEOMETRY_POSE_SCRATCH = { x: 0, y: 0, heading: 0 };
350
+
351
+ export function createVehicleGeometryState(car, target = null) {
352
+ const state = target ?? createGeometryStateShell();
353
+ const pose = state.pose = writeGeometryPose(state.pose, car);
354
+ const current = state.current = writeVehicleGeometry(state.current, pose);
355
+ PREVIOUS_GEOMETRY_POSE_SCRATCH.x = pose.previousX;
356
+ PREVIOUS_GEOMETRY_POSE_SCRATCH.y = pose.previousY;
357
+ PREVIOUS_GEOMETRY_POSE_SCRATCH.heading = pose.previousHeading;
358
+ const previous = state.previous = writeVehicleGeometry(state.previous, PREVIOUS_GEOMETRY_POSE_SCRATCH);
359
+ const bodyAabb = writeVehicleShapeAabb(state.bodyAabb, current.body);
360
+ const previousBodyAabb = writeVehicleShapeAabb(state.previousBodyAabb, previous.body);
361
+ const sweptBodyAabb = state.sweptBodyAabb;
362
+ sweptBodyAabb.minX = Math.min(bodyAabb.minX, previousBodyAabb.minX);
363
+ sweptBodyAabb.maxX = Math.max(bodyAabb.maxX, previousBodyAabb.maxX);
364
+ sweptBodyAabb.minY = Math.min(bodyAabb.minY, previousBodyAabb.minY);
365
+ sweptBodyAabb.maxY = Math.max(bodyAabb.maxY, previousBodyAabb.maxY);
366
+
367
+ state.body = current.body;
368
+ state.wheels = current.wheels;
369
+ state.contactPatches = current.contactPatches;
370
+ return state;
375
371
  }
376
372
 
377
373
  export function createCurrentVehicleGeometryState(car, target = null) {
@@ -388,8 +384,13 @@ export function createCurrentVehicleGeometryState(car, target = null) {
388
384
 
389
385
  export function getVehicleGeometryState(car) {
390
386
  if (geometryStateMatches(car, car.geometryState)) return car.geometryState;
391
- car.geometryState = createVehicleGeometryState(car);
392
- return car.geometryState;
387
+ // Double-buffer pooled states: consumers may still hold the state returned
388
+ // for the previous pose within a step, so alternate between two reusable
389
+ // shells instead of allocating a fresh geometry tree on every pose change.
390
+ const next = createVehicleGeometryState(car, car._geometryStateSpare ?? null);
391
+ car._geometryStateSpare = car.geometryState ?? null;
392
+ car.geometryState = next;
393
+ return next;
393
394
  }
394
395
 
395
396
  export function getCurrentVehicleGeometryState(car) {
@@ -417,25 +418,6 @@ export function ensureOrientedRectCorners(rect) {
417
418
  }).corners;
418
419
  }
419
420
 
420
- export function createVehicleAabb(car, options = {}) {
421
- const geometry = createVehicleGeometry(car, options);
422
- return mergeAabbs(geometry.shapes.map(createVehicleShapeAabb));
423
- }
424
-
425
- export function interpolateVehiclePose(car, amount) {
426
- const previousX = finiteOr(car.previousX, car.x);
427
- const previousY = finiteOr(car.previousY, car.y);
428
- const previousHeading = finiteOr(car.previousHeading, car.heading);
429
- const headingDelta = normalizeAngle(finiteOr(car.heading, 0) - previousHeading);
430
-
431
- return {
432
- ...car,
433
- x: previousX + (finiteOr(car.x, previousX) - previousX) * amount,
434
- y: previousY + (finiteOr(car.y, previousY) - previousY) * amount,
435
- heading: normalizeAngle(previousHeading + headingDelta * amount),
436
- };
437
- }
438
-
439
421
  export function getCarCorners(car) {
440
422
  return createVehicleGeometry(car).body.corners;
441
423
  }
@@ -1,3 +1,5 @@
1
+ import { normalizeAngle } from '../simMath.js';
2
+
1
3
  export function freezeVehicleMotion(car, {
2
4
  brake = 1,
3
5
  clearManualControls = false,
@@ -35,3 +37,11 @@ export function syncVehicleVelocityToHeading(car) {
35
37
  car.velocityX = Math.cos(heading) * speed;
36
38
  car.velocityY = Math.sin(heading) * speed;
37
39
  }
40
+
41
+ export function shiftPreviousRenderPose(car, dx, dy, dheading = 0) {
42
+ car.previousX = Number.isFinite(car.previousX) ? car.previousX + dx : car.x + dx;
43
+ car.previousY = Number.isFinite(car.previousY) ? car.previousY + dy : car.y + dy;
44
+ car.previousHeading = normalizeAngle(
45
+ (Number.isFinite(car.previousHeading) ? car.previousHeading : car.heading) + dheading,
46
+ );
47
+ }
@@ -1,9 +1,9 @@
1
+ import { integrateAdvancedVehiclePhysics } from './advancedVehiclePhysics.js';
1
2
  import { clamp, normalizeAngle } from '../simMath.js';
2
3
  import {
3
4
  REAL_F1_WHEELBASE_METERS,
4
5
  SIM_UNITS_PER_METER,
5
6
  TOP_SPEED_SIM_UNITS_PER_SECOND,
6
- metersPerSecondToSimSpeed,
7
7
  simUnitsToMeters,
8
8
  metersToSimUnits,
9
9
  } from '../units.js';
@@ -37,20 +37,12 @@ const SURFACE_MODEL = {
37
37
  const WHEEL_DRAG_YAW_GAIN = 0.08;
38
38
  const MAX_WHEEL_DRAG_YAW_RATE = 0.22;
39
39
 
40
- const SIMULATOR_SURFACE_MODEL = {
41
- track: { grip: 1, drag: 0, rollingResistance: 0.008, scrub: 0 },
42
- 'pit-entry': { grip: 0.92, drag: 0.06, rollingResistance: 0.018, scrub: 1.2 },
43
- 'pit-lane': { grip: 0.9, drag: 0.08, rollingResistance: 0.02, scrub: 1.4 },
44
- 'pit-exit': { grip: 0.92, drag: 0.06, rollingResistance: 0.018, scrub: 1.2 },
45
- 'pit-box': { grip: 0.82, drag: 0.12, rollingResistance: 0.035, scrub: 2.5 },
46
- kerb: { grip: 0.78, drag: 0.42, rollingResistance: 0.09, scrub: 8.5 },
47
- gravel: { grip: 0.36, drag: 5.4, rollingResistance: 0.24, scrub: 18 },
48
- grass: { grip: 0.3, drag: 3.4, rollingResistance: 0.2, scrub: 15 },
49
- barrier: { grip: 0.08, drag: 15, rollingResistance: 2.4, scrub: 30 },
50
- };
51
-
52
40
  export function normalizePhysicsMode(value) {
53
- return value === 'advanced' ? 'advanced' : DEFAULT_PHYSICS_MODE;
41
+ return PHYSICS_MODES.includes(value) ? value : DEFAULT_PHYSICS_MODE;
42
+ }
43
+
44
+ export function isSimulatorPhysicsMode(value) {
45
+ return value === 'advanced';
54
46
  }
55
47
 
56
48
  function accelerationLimit(speed, surfaceGrip) {
@@ -63,11 +55,39 @@ function brakingLimit(speed, surfaceGrip) {
63
55
  return SIM_UNITS_PER_METER * surfaceGrip * (20 + speedRatio * 14);
64
56
  }
65
57
 
58
+ // Policy accessors use the same arcade model without changing its integrator.
59
+ // Surface grip is the base coefficient; callers apply tire condition once.
60
+ export function arcadeSurfaceGrip(car) {
61
+ return (SURFACE_MODEL[car.trackState?.surface] ?? SURFACE_MODEL.track).grip;
62
+ }
63
+
64
+ export function arcadeResistanceForce(car, speedMetersPerSecond) {
65
+ const surface = SURFACE_MODEL[car.trackState?.surface] ?? SURFACE_MODEL.track;
66
+ const dragMultiplier = car.drsActive ? 0.42 : 1;
67
+ return (car.dragCoefficient * dragMultiplier * 0.12 + surface.drag) * speedMetersPerSecond ** 2 +
68
+ surface.rollingResistance * car.mass * G;
69
+ }
70
+
71
+ // The existing limits take speed and return acceleration in simulator units.
72
+ export { accelerationLimit as arcadeAccelerationLimit, brakingLimit as arcadeBrakingLimit };
73
+
66
74
  export function tirePerformanceFactor(tireEnergy = 100) {
67
75
  const normalized = clamp((Number(tireEnergy) || 1) / 100, 0.01, 1);
68
76
  return clamp(0.45 + 0.55 * normalized ** 0.72, 0.45, 1);
69
77
  }
70
78
 
79
+ export function arcadeTrackCornerSpeedLimit(car, curvature, gripShare) {
80
+ const tire = tirePerformanceFactor(car.tireEnergy ?? 100);
81
+ // On tarmac the arcade yaw limit is (static + aero * speed²) / speed.
82
+ // Invert that same law for a future corner; the driver owns its grip reserve.
83
+ const staticCapacity = car.tireGrip * G * tire * tire;
84
+ const aeroCapacity = car.downforceCoefficient / car.mass * tire;
85
+ const denominator = Math.abs(curvature) - gripShare * aeroCapacity;
86
+ return denominator > 0
87
+ ? Math.min(simUnitsToMeters(VEHICLE_LIMITS.maxSpeed), Math.sqrt(gripShare * staticCapacity / denominator))
88
+ : simUnitsToMeters(VEHICLE_LIMITS.maxSpeed);
89
+ }
90
+
71
91
  function surfaceResistance(surfaceName, model = SURFACE_MODEL) {
72
92
  const surface = model[surfaceName] ?? model.track;
73
93
  return surface.drag * 0.14 + surface.rollingResistance * 2.4 + (1 - surface.grip) * 0.65;
@@ -120,8 +140,14 @@ function setPhysicsTelemetry(car, {
120
140
  }
121
141
 
122
142
  export function integrateVehiclePhysics(car, controls, dt, options = {}) {
123
- if (normalizePhysicsMode(options.physicsMode) === 'advanced') {
124
- return integrateSimulatorVehiclePhysics(car, controls, dt, options);
143
+ const mode = normalizePhysicsMode(options.physicsMode);
144
+ if (mode === 'advanced') {
145
+ return integrateAdvancedVehiclePhysics(car, controls, dt, {
146
+ tireFactor: tirePerformanceFactor(car.tireEnergy ?? 100),
147
+ tireDegradationEnabled: options.tireDegradationEnabled,
148
+ maxSteer: VEHICLE_LIMITS.maxSteer,
149
+ steerRate: VEHICLE_LIMITS.steerRate,
150
+ });
125
151
  }
126
152
  return integrateArcadeVehiclePhysics(car, controls, dt, options);
127
153
  }
@@ -196,184 +222,4 @@ function integrateArcadeVehiclePhysics(car, controls, dt, options = {}) {
196
222
  return car;
197
223
  }
198
224
 
199
- function simulatorSurface(surfaceName) {
200
- return SIMULATOR_SURFACE_MODEL[surfaceName] ?? SIMULATOR_SURFACE_MODEL.track;
201
- }
202
-
203
- function aggregateSimulatorSurface(car) {
204
- const wheels = Array.isArray(car.wheelStates) ? car.wheelStates : [];
205
- if (!wheels.length) return simulatorSurface(car.trackState?.surface ?? 'track');
206
- let grip = 0;
207
- let drag = 0;
208
- let rollingResistance = 0;
209
- let scrub = 0;
210
- for (let index = 0; index < wheels.length; index += 1) {
211
- const surface = simulatorSurface(wheels[index].surface);
212
- grip += surface.grip;
213
- drag += surface.drag;
214
- rollingResistance += surface.rollingResistance;
215
- scrub += surface.scrub;
216
- }
217
- return {
218
- grip: grip / wheels.length,
219
- drag: drag / wheels.length,
220
- rollingResistance: rollingResistance / wheels.length,
221
- scrub: scrub / wheels.length,
222
- };
223
- }
224
-
225
- function simulatorStabilityState({ gripUsage, slipAngle, surfaceName, brake, throttle }) {
226
- if (gripUsage > 1.32 || Math.abs(slipAngle) > 0.42) return 'spin-risk';
227
- if (gripUsage > 1.02) return brake > 0.2 && throttle < 0.2 ? 'oversteer' : 'understeer';
228
- if (surfaceName === 'kerb' && gripUsage > 0.72) return 'oversteer';
229
- if ((surfaceName === 'gravel' || surfaceName === 'grass') && gripUsage > 0.5) return 'understeer';
230
- return 'stable';
231
- }
232
-
233
- function ensureSimulatorVelocity(car) {
234
- if (Number.isFinite(car.velocityX) && Number.isFinite(car.velocityY)) return;
235
- const speed = clamp(Number(car.speed) || 0, 0, VEHICLE_LIMITS.maxSpeed);
236
- car.velocityX = Math.cos(car.heading ?? 0) * speed;
237
- car.velocityY = Math.sin(car.heading ?? 0) * speed;
238
- }
239
-
240
- function limitSimulatorSpeed(car) {
241
- const speed = Math.hypot(car.velocityX, car.velocityY);
242
- if (speed <= VEHICLE_LIMITS.maxSpeed || speed <= 0) return speed;
243
- const scale = VEHICLE_LIMITS.maxSpeed / speed;
244
- car.velocityX *= scale;
245
- car.velocityY *= scale;
246
- return VEHICLE_LIMITS.maxSpeed;
247
- }
248
-
249
- function integrateSimulatorVehiclePhysics(car, controls, dt, options = {}) {
250
- ensureSimulatorVelocity(car);
251
- const steeringTarget = clamp(controls.steering ?? 0, -VEHICLE_LIMITS.maxSteer, VEHICLE_LIMITS.maxSteer);
252
- const steerDelta = clamp(
253
- steeringTarget - car.steeringAngle,
254
- -VEHICLE_LIMITS.steerRate * dt,
255
- VEHICLE_LIMITS.steerRate * dt,
256
- );
257
- car.steeringAngle += steerDelta;
258
-
259
- const throttle = clamp(controls.throttle ?? 0, 0, 1);
260
- const brake = clamp(controls.brake ?? 0, 0, 1);
261
- const surfaceName = car.trackState?.surface ?? 'track';
262
- const surface = aggregateSimulatorSurface(car);
263
- const tireFactor = tirePerformanceFactor(car.tireEnergy ?? 100);
264
- const surfaceGrip = surface.grip * tireFactor;
265
- const speedBefore = Math.hypot(car.velocityX, car.velocityY);
266
- const speedBeforeMps = simUnitsToMeters(speedBefore);
267
- const speedRatio = clamp(speedBefore / VEHICLE_LIMITS.maxSpeed, 0, 1);
268
- const downforceGrip = car.downforceCoefficient * speedBeforeMps * speedBeforeMps / car.mass;
269
- const totalGripAcceleration = Math.max(0.1, (car.tireGrip * tireFactor * G + downforceGrip) * surfaceGrip);
270
- const heading = car.heading ?? 0;
271
- const forwardX = Math.cos(heading);
272
- const forwardY = Math.sin(heading);
273
- const rightX = -forwardY;
274
- const rightY = forwardX;
275
- const forwardVelocity = car.velocityX * forwardX + car.velocityY * forwardY;
276
- const lateralVelocity = car.velocityX * rightX + car.velocityY * rightY;
277
- const forwardSpeedMps = simUnitsToMeters(forwardVelocity);
278
- const lateralSpeedMps = simUnitsToMeters(lateralVelocity);
279
- const currentSlipAngle = Math.atan2(lateralSpeedMps, Math.max(Math.abs(forwardSpeedMps), 0.1));
280
- const engineForce = throttle *
281
- car.powerNewtons *
282
- (car.drsActive ? 1.07 : 1) *
283
- Math.max(0.08, 1 - speedRatio ** 1.22);
284
- const brakeForce = brake * car.brakeNewtons;
285
- const driveAcceleration = Math.min(
286
- engineForce / car.mass,
287
- accelerationLimit(car.speed, surfaceGrip) / SIM_UNITS_PER_METER,
288
- );
289
- const brakeDeceleration = Math.min(
290
- brakeForce / car.mass,
291
- brakingLimit(car.speed, surfaceGrip) / SIM_UNITS_PER_METER,
292
- );
293
- const dragMultiplier = car.drsActive ? 0.46 : 1;
294
- const dragAcceleration = (
295
- (car.dragCoefficient * dragMultiplier * 0.28 + surface.drag * 0.018) *
296
- speedBeforeMps * speedBeforeMps
297
- ) / car.mass;
298
- const rollingAcceleration = surface.rollingResistance * G;
299
- const normalizedSteer = Math.abs(car.steeringAngle) / VEHICLE_LIMITS.maxSteer;
300
- const speedSensitiveSteer = 1 / (1 + (Math.abs(forwardSpeedMps) / 58) ** 1.85 * 2.35);
301
- const effectiveSteeringAngle = car.steeringAngle * clamp(speedSensitiveSteer, 0.16, 1);
302
- const rawYawRate = Math.abs(forwardSpeedMps) <= 0.05
303
- ? 0
304
- : (forwardSpeedMps / REAL_F1_WHEELBASE_METERS) * Math.tan(effectiveSteeringAngle);
305
- const sideSlipDamping = 4.2 + surfaceGrip * 2.1 + speedRatio * 1.6;
306
- const desiredLateralAcceleration = forwardSpeedMps * rawYawRate - lateralSpeedMps * sideSlipDamping;
307
- const longitudinalDemand = Math.max(driveAcceleration, brakeDeceleration);
308
- const longitudinalUsage = clamp(longitudinalDemand / Math.max(totalGripAcceleration * 0.52, 1), 0, 0.94);
309
- const lateralCapacity = totalGripAcceleration * Math.sqrt(Math.max(0.08, 1 - longitudinalUsage ** 2));
310
- const desiredLateralAbs = Math.abs(desiredLateralAcceleration);
311
- const lateralUsage = desiredLateralAbs / Math.max(lateralCapacity, 0.1);
312
- const gripUsage = Math.sqrt(lateralUsage ** 2 + longitudinalUsage ** 2);
313
- const tractionLimited = gripUsage > 1 || longitudinalUsage > 0.9;
314
- const actualLateralAcceleration = clamp(desiredLateralAcceleration, -lateralCapacity, lateralCapacity);
315
- const steeringYawRate = Math.abs(forwardSpeedMps) <= 0.05 ? 0 : actualLateralAcceleration / Math.max(Math.abs(forwardSpeedMps), 0.1);
316
- const steeringScrubAcceleration = normalizedSteer ** 1.55 *
317
- speedRatio ** 1.05 *
318
- (42 + surface.scrub + Math.max(0, gripUsage - 0.55) * 18);
319
- const tractionPowerScale = tractionLimited
320
- ? clamp(1 - (gripUsage - 1) * 0.55 - longitudinalUsage * 0.18, 0.12, 1)
321
- : 1;
322
- const brakingDirection = forwardSpeedMps >= -0.2 ? 1 : -1;
323
- const scrubDirection = forwardSpeedMps >= 0 ? 1 : -1;
324
- const longitudinalAcceleration =
325
- driveAcceleration * tractionPowerScale -
326
- brakeDeceleration * brakingDirection -
327
- dragAcceleration * Math.sign(forwardSpeedMps || 1) -
328
- rollingAcceleration * Math.sign(forwardSpeedMps || 1) -
329
- steeringScrubAcceleration * scrubDirection;
330
-
331
- const requestedWheelDragYawRate = wheelDragYawRate(car, SIMULATOR_SURFACE_MODEL) * (surfaceName === 'kerb' ? 1.6 : 1);
332
- const maxYawRate = speedBeforeMps <= 0.05 ? 0 : totalGripAcceleration / Math.max(speedBeforeMps, 0.1);
333
- const slipSteeringAuthority = clamp(1 - Math.abs(currentSlipAngle) * 1.8, 0.35, 1);
334
- const yawTarget = clamp(steeringYawRate * slipSteeringAuthority + requestedWheelDragYawRate, -maxYawRate, maxYawRate);
335
- const yawResponse = 1.6 + surfaceGrip * 0.9 + speedRatio * 0.5;
336
- const yawAccelerationCap = speedRatio < 0.18 ? 6.2 : 3.2;
337
- const maxYawDelta = Math.min(Math.max(0.08, maxYawRate * yawResponse), yawAccelerationCap) * dt;
338
- car.yawRate = clamp(
339
- (car.yawRate ?? 0) + clamp(yawTarget - (car.yawRate ?? 0), -maxYawDelta, maxYawDelta),
340
- -maxYawRate,
341
- maxYawRate,
342
- );
343
- car.wheelDragYawRate = requestedWheelDragYawRate;
344
- car.heading = normalizeAngle(car.heading + car.yawRate * dt);
345
-
346
- const accelerationX = forwardX * longitudinalAcceleration + rightX * actualLateralAcceleration;
347
- const accelerationY = forwardY * longitudinalAcceleration + rightY * actualLateralAcceleration;
348
- car.velocityX += metersToSimUnits(accelerationX) * dt;
349
- car.velocityY += metersToSimUnits(accelerationY) * dt;
350
- car.speed = limitSimulatorSpeed(car);
351
- car.x += car.velocityX * dt;
352
- car.y += car.velocityY * dt;
353
- car.throttle = throttle;
354
- car.brake = brake;
355
- car.steerSaturation = rawYawRate === 0 ? 0 : Math.abs(steeringYawRate / rawYawRate);
356
- car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
357
- const velocityHeading = car.speed <= 0.01 ? car.heading : Math.atan2(car.velocityY, car.velocityX);
358
- const slipAngle = normalizeAngle(velocityHeading - car.heading);
359
- setPhysicsTelemetry(car, {
360
- lateralAcceleration: actualLateralAcceleration,
361
- longitudinalAcceleration,
362
- gripUsage,
363
- slipAngleRadians: slipAngle,
364
- tractionLimited,
365
- stabilityState: simulatorStabilityState({ gripUsage, slipAngle, surfaceName, brake, throttle }),
366
- });
367
-
368
- if (options.tireDegradationEnabled !== false) {
369
- const tyreLoad = Math.abs(car.lateralAcceleration) / G;
370
- const tireCare = clamp(Number(car.tireCare) || 1, 0.45, 1.8);
371
- const heatPenalty = Math.max(0, gripUsage - 0.72) * 0.24 + normalizedSteer * speedRatio * 0.08;
372
- const wearRate = (0.045 + tyreLoad * 0.16 + brake * 0.09 + throttle * 0.035 + heatPenalty) / tireCare;
373
- car.tireEnergy = clamp((car.tireEnergy ?? 100) - wearRate * dt, 1, 100);
374
- }
375
-
376
- return car;
377
- }
378
-
379
225
  export { getCarCorners };
@@ -1,8 +1,11 @@
1
- import { serializeParticipantInteraction } from '../participants/participantInteractions.js';
1
+ import { finiteOrNull } from '../simMath.js';
2
+ export { finiteOrNull } from '../simMath.js';
3
+
4
+ import {
5
+ serializeParticipantInteraction,
6
+ serializeParticipantInteractionCached,
7
+ } from '../participants/participantInteractions.js';
2
8
 
3
- export function finiteOrNull(value) {
4
- return Number.isFinite(value) ? value : null;
5
- }
6
9
 
7
10
  function serializeAppliedControls(car, dependencies) {
8
11
  const { VEHICLE_LIMITS } = dependencies;
@@ -21,6 +24,15 @@ function serializeVelocityComponent(car, component, dependencies) {
21
24
  return finiteOrNull(car[component]);
22
25
  }
23
26
 
27
+ function serializeTeam(team) {
28
+ if (!team) return null;
29
+ return {
30
+ ...team,
31
+ ...(team.pitCrew && typeof team.pitCrew === 'object' ? { pitCrew: { ...team.pitCrew } } : {}),
32
+ ...(team.pitCrewStats && typeof team.pitCrewStats === 'object' ? { pitCrewStats: { ...team.pitCrewStats } } : {}),
33
+ };
34
+ }
35
+
24
36
  export function serializeWheels(wheels = []) {
25
37
  return wheels.map((wheel) => ({
26
38
  id: wheel.id,
@@ -57,7 +69,7 @@ export function serializeCar(car, rank, penaltySeconds = 0, dependencies) {
57
69
  raceName: car.raceName,
58
70
  name: car.name,
59
71
  color: car.color,
60
- team: car.team ? { ...car.team } : null,
72
+ team: serializeTeam(car.team),
61
73
  tire: car.tire,
62
74
  personality: { ...car.personality },
63
75
  aggression: car.aggression,
@@ -185,7 +197,7 @@ export function serializeRenderCarInto(target, car, dependencies) {
185
197
  target.dnfOrder = car.dnfOrder ?? null;
186
198
  target.outOfRace = Boolean(car.outOfRace || car.destroyed);
187
199
  target.pitStop = serializeRenderPitStop(car.pitStop);
188
- target.interaction = serializeParticipantInteraction(car.interaction);
200
+ target.interaction = serializeParticipantInteractionCached(car);
189
201
  return target;
190
202
  }
191
203
 
@@ -1,8 +1,8 @@
1
- import { buildDriverPersonality } from '../driverController.js';
1
+ import { buildDriverPersonality } from '../driver/driverPersonality.js';
2
2
  import { clamp, normalizeAngle, seededRange } from '../simMath.js';
3
3
  import { offsetTrackPoint, pointAt } from '../track/trackModel.js';
4
4
  import { kphToSimSpeed, metersToSimUnits } from '../units.js';
5
- import { VEHICLE_LIMITS } from './vehiclePhysics.js';
5
+ import { VEHICLE_LIMITS, isSimulatorPhysicsMode } from './vehiclePhysics.js';
6
6
  import { applyWheelSurfaceState } from './wheelSurface.js';
7
7
  import { nearestTrackStateForCar } from '../track/trackStatePolicy.js';
8
8
  import { clearCarDnf } from '../race/retirements.js';
@@ -73,8 +73,10 @@ export function createCar(driver, index, random, track, { standingStart = false,
73
73
  downforceCoefficient: vehicle.downforceCoefficient ?? 6.1 + seededRange(random, -0.18, 0.18),
74
74
  tireGrip: vehicle.tireGrip ?? 2.22 + racecraft * 0.28 + seededRange(random, -0.03, 0.03),
75
75
  tireCare: vehicle.tireCare ?? 1,
76
+ driverModel: driver.driverModel ?? vehicle.driverModel ?? null,
76
77
  pace,
77
78
  racecraft,
79
+ consistency: Number.isFinite(driver.consistency) ? clamp(driver.consistency, 0, 1) : 0.75,
78
80
  personality,
79
81
  aggression: personality.baseAggression,
80
82
  gridLocked: standingStart,
@@ -170,7 +172,7 @@ export function applyExternalCarState(car, partial, context) {
170
172
  }
171
173
  car.speed = clamp(Number.isFinite(car.speed) ? car.speed : 0, 0, VEHICLE_LIMITS.maxSpeed);
172
174
  car.heading = normalizeAngle(Number.isFinite(car.heading) ? car.heading : 0);
173
- syncExternalVelocityState(car, nextPartial, physicsMode === 'advanced');
175
+ syncExternalVelocityState(car, nextPartial, isSimulatorPhysicsMode(physicsMode));
174
176
  const centerState = nearestTrackStateForCar(track, car, car, car.progress ?? car.raceDistance);
175
177
  applyWheelSurfaceState(car, track, { centerState });
176
178
  if (