@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
@@ -0,0 +1,127 @@
1
+ import { clamp, normalizeAngle } from '../simMath.js';
2
+ import { SIM_UNITS_PER_METER, TOP_SPEED_SIM_UNITS_PER_SECOND } from '../units.js';
3
+ import { ADVANCED_VEHICLE_MODEL as MODEL, ADVANCED_WHEELS, advancedWheelSurface, advancedDriveForce, advancedDragFactor } from './advancedVehicleModel.js';
4
+ import { advancedNormalLoads, advancedTireForce } from './advancedTireForces.js';
5
+
6
+ export function integrateAdvancedVehiclePhysics(car, controls, dt, { tireFactor, tireDegradationEnabled, maxSteer, steerRate }) {
7
+ if (!(dt > 0) || !Number.isFinite(dt)) return car;
8
+ if (!Number.isFinite(car.velocityX) || !Number.isFinite(car.velocityY)) {
9
+ car.velocityX = Math.cos(car.heading) * car.speed;
10
+ car.velocityY = Math.sin(car.heading) * car.speed;
11
+ }
12
+ const throttle = clamp(controls.throttle ?? 0, 0, 1);
13
+ const brake = clamp(controls.brake ?? 0, 0, 1);
14
+ const steeringTarget = clamp(controls.steering ?? 0, -maxSteer, maxSteer);
15
+ const steps = Math.ceil(dt / MODEL.maxIntegrationStep);
16
+ const h = dt / steps;
17
+ const initialVelocityX = car.velocityX / SIM_UNITS_PER_METER;
18
+ const initialVelocityY = car.velocityY / SIM_UNITS_PER_METER;
19
+ const initialHeading = car.heading;
20
+ let frontUsage = 0;
21
+ let rearUsage = 0;
22
+ let resistanceYawAcceleration = 0;
23
+ for (let step = 0; step < steps; step += 1) {
24
+ car.steeringAngle += clamp(steeringTarget - car.steeringAngle, -steerRate * h, steerRate * h);
25
+ const cos = Math.cos(car.heading);
26
+ const sin = Math.sin(car.heading);
27
+ const vx = car.velocityX / SIM_UNITS_PER_METER;
28
+ const vy = car.velocityY / SIM_UNITS_PER_METER;
29
+ const u = vx * cos + vy * sin;
30
+ const v = -vx * sin + vy * cos;
31
+ const speed = Math.hypot(vx, vy);
32
+ const yaw = car.yawRate ?? 0;
33
+ const loads = advancedNormalLoads(car, speed, car.longitudinalAcceleration ?? 0, car.lateralAcceleration ?? 0);
34
+ // The existing newton-valued power rating defines launch force. Above the
35
+ // crossover speed, the envelope is constant power instead of constant force.
36
+ const drive = throttle * advancedDriveForce(car, u);
37
+ let fx = 0;
38
+ let fy = 0;
39
+ let moment = 0;
40
+ let resistanceMoment = 0;
41
+ frontUsage = 0;
42
+ rearUsage = 0;
43
+ for (let index = 0; index < ADVANCED_WHEELS.length; index += 1) {
44
+ const wheel = ADVANCED_WHEELS[index];
45
+ const { x, y } = wheel;
46
+ const steer = wheel.front ? car.steeringAngle : 0;
47
+ const cs = Math.cos(steer);
48
+ const sn = Math.sin(steer);
49
+ const wheelU = (u - yaw * y) * cs + (v + yaw * x) * sn;
50
+ const wheelV = -(u - yaw * y) * sn + (v + yaw * x) * cs;
51
+ const surface = advancedWheelSurface(car, wheel.id);
52
+ const engine = wheel.front ? 0 : drive / 2;
53
+ const brakeDemand = brake * car.brakeNewtons * (wheel.front ? MODEL.frontBrakeFraction : 1 - MODEL.frontBrakeFraction) / 2;
54
+ // Brake/static resistance cannot manufacture a reverse launch at rest.
55
+ const brakingDirection = Math.sign(wheelU || engine);
56
+ const longitudinalLever = x * sn - y * cs;
57
+ const inverseContactMass = 1 / car.mass + longitudinalLever ** 2 / (car.mass * MODEL.yawInertiaPerMass);
58
+ // Four simultaneous contacts share the stopping impulse. Include yaw
59
+ // inertia and share the budget between braking and rolling resistance;
60
+ // independently spending it twice would create motion near rest.
61
+ const stoppingBudget = Math.abs(wheelU) / (4 * h * inverseContactMass);
62
+ const braking = brakingDirection * Math.min(brakeDemand, stoppingBudget + engine);
63
+ const remainingBudget = Math.max(0, stoppingBudget - Math.max(0, (braking - engine) * Math.sign(wheelU)));
64
+ const rolling = Math.sign(wheelU) * Math.min(surface.rolling * loads[index], remainingBudget);
65
+ const nominalLoad = car.mass * MODEL.gravity * (wheel.front ? MODEL.frontWeightFraction : 1 - MODEL.frontWeightFraction) / 2;
66
+ const tire = advancedTireForce({
67
+ normalLoad: loads[index], nominalLoad, grip: car.tireGrip, tireFactor, surface,
68
+ longitudinalDemand: engine - braking, forwardVelocity: wheelU, lateralVelocity: wheelV,
69
+ });
70
+ const wheelFx = tire.longitudinal - rolling;
71
+ const bodyFx = wheelFx * cs - tire.lateral * sn;
72
+ const bodyFy = wheelFx * sn + tire.lateral * cs;
73
+ fx += bodyFx;
74
+ fy += bodyFy;
75
+ moment += x * bodyFy - y * bodyFx;
76
+ resistanceMoment += x * (-rolling * sn) - y * (-rolling * cs);
77
+ if (wheel.front) frontUsage = Math.max(frontUsage, tire.usage);
78
+ else rearUsage = Math.max(rearUsage, tire.usage);
79
+ }
80
+ const dragFactor = advancedDragFactor(car);
81
+ fx -= dragFactor * speed * u;
82
+ fy -= dragFactor * speed * v;
83
+ car.longitudinalAcceleration = fx / car.mass;
84
+ car.lateralAcceleration = fy / car.mass;
85
+ const ax = (fx * cos - fy * sin) / car.mass;
86
+ const ay = (fx * sin + fy * cos) / car.mass;
87
+ car.velocityX += ax * h * SIM_UNITS_PER_METER;
88
+ car.velocityY += ay * h * SIM_UNITS_PER_METER;
89
+ car.yawRate = yaw + moment / (car.mass * MODEL.yawInertiaPerMass) * h;
90
+ resistanceYawAcceleration = resistanceMoment / (car.mass * MODEL.yawInertiaPerMass);
91
+ car.heading = normalizeAngle(car.heading + car.yawRate * h);
92
+ car.speed = Math.hypot(car.velocityX, car.velocityY);
93
+ // Retain the package's supported top-speed envelope, including externally
94
+ // placed cars. Engine/drag balance determines normal acceleration below it.
95
+ if (car.speed > TOP_SPEED_SIM_UNITS_PER_SECOND) {
96
+ const scale = TOP_SPEED_SIM_UNITS_PER_SECOND / car.speed;
97
+ car.velocityX *= scale;
98
+ car.velocityY *= scale;
99
+ car.speed = TOP_SPEED_SIM_UNITS_PER_SECOND;
100
+ }
101
+ car.x += car.velocityX * h;
102
+ car.y += car.velocityY * h;
103
+ }
104
+ const ax = (car.velocityX / SIM_UNITS_PER_METER - initialVelocityX) / dt;
105
+ const ay = (car.velocityY / SIM_UNITS_PER_METER - initialVelocityY) / dt;
106
+ car.longitudinalAcceleration = ax * Math.cos(initialHeading) + ay * Math.sin(initialHeading);
107
+ car.lateralAcceleration = -ax * Math.sin(initialHeading) + ay * Math.cos(initialHeading);
108
+ car.longitudinalG = car.longitudinalAcceleration / MODEL.gravity;
109
+ car.lateralG = car.lateralAcceleration / MODEL.gravity;
110
+ car.gripUsage = Math.max(frontUsage, rearUsage);
111
+ car.slipAngleRadians = car.speed > 0.01 ? normalizeAngle(Math.atan2(car.velocityY, car.velocityX) - car.heading) : 0;
112
+ car.tractionLimited = car.gripUsage > 1;
113
+ car.stabilityState = Math.abs(car.slipAngleRadians) > 0.35 ? 'spin-risk'
114
+ : rearUsage > 1 && rearUsage > frontUsage * 1.15 ? 'oversteer'
115
+ : frontUsage > 1 ? 'understeer' : 'stable';
116
+ car.wheelDragYawRate = resistanceYawAcceleration * dt;
117
+ car.steerSaturation = 1 / Math.max(1, frontUsage);
118
+ car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
119
+ car.throttle = throttle;
120
+ car.brake = brake;
121
+ if (tireDegradationEnabled !== false) {
122
+ const care = clamp(Number(car.tireCare) || 1, 0.45, 1.8);
123
+ const wear = (0.035 + Math.abs(car.lateralG) * 0.11 + brake * 0.07 + throttle * 0.025 + Math.max(0, car.gripUsage - 1) * 0.03) / care;
124
+ car.tireEnergy = clamp((car.tireEnergy ?? 100) - wear * dt, 1, 100);
125
+ }
126
+ return car;
127
+ }
@@ -6,8 +6,9 @@ import {
6
6
  } from '../collisionGeometry.js';
7
7
  import { canCollide, isCollidable } from '../participants/participantInteractions.js';
8
8
  import { clamp, normalizeAngle } from '../simMath.js';
9
- import { VEHICLE_LIMITS } from './vehiclePhysics.js';
10
- import { shiftPreviousRenderPose } from '../pit/pitRouting.js';
9
+ import { VEHICLE_LIMITS, isSimulatorPhysicsMode } from './vehiclePhysics.js';
10
+ import { shiftPreviousRenderPose } from './vehicleKinematics.js';
11
+ import { applyAdvancedContactResponse } from './advancedContactResponse.js';
11
12
 
12
13
  const MAX_COLLISION_CORRECTION = 4.5;
13
14
 
@@ -113,23 +114,29 @@ export function resolveCollisionsForSimulation(sim) {
113
114
  second.y += secondCorrectionY;
114
115
  }
115
116
 
116
- if (oneCarFixed) {
117
- if (!firstPitControlled) dampPitContactVelocity(first, 0.985, sim.physicsMode === 'advanced');
118
- if (!secondPitControlled) dampPitContactVelocity(second, 0.985, sim.physicsMode === 'advanced');
117
+ const advancedPhysics = isSimulatorPhysicsMode(sim.physicsMode);
118
+ if (advancedPhysics) {
119
+ applyContactVelocityResponse(sim, first, second, collision.axis, {
120
+ firstFixed: firstPitControlled,
121
+ secondFixed: secondPitControlled,
122
+ });
123
+ } else if (oneCarFixed) {
124
+ if (!firstPitControlled) dampPitContactVelocity(first, 0.985, isSimulatorPhysicsMode(sim.physicsMode));
125
+ if (!secondPitControlled) dampPitContactVelocity(second, 0.985, isSimulatorPhysicsMode(sim.physicsMode));
119
126
  } else {
120
127
  applyContactVelocityResponse(sim, first, second, collision.axis);
121
128
  }
122
129
 
123
- const yawNudge = clamp(collision.depth * 0.0025, 0.008, 0.035);
130
+ const yawNudge = advancedPhysics ? 0 : clamp(collision.depth * 0.0025, 0.008, 0.035);
124
131
  const freshContact = first.contactCooldown <= 0 && second.contactCooldown <= 0;
125
132
  const firstHeadingCorrection = firstPitControlled ? 0 : -collision.axis.y * yawNudge;
126
133
  const secondHeadingCorrection = secondPitControlled ? 0 : collision.axis.y * yawNudge;
127
134
  if (!firstPitControlled) {
128
- first.heading = normalizeAngle(first.heading + firstHeadingCorrection);
135
+ if (!advancedPhysics) first.heading = normalizeAngle(first.heading + firstHeadingCorrection);
129
136
  shiftPreviousRenderPose(first, firstCorrectionX, firstCorrectionY, firstHeadingCorrection);
130
137
  }
131
138
  if (!secondPitControlled) {
132
- second.heading = normalizeAngle(second.heading + secondHeadingCorrection);
139
+ if (!advancedPhysics) second.heading = normalizeAngle(second.heading + secondHeadingCorrection);
133
140
  shiftPreviousRenderPose(second, secondCorrectionX, secondCorrectionY, secondHeadingCorrection);
134
141
  }
135
142
  first.contactCooldown = 1;
@@ -166,8 +173,15 @@ export function resolveCollisionsForSimulation(sim) {
166
173
 
167
174
  export function applyContactVelocityResponse(sim, first, second, axis, options = {}) {
168
175
  recordContactVelocityResponse(options.stats);
176
+ if (isSimulatorPhysicsMode(sim.physicsMode)) {
177
+ applyAdvancedContactResponse(first, second, axis, {
178
+ restitution: sim.rules.collisionRestitution,
179
+ firstFixed: options.firstFixed,
180
+ secondFixed: options.secondFixed,
181
+ });
182
+ return;
183
+ }
169
184
  if (
170
- sim.physicsMode === 'advanced' ||
171
185
  Number.isFinite(first.velocityX) ||
172
186
  Number.isFinite(second.velocityX)
173
187
  ) {
@@ -37,17 +37,6 @@ export function wheelOutsideFromOffsets(minimumSignedOffset, maximumSignedOffset
37
37
  };
38
38
  }
39
39
 
40
- export function analyticWheelState(patch, centerState, track, trackLimit) {
41
- return writeAnalyticWheelStateWithProjectedHalfWidth(
42
- {},
43
- patch,
44
- centerState,
45
- track,
46
- trackLimit,
47
- analyticWheelProjectedHalfWidth(patch, centerState),
48
- );
49
- }
50
-
51
40
  export function writeAnalyticWheelState(target, patch, centerState, track, trackLimit) {
52
41
  return writeAnalyticWheelStateWithProjectedHalfWidth(
53
42
  target,
@@ -87,7 +76,7 @@ export function writeAnalyticWheelStateWithProjectedHalfWidth(
87
76
  const state = sampledStates.length === 1 ? (sampledStates[0] ?? {}) : {};
88
77
  sampledStates.length = 1;
89
78
  sampledStates[0] = state;
90
- writeStateFromSignedOffset(state, centerState, track, signedOffset);
79
+ writeStateFromSignedOffset(state, track, signedOffset);
91
80
 
92
81
  target.id = patch.id;
93
82
  target.x = patch.center.x;
@@ -107,7 +96,7 @@ export function writeAnalyticWheelStateWithProjectedHalfWidth(
107
96
  return target;
108
97
  }
109
98
 
110
- function writeStateFromSignedOffset(target, centerState, track, signedOffset) {
99
+ function writeStateFromSignedOffset(target, track, signedOffset) {
111
100
  const crossTrackError = Math.abs(signedOffset);
112
101
  const surface = mainTrackSurface(track, crossTrackError);
113
102
  target.signedOffset = signedOffset;
@@ -38,10 +38,6 @@ export function canSkipConnectorPitChecks(track, geometry, centerState, epsilon
38
38
  return true;
39
39
  }
40
40
 
41
- export function pitPatchOffsetRange(patch, centerState) {
42
- return patchPitOffsetRange(patch, centerState);
43
- }
44
-
45
41
  export function pitPatchInsideRoad(patch, centerState, epsilon = 0.001) {
46
42
  const roadWidth = Number(centerState?.pitLaneRoadWidth);
47
43
  if (!centerState?.inPitLane || !Number.isFinite(roadWidth) || roadWidth <= 0) return false;
@@ -50,14 +46,6 @@ export function pitPatchInsideRoad(patch, centerState, epsilon = 0.001) {
50
46
  return range.minimum >= -halfRoadWidth - epsilon && range.maximum <= halfRoadWidth + epsilon;
51
47
  }
52
48
 
53
- export function pitPatchOverlapsRoad(patch, centerState, epsilon = 0.001) {
54
- const roadWidth = Number(centerState?.pitLaneRoadWidth);
55
- if (!centerState?.inPitLane || !Number.isFinite(roadWidth) || roadWidth <= 0) return false;
56
- const halfRoadWidth = roadWidth / 2;
57
- const range = patchPitOffsetRange(patch, centerState);
58
- return range.minimum <= halfRoadWidth + epsilon && range.maximum >= -halfRoadWidth - epsilon;
59
- }
60
-
61
49
  export function writeAnalyticPitWheelState(target, patch, centerState) {
62
50
  const range = patchPitOffsetRange(patch, centerState);
63
51
  const signedOffset = centerState.mainTrackSignedOffset ?? centerState.signedOffset;
@@ -1,3 +1,4 @@
1
+ import { clearCurrentDrsState } from '../timing/drsState.js';
1
2
  import {
2
3
  VEHICLE_GEOMETRY,
3
4
  vehicleAxes,
@@ -89,10 +90,7 @@ function destroyCarOnBarrier(sim, car) {
89
90
  car.outOfRace = true;
90
91
  markCarDnf(sim, car, { reason: 'barrier' });
91
92
  car.canAttack = false;
92
- car.drsActive = false;
93
- car.drsEligible = false;
94
- car.drsZoneId = null;
95
- car.drsZoneEnabled = false;
93
+ clearCurrentDrsState(car);
96
94
  freezeBarrierMotion(car, sim.physicsMode);
97
95
  sim.events.unshift({
98
96
  type: 'car-destroyed',
@@ -0,0 +1,266 @@
1
+ import { ensureOrientedRectCorners } from './vehicleGeometry.js';
2
+ import { analyticWheelProjectedHalfWidth, wheelFullyOutside, writeAnalyticWheelState } from './mainTrackWheelSurface.js';
3
+ import { pitPatchInsideRoad, writeAnalyticPitWheelState } from './pitWheelSurface.js';
4
+ import { worstState } from './surfacePriority.js';
5
+ import { resetWheelSummary, recordWheelSummary, finishWheelSummary } from './wheelSurfaceSummary.js';
6
+ import {
7
+ nearestTrackStateForCar,
8
+ queryHintedTrackStateForCar,
9
+ queryLocalSegmentTrackProjectionsForCar,
10
+ queryLocalSegmentTrackStateForCar,
11
+ queryLocalSegmentTrackStatesForCar,
12
+ } from '../track/trackStatePolicy.js';
13
+
14
+ const STRAIGHT_CONNECTOR_CURVATURE_EPSILON = 1e-8;
15
+ const MAIN_TRACK_PATCH_MARGIN = 0.001;
16
+
17
+ function writeFullWheelState(target, patch, sampledStates, trackLimit) {
18
+ const state = worstState(sampledStates);
19
+ let minimumSignedOffset = Infinity;
20
+ let maximumSignedOffset = -Infinity;
21
+ for (let index = 0; index < sampledStates.length; index += 1) {
22
+ const signedOffset = sampledStates[index].signedOffset;
23
+ if (signedOffset < minimumSignedOffset) minimumSignedOffset = signedOffset;
24
+ if (signedOffset > maximumSignedOffset) maximumSignedOffset = signedOffset;
25
+ }
26
+ const outside = wheelFullyOutside(sampledStates, trackLimit);
27
+
28
+ target.id = patch.id;
29
+ target.x = patch.center.x;
30
+ target.y = patch.center.y;
31
+ target.signedOffset = state.signedOffset;
32
+ target.crossTrackError = state.crossTrackError;
33
+ target.surface = state.surface;
34
+ target.onTrack = Boolean(state.onTrack);
35
+ target.inPitLane = Boolean(state.inPitLane);
36
+ target.pitLanePart = state.pitLanePart ?? null;
37
+ target.pitBoxId = state.pitBoxId ?? null;
38
+ target.minimumSignedOffset = minimumSignedOffset;
39
+ target.maximumSignedOffset = maximumSignedOffset;
40
+ target.fullyOutsideWhiteLine = outside.fullyOutsideWhiteLine;
41
+ target.outsideSide = outside.outsideSide;
42
+ target.sampledStates = sampledStates;
43
+ return target;
44
+ }
45
+
46
+ function queryConnectorSampleState(track, car, point, progressHint, centerState) {
47
+ const baseSegmentId = Number.isInteger(centerState?.segmentId) ? centerState.segmentId : null;
48
+ if (baseSegmentId == null) return queryHintedTrackStateForCar(track, car, point, progressHint);
49
+ return queryLocalSegmentTrackStateForCar(
50
+ track,
51
+ car,
52
+ point,
53
+ baseSegmentId,
54
+ progressHint,
55
+ { skipPitOverrideInsideMainRoad: true },
56
+ );
57
+ }
58
+
59
+ function sampleFullConnectorPatchInto(sampledStates, patch, track, car, progressHint, centerState) {
60
+ const corners = ensureOrientedRectCorners(patch);
61
+ sampledStates.length = 1 + corners.length;
62
+ sampledStates[0] = queryConnectorSampleState(track, car, patch.center, progressHint, centerState);
63
+ for (let index = 0; index < corners.length; index += 1) {
64
+ sampledStates[index + 1] = queryConnectorSampleState(track, car, corners[index], progressHint, centerState);
65
+ }
66
+ return sampledStates;
67
+ }
68
+
69
+ function sampleFullNearestPatchInto(sampledStates, patch, track, car) {
70
+ const corners = ensureOrientedRectCorners(patch);
71
+ sampledStates.length = 1 + corners.length;
72
+ sampledStates[0] = nearestTrackStateForCar(track, car, patch.center, car.progress);
73
+ for (let index = 0; index < corners.length; index += 1) {
74
+ sampledStates[index + 1] = nearestTrackStateForCar(track, car, corners[index], car.progress);
75
+ }
76
+ return sampledStates;
77
+ }
78
+
79
+ function writeConnectorWheelState(target, {
80
+ patch,
81
+ wheelCenterState,
82
+ centerState,
83
+ car,
84
+ track,
85
+ trackLimit,
86
+ progressHint,
87
+ sampledStates,
88
+ }) {
89
+ if (!wheelCenterState) {
90
+ return writeAnalyticWheelState(target, patch, centerState, track, trackLimit);
91
+ }
92
+ if (!wheelCenterState.inPitLane) {
93
+ return writeAnalyticWheelState(target, patch, wheelCenterState, track, trackLimit);
94
+ }
95
+ if (pitPatchInsideRoad(patch, wheelCenterState)) {
96
+ return writeAnalyticPitWheelState(target, patch, wheelCenterState);
97
+ }
98
+ sampleFullConnectorPatchInto(sampledStates, patch, track, car, progressHint, centerState);
99
+ return writeFullWheelState(target, patch, sampledStates, trackLimit);
100
+ }
101
+
102
+ function collectPatchCenters(target, patches) {
103
+ target.length = patches.length;
104
+ for (let index = 0; index < patches.length; index += 1) {
105
+ target[index] = patches[index].center;
106
+ }
107
+ return target;
108
+ }
109
+
110
+ export function connectorWheelCenterStates(
111
+ track,
112
+ car,
113
+ patches,
114
+ centerState,
115
+ progressHint,
116
+ scratchStates = null,
117
+ scratchProjections = null,
118
+ scratchPositions = null,
119
+ ) {
120
+ const baseSegmentId = Number.isInteger(centerState?.segmentId) ? centerState.segmentId : null;
121
+ const states = scratchStates ?? new Array(patches.length);
122
+ states.length = patches.length;
123
+ if (baseSegmentId == null) {
124
+ for (let index = 0; index < patches.length; index += 1) {
125
+ states[index] = queryHintedTrackStateForCar(track, car, patches[index].center, progressHint);
126
+ }
127
+ return states;
128
+ }
129
+ const positions = collectPatchCenters(scratchPositions ?? new Array(patches.length), patches);
130
+ return queryLocalSegmentTrackStatesForCar(
131
+ track,
132
+ car,
133
+ positions,
134
+ baseSegmentId,
135
+ progressHint,
136
+ {
137
+ radius: 2,
138
+ skipPitOverrideInsideMainRoad: true,
139
+ target: states,
140
+ projections: scratchProjections,
141
+ },
142
+ );
143
+ }
144
+
145
+ export function connectorWheelCenterProjections(
146
+ track,
147
+ car,
148
+ patches,
149
+ centerState,
150
+ progressHint,
151
+ scratchProjections = null,
152
+ scratchPositions = null,
153
+ ) {
154
+ const baseSegmentId = Number.isInteger(centerState?.segmentId) ? centerState.segmentId : null;
155
+ if (baseSegmentId == null) return null;
156
+ const positions = collectPatchCenters(scratchPositions ?? new Array(patches.length), patches);
157
+ return queryLocalSegmentTrackProjectionsForCar(
158
+ track,
159
+ car,
160
+ positions,
161
+ baseSegmentId,
162
+ progressHint,
163
+ {
164
+ radius: 2,
165
+ projections: scratchProjections,
166
+ },
167
+ );
168
+ }
169
+
170
+ export function canUseStraightConnectorMainTrackAnalytic(track, geometry, centerState, trackLimit) {
171
+ if (
172
+ !track ||
173
+ !geometry?.contactPatches?.length ||
174
+ !centerState ||
175
+ centerState.inPitLane ||
176
+ !centerState.onTrack ||
177
+ Math.abs(centerState.curvature ?? 0) > STRAIGHT_CONNECTOR_CURVATURE_EPSILON
178
+ ) {
179
+ return false;
180
+ }
181
+
182
+ for (let index = 0; index < geometry.contactPatches.length; index += 1) {
183
+ const patch = geometry.contactPatches[index];
184
+ const wheelCenterOffset =
185
+ (patch.center.x - centerState.x) * centerState.normalX +
186
+ (patch.center.y - centerState.y) * centerState.normalY;
187
+ const projectedHalfWidth = analyticWheelProjectedHalfWidth(patch, centerState);
188
+ if (
189
+ wheelCenterOffset - projectedHalfWidth < -trackLimit - MAIN_TRACK_PATCH_MARGIN ||
190
+ wheelCenterOffset + projectedHalfWidth > trackLimit + MAIN_TRACK_PATCH_MARGIN
191
+ ) {
192
+ return false;
193
+ }
194
+ }
195
+ return true;
196
+ }
197
+
198
+ export function writeConnectorWheelStatesWithSummary({
199
+ targetWheels,
200
+ patches,
201
+ wheelCenterStates,
202
+ centerState,
203
+ car,
204
+ track,
205
+ trackLimit,
206
+ progressHint,
207
+ sampledStatePools,
208
+ summaryTarget = null,
209
+ }) {
210
+ const summary = resetWheelSummary(summaryTarget ?? {});
211
+ for (let index = 0; index < patches.length; index += 1) {
212
+ const sampledStates = sampledStatePools[index] ?? [];
213
+ sampledStatePools[index] = sampledStates;
214
+ const wheel = writeConnectorWheelState(targetWheels[index], {
215
+ patch: patches[index],
216
+ wheelCenterState: wheelCenterStates[index],
217
+ centerState,
218
+ car,
219
+ track,
220
+ trackLimit,
221
+ progressHint,
222
+ sampledStates,
223
+ });
224
+ recordWheelSummary(summary, wheel);
225
+ }
226
+ finishWheelSummary(summary, track, trackLimit);
227
+
228
+ return {
229
+ wheels: targetWheels,
230
+ effectiveSurface: summary.effectiveSurface,
231
+ representative: summary.representative,
232
+ trackLimits: summary.trackLimits,
233
+ allOnTrackOrPit: summary.allOnTrackOrPit,
234
+ allInPitLane: summary.allInPitLane,
235
+ };
236
+ }
237
+
238
+ export function writeNearestSampledWheelStatesWithSummary({
239
+ targetWheels,
240
+ patches,
241
+ car,
242
+ track,
243
+ trackLimit,
244
+ sampledStatePools,
245
+ summaryTarget = null,
246
+ }) {
247
+ const summary = resetWheelSummary(summaryTarget ?? {});
248
+ for (let index = 0; index < patches.length; index += 1) {
249
+ const patch = patches[index];
250
+ const sampledStates = sampledStatePools[index] ?? [];
251
+ sampledStatePools[index] = sampledStates;
252
+ sampleFullNearestPatchInto(sampledStates, patch, track, car);
253
+ const wheel = writeFullWheelState(targetWheels[index], patch, sampledStates, trackLimit);
254
+ recordWheelSummary(summary, wheel);
255
+ }
256
+ finishWheelSummary(summary, track, trackLimit);
257
+
258
+ return {
259
+ wheels: targetWheels,
260
+ effectiveSurface: summary.effectiveSurface,
261
+ representative: summary.representative,
262
+ trackLimits: summary.trackLimits,
263
+ allOnTrackOrPit: summary.allOnTrackOrPit,
264
+ allInPitLane: summary.allInPitLane,
265
+ };
266
+ }