@inventure71/paddockjs 1.0.0 → 2.0.1

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 (203) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/INSTALL_AND_UPDATE.md +4 -3
  3. package/README.md +219 -12
  4. package/docs/architecture.md +126 -45
  5. package/docs/concepts.md +9 -5
  6. package/docs/custom_model_controller.md +329 -0
  7. package/docs/data_contract.md +317 -22
  8. package/docs/data_model.md +1 -1
  9. package/docs/index.md +10 -0
  10. package/docs/learnings.md +22 -0
  11. package/docs/rules.md +71 -14
  12. package/docs/sense_contract.md +59 -0
  13. package/docs/system_specs.md +75 -13
  14. package/docs/training.md +280 -32
  15. package/examples/python/README.md +62 -0
  16. package/examples/python/base_policy_server.py +191 -0
  17. package/examples/python/requirements.txt +2 -0
  18. package/package.json +12 -4
  19. package/src/api/PaddockSimulatorController.js +6 -33
  20. package/src/app/BrowserExpertAdapter.js +76 -9
  21. package/src/app/BrowserExpertExternalRenderer.js +303 -0
  22. package/src/app/F1SimulatorApp.js +291 -1630
  23. package/src/app/banners/bannerFormatters.js +55 -0
  24. package/src/app/banners/raceDataBannerController.js +141 -0
  25. package/src/app/banners/stewardMessageController.js +99 -0
  26. package/src/app/camera/cameraConstants.js +21 -0
  27. package/src/app/camera/cameraController.js +265 -0
  28. package/src/app/camera/cameraGeometry.js +176 -0
  29. package/src/app/domBindings.js +4 -0
  30. package/src/app/readouts/carOverviewRenderer.js +109 -0
  31. package/src/app/readouts/controlStateRenderer.js +37 -0
  32. package/src/app/readouts/raceStatusRenderer.js +133 -0
  33. package/src/app/readouts/readoutFormatters.js +58 -0
  34. package/src/app/readouts/telemetryRenderer.js +113 -0
  35. package/src/app/readouts/timingTowerRenderer.js +177 -0
  36. package/src/app/rendering/appAssets.js +38 -0
  37. package/src/app/rendering/carRenderer.js +286 -0
  38. package/src/app/rendering/displayUtils.js +22 -0
  39. package/src/app/rendering/drsTrailRenderer.js +90 -0
  40. package/src/app/rendering/expertSensorRenderer.js +126 -0
  41. package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
  42. package/src/app/rendering/replayGhostRenderer.js +124 -0
  43. package/src/app/rendering/trackRenderer.js +25 -0
  44. package/src/app/runtime/frameLoop.js +66 -0
  45. package/src/app/runtime/runtimeTiming.js +34 -0
  46. package/src/app/runtime/runtimeVisibility.js +45 -0
  47. package/src/config/defaultOptions.js +20 -0
  48. package/src/config/restartOptions.js +34 -0
  49. package/src/environment/actions.js +13 -5
  50. package/src/environment/controllerLoop.js +261 -0
  51. package/src/environment/episode.js +98 -2
  52. package/src/environment/evaluation.js +54 -32
  53. package/src/environment/index.d.ts +376 -10
  54. package/src/environment/index.js +1 -0
  55. package/src/environment/metrics.js +159 -0
  56. package/src/environment/observationOptions.js +22 -1
  57. package/src/environment/observationVector.js +200 -0
  58. package/src/environment/observations.js +199 -141
  59. package/src/environment/options.js +37 -5
  60. package/src/environment/recorder.js +13 -6
  61. package/src/environment/rewards.js +13 -2
  62. package/src/environment/runtime.js +272 -23
  63. package/src/environment/scenarios.js +57 -9
  64. package/src/environment/sensors/bodySenses.js +23 -0
  65. package/src/environment/sensors/boundarySenses.js +17 -0
  66. package/src/environment/sensors/carRays.js +75 -0
  67. package/src/environment/sensors/contactSenses.js +32 -0
  68. package/src/environment/sensors/index.js +157 -0
  69. package/src/environment/sensors/indexedRayBands.js +103 -0
  70. package/src/environment/sensors/nearbyCars.js +52 -0
  71. package/src/environment/sensors/opponentRadar.js +75 -0
  72. package/src/environment/sensors/rayConfig.js +110 -0
  73. package/src/environment/sensors/rayDefaults.js +6 -0
  74. package/src/environment/sensors/rayGeometry.js +60 -0
  75. package/src/environment/sensors/rayGuards.js +19 -0
  76. package/src/environment/sensors/sensorTargets.js +66 -0
  77. package/src/environment/sensors/surfaceRays.js +360 -0
  78. package/src/environment/sensors/trackRays.js +424 -0
  79. package/src/environment/sensors.js +1 -367
  80. package/src/environment/specs.js +159 -26
  81. package/src/environment/workerProtocol.js +13 -1
  82. package/src/index.d.ts +171 -6
  83. package/src/index.js +6 -32
  84. package/src/rendering/proceduralTrackAsset.js +20 -823
  85. package/src/rendering/renderSnapshot.js +38 -12
  86. package/src/rendering/track/finishLineRenderer.js +34 -0
  87. package/src/rendering/track/gridRenderer.js +47 -0
  88. package/src/rendering/track/offsetStrokeSafety.js +136 -0
  89. package/src/rendering/track/pitLaneRenderer.js +248 -0
  90. package/src/rendering/track/trackMaterialRenderer.js +326 -0
  91. package/src/rendering/track/trackRenderConstants.js +38 -0
  92. package/src/rendering/track/trackRenderGeometry.js +94 -0
  93. package/src/simulation/collisionGeometry.js +1 -1
  94. package/src/simulation/driver/driverControlConstants.js +28 -0
  95. package/src/simulation/driver/driverController.js +47 -0
  96. package/src/simulation/driver/driverInput.js +28 -0
  97. package/src/simulation/driver/driverMath.js +6 -0
  98. package/src/simulation/driver/driverPersonality.js +25 -0
  99. package/src/simulation/driver/edgeRecovery.js +48 -0
  100. package/src/simulation/driver/racingControls.js +399 -0
  101. package/src/simulation/driver/racingLinePlan.js +166 -0
  102. package/src/simulation/driver/recoveryDynamics.js +38 -0
  103. package/src/simulation/driver/rejoinControls.js +213 -0
  104. package/src/simulation/driver/safetyCarControls.js +35 -0
  105. package/src/simulation/driver/trafficScan.js +51 -0
  106. package/src/simulation/driverController.js +4 -619
  107. package/src/simulation/participants/participantInteractions.js +120 -0
  108. package/src/simulation/pit/pitFlow.js +239 -0
  109. package/src/simulation/pit/pitIntent.js +92 -0
  110. package/src/simulation/pit/pitKinematics.js +24 -0
  111. package/src/simulation/pit/pitOccupancy.js +47 -0
  112. package/src/simulation/pit/pitPenaltyService.js +71 -0
  113. package/src/simulation/pit/pitQueue.js +60 -0
  114. package/src/simulation/pit/pitRouteMovement.js +167 -0
  115. package/src/simulation/pit/pitRouting.js +319 -0
  116. package/src/simulation/pit/pitService.js +43 -0
  117. package/src/simulation/pit/pitServiceConstants.js +21 -0
  118. package/src/simulation/pit/pitServiceProfile.js +45 -0
  119. package/src/simulation/pit/pitSnapshots.js +61 -0
  120. package/src/simulation/pit/pitState.js +113 -0
  121. package/src/simulation/pit/pitTireService.js +94 -0
  122. package/src/simulation/race/classification.js +104 -0
  123. package/src/simulation/race/gridPenalties.js +37 -0
  124. package/src/simulation/race/raceControlState.js +38 -0
  125. package/src/simulation/race/raceDistance.js +32 -0
  126. package/src/simulation/race/raceFinish.js +65 -0
  127. package/src/simulation/race/raceLifecycle.js +67 -0
  128. package/src/simulation/race/raceOrder.js +113 -0
  129. package/src/simulation/race/raceProgress.js +129 -0
  130. package/src/simulation/race/raceSetup.js +93 -0
  131. package/src/simulation/race/raceStep.js +80 -0
  132. package/src/simulation/race/redFlag.js +15 -0
  133. package/src/simulation/race/retirements.js +44 -0
  134. package/src/simulation/race/safetyCar.js +92 -0
  135. package/src/simulation/race/stalledDnf.js +77 -0
  136. package/src/simulation/raceSimulation.js +231 -2817
  137. package/src/simulation/replay/replayGhosts.js +128 -0
  138. package/src/simulation/rules/moduleConfig.js +72 -0
  139. package/src/simulation/rules/penaltyConfig.js +99 -0
  140. package/src/simulation/rules/penaltyEvents.js +18 -0
  141. package/src/simulation/rules/penaltyStats.js +26 -0
  142. package/src/simulation/rules/ruleConfigMerge.js +41 -0
  143. package/src/simulation/rules/ruleDefaults.js +85 -0
  144. package/src/simulation/rules/rulesReview.js +144 -0
  145. package/src/simulation/rules/rulesetPresets.js +47 -0
  146. package/src/simulation/rules/trackLimitsSteward.js +1 -1
  147. package/src/simulation/rulesConfig.js +5 -323
  148. package/src/simulation/snapshots/raceSnapshots.js +193 -0
  149. package/src/simulation/timing/drsTiming.js +87 -0
  150. package/src/simulation/timing/gapEstimation.js +38 -0
  151. package/src/simulation/timing/lapTelemetry.js +182 -0
  152. package/src/simulation/timing/raceTiming.js +15 -0
  153. package/src/simulation/timing/sectorPerformance.js +60 -0
  154. package/src/simulation/timing/timingConstants.js +5 -0
  155. package/src/simulation/timing/timingHistory.js +46 -0
  156. package/src/simulation/timing/timingLines.js +79 -0
  157. package/src/simulation/track/centerlineSpline.js +59 -0
  158. package/src/simulation/track/drsZones.js +67 -0
  159. package/src/simulation/track/pitLaneAccess.js +103 -0
  160. package/src/simulation/track/pitLaneLayout.js +395 -0
  161. package/src/simulation/track/pitLaneState.js +160 -0
  162. package/src/simulation/track/proceduralCenterline.js +237 -0
  163. package/src/simulation/track/sampleGeometry.js +49 -0
  164. package/src/simulation/track/spatialQueries.js +154 -0
  165. package/src/simulation/track/startStraight.js +141 -0
  166. package/src/simulation/track/trackConstants.js +109 -0
  167. package/src/simulation/track/trackGenerationOptions.js +283 -0
  168. package/src/simulation/track/trackMath.js +241 -0
  169. package/src/simulation/track/trackModel.js +240 -0
  170. package/src/simulation/track/trackQueryGrid.js +94 -0
  171. package/src/simulation/track/trackQueryIndex.js +659 -0
  172. package/src/simulation/track/trackQueryPitIndex.js +140 -0
  173. package/src/simulation/track/trackQueryScratch.js +46 -0
  174. package/src/simulation/track/trackQueryStats.js +28 -0
  175. package/src/simulation/track/trackSectors.js +19 -0
  176. package/src/simulation/track/trackStatePolicy.js +24 -0
  177. package/src/simulation/track/trackValidation.js +98 -0
  178. package/src/simulation/trackModel.js +1 -1706
  179. package/src/simulation/vehicle/contactResolution.js +181 -0
  180. package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
  181. package/src/simulation/vehicle/pitWheelSurface.js +69 -0
  182. package/src/simulation/vehicle/runoffResponse.js +87 -0
  183. package/src/simulation/vehicle/surfacePriority.js +28 -0
  184. package/src/simulation/vehicle/vehicleGeometry.js +247 -0
  185. package/src/simulation/vehicle/vehicleKinematics.js +37 -0
  186. package/src/simulation/vehicle/vehiclePhysics.js +364 -0
  187. package/src/simulation/vehicle/vehicleSnapshots.js +327 -0
  188. package/src/simulation/vehicle/vehicleState.js +220 -0
  189. package/src/simulation/vehicle/wheelSurface.js +152 -0
  190. package/src/simulation/vehicleGeometry.js +1 -247
  191. package/src/simulation/vehiclePhysics.js +1 -136
  192. package/src/simulation/warmup/runtimeWarmup.js +224 -0
  193. package/src/simulation/wheelSurface.js +1 -338
  194. package/src/styles.css +42 -0
  195. package/src/ui/cameraControlsTemplate.js +20 -0
  196. package/src/ui/carOverviewTemplate.js +33 -0
  197. package/src/ui/componentTemplates.js +14 -360
  198. package/src/ui/raceCanvasTemplate.js +69 -0
  199. package/src/ui/raceControlsTemplate.js +36 -0
  200. package/src/ui/shellTemplate.js +5 -4
  201. package/src/ui/telemetryDrawerTemplate.js +55 -0
  202. package/src/ui/telemetryTemplates.js +136 -0
  203. package/src/ui/timingTowerTemplate.js +34 -0
@@ -0,0 +1,37 @@
1
+ export function freezeVehicleMotion(car, {
2
+ brake = 1,
3
+ clearManualControls = false,
4
+ physicsMode = 'simulator',
5
+ stabilityState = car.stabilityState ?? 'stable',
6
+ } = {}) {
7
+ car.speed = 0;
8
+ if (physicsMode === 'simulator') {
9
+ car.velocityX = 0;
10
+ car.velocityY = 0;
11
+ } else {
12
+ car.velocityX = null;
13
+ car.velocityY = null;
14
+ }
15
+ car.throttle = 0;
16
+ car.brake = brake;
17
+ car.appliedControls = { steering: 0, throttle: 0, brake };
18
+ if (clearManualControls) car.manualControls = null;
19
+ car.steeringAngle = 0;
20
+ car.yawRate = 0;
21
+ car.turnRadius = Infinity;
22
+ car.longitudinalAcceleration = 0;
23
+ car.lateralAcceleration = 0;
24
+ car.longitudinalG = 0;
25
+ car.lateralG = 0;
26
+ car.gripUsage = 0;
27
+ car.slipAngleRadians = 0;
28
+ car.tractionLimited = false;
29
+ car.stabilityState = stabilityState;
30
+ }
31
+
32
+ export function syncVehicleVelocityToHeading(car) {
33
+ const speed = Number.isFinite(car.speed) ? car.speed : 0;
34
+ const heading = Number.isFinite(car.heading) ? car.heading : 0;
35
+ car.velocityX = Math.cos(heading) * speed;
36
+ car.velocityY = Math.sin(heading) * speed;
37
+ }
@@ -0,0 +1,364 @@
1
+ import { clamp, normalizeAngle } from '../simMath.js';
2
+ import {
3
+ REAL_F1_WHEELBASE_METERS,
4
+ SIM_UNITS_PER_METER,
5
+ TOP_SPEED_SIM_UNITS_PER_SECOND,
6
+ metersPerSecondToSimSpeed,
7
+ simUnitsToMeters,
8
+ metersToSimUnits,
9
+ } from '../units.js';
10
+ import { VEHICLE_GEOMETRY, getCarCorners, vehicleAxes } from './vehicleGeometry.js';
11
+
12
+ const G = 9.80665;
13
+
14
+ export const PHYSICS_MODES = ['arcade', 'simulator'];
15
+ export const DEFAULT_PHYSICS_MODE = 'arcade';
16
+
17
+ export const VEHICLE_LIMITS = {
18
+ wheelbase: metersToSimUnits(REAL_F1_WHEELBASE_METERS),
19
+ maxSteer: 0.56,
20
+ steerRate: 2.35,
21
+ maxSpeed: TOP_SPEED_SIM_UNITS_PER_SECOND,
22
+ carLength: VEHICLE_GEOMETRY.visualLength,
23
+ carWidth: VEHICLE_GEOMETRY.visualWidth,
24
+ };
25
+
26
+ const SURFACE_MODEL = {
27
+ track: { grip: 1, drag: 0, rollingResistance: 0 },
28
+ 'pit-entry': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
29
+ 'pit-lane': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
30
+ 'pit-exit': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
31
+ 'pit-box': { grip: 0.94, drag: 0.08, rollingResistance: 0.025 },
32
+ kerb: { grip: 0.92, drag: 0.12, rollingResistance: 0.045 },
33
+ gravel: { grip: 0.43, drag: 4.0, rollingResistance: 0.68 },
34
+ grass: { grip: 0.34, drag: 1.9, rollingResistance: 0.36 },
35
+ barrier: { grip: 0.18, drag: 9, rollingResistance: 1.2 },
36
+ };
37
+ const WHEEL_DRAG_YAW_GAIN = 0.08;
38
+ const MAX_WHEEL_DRAG_YAW_RATE = 0.22;
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
+ export function normalizePhysicsMode(value) {
53
+ return value === 'simulator' ? 'simulator' : DEFAULT_PHYSICS_MODE;
54
+ }
55
+
56
+ function accelerationLimit(speed, surfaceGrip) {
57
+ const speedRatio = clamp(speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
58
+ return SIM_UNITS_PER_METER * surfaceGrip * (17 * (1 - speedRatio ** 2.1) + 1.2);
59
+ }
60
+
61
+ function brakingLimit(speed, surfaceGrip) {
62
+ const speedRatio = clamp(speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
63
+ return SIM_UNITS_PER_METER * surfaceGrip * (20 + speedRatio * 14);
64
+ }
65
+
66
+ export function tirePerformanceFactor(tireEnergy = 100) {
67
+ const normalized = clamp((Number(tireEnergy) || 1) / 100, 0.01, 1);
68
+ return clamp(0.45 + 0.55 * normalized ** 0.72, 0.45, 1);
69
+ }
70
+
71
+ function surfaceResistance(surfaceName, model = SURFACE_MODEL) {
72
+ const surface = model[surfaceName] ?? model.track;
73
+ return surface.drag * 0.14 + surface.rollingResistance * 2.4 + (1 - surface.grip) * 0.65;
74
+ }
75
+
76
+ function sideWheelResistance(wheels, side, model = SURFACE_MODEL) {
77
+ const sideWheels = wheels.filter((wheel) => wheel.id?.endsWith(`-${side}`));
78
+ if (!sideWheels.length) return 0;
79
+ return sideWheels.reduce((total, wheel) => total + surfaceResistance(wheel.surface, model), 0) / sideWheels.length;
80
+ }
81
+
82
+ function wheelDragYawRate(car, model = SURFACE_MODEL) {
83
+ const wheels = Array.isArray(car.wheelStates) ? car.wheelStates : [];
84
+ if (!wheels.length) return 0;
85
+ const leftResistance = sideWheelResistance(wheels, 'left', model);
86
+ const rightResistance = sideWheelResistance(wheels, 'right', model);
87
+ const speedFactor = clamp(car.speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
88
+ return clamp(
89
+ (rightResistance - leftResistance) * speedFactor * WHEEL_DRAG_YAW_GAIN,
90
+ -MAX_WHEEL_DRAG_YAW_RATE,
91
+ MAX_WHEEL_DRAG_YAW_RATE,
92
+ );
93
+ }
94
+
95
+ function setPhysicsTelemetry(car, {
96
+ lateralAcceleration = 0,
97
+ longitudinalAcceleration = 0,
98
+ gripUsage = 0,
99
+ slipAngleRadians = 0,
100
+ tractionLimited = false,
101
+ stabilityState = 'stable',
102
+ } = {}) {
103
+ car.lateralAcceleration = lateralAcceleration;
104
+ car.longitudinalAcceleration = longitudinalAcceleration;
105
+ car.lateralG = lateralAcceleration / G;
106
+ car.longitudinalG = longitudinalAcceleration / G;
107
+ car.gripUsage = Number.isFinite(gripUsage) ? gripUsage : 0;
108
+ car.slipAngleRadians = Number.isFinite(slipAngleRadians) ? slipAngleRadians : 0;
109
+ car.tractionLimited = Boolean(tractionLimited);
110
+ car.stabilityState = stabilityState;
111
+ }
112
+
113
+ export function integrateVehiclePhysics(car, controls, dt, options = {}) {
114
+ if (normalizePhysicsMode(options.physicsMode) === 'simulator') {
115
+ return integrateSimulatorVehiclePhysics(car, controls, dt, options);
116
+ }
117
+ return integrateArcadeVehiclePhysics(car, controls, dt, options);
118
+ }
119
+
120
+ function integrateArcadeVehiclePhysics(car, controls, dt, options = {}) {
121
+ const steeringTarget = clamp(controls.steering ?? 0, -VEHICLE_LIMITS.maxSteer, VEHICLE_LIMITS.maxSteer);
122
+ const steerDelta = clamp(
123
+ steeringTarget - car.steeringAngle,
124
+ -VEHICLE_LIMITS.steerRate * dt,
125
+ VEHICLE_LIMITS.steerRate * dt,
126
+ );
127
+ car.steeringAngle += steerDelta;
128
+
129
+ const throttle = clamp(controls.throttle ?? 0, 0, 1);
130
+ const brake = clamp(controls.brake ?? 0, 0, 1);
131
+ const surface = SURFACE_MODEL[car.trackState?.surface] ?? SURFACE_MODEL.track;
132
+ const tireFactor = tirePerformanceFactor(car.tireEnergy ?? 100);
133
+ const surfaceGrip = surface.grip * tireFactor;
134
+ const dragMultiplier = car.drsActive ? 0.42 : 1;
135
+ const speedRatio = clamp(car.speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
136
+ const engineForce = throttle *
137
+ car.powerNewtons *
138
+ (car.drsActive ? 1.08 : 1) *
139
+ Math.max(0.12, 1 - speedRatio ** 1.15);
140
+ const brakeForce = brake * car.brakeNewtons;
141
+ const speedMetersPerSecondBefore = simUnitsToMeters(car.speed);
142
+ const dragForce = (car.dragCoefficient * dragMultiplier * 0.12 + surface.drag) *
143
+ speedMetersPerSecondBefore * speedMetersPerSecondBefore;
144
+ const rollingForce = surface.rollingResistance * car.mass * G;
145
+ const driveAcceleration = clamp((engineForce / car.mass) * SIM_UNITS_PER_METER, 0, accelerationLimit(car.speed, surfaceGrip));
146
+ const drsAcceleration = car.drsActive ? driveAcceleration * 0.06 : 0;
147
+ const brakeDeceleration = clamp((brakeForce / car.mass) * SIM_UNITS_PER_METER, 0, brakingLimit(car.speed, surfaceGrip));
148
+ const dragDeceleration = ((dragForce + rollingForce) / car.mass) * SIM_UNITS_PER_METER;
149
+ const acceleration = driveAcceleration + drsAcceleration - brakeDeceleration - dragDeceleration;
150
+
151
+ car.speed = clamp(car.speed + acceleration * dt, 0, VEHICLE_LIMITS.maxSpeed);
152
+
153
+ const speedMetersPerSecond = simUnitsToMeters(car.speed);
154
+ const rawYawRate = (speedMetersPerSecond / REAL_F1_WHEELBASE_METERS) *
155
+ Math.tan(car.steeringAngle) *
156
+ tireFactor;
157
+ const downforceGrip = car.downforceCoefficient * speedMetersPerSecond * speedMetersPerSecond / car.mass;
158
+ const tyreConditionGrip = tireFactor;
159
+ const maxYawRate = ((car.tireGrip * tyreConditionGrip * G + downforceGrip) * surfaceGrip) /
160
+ Math.max(speedMetersPerSecond, 1);
161
+ const steeringYawRate = clamp(rawYawRate, -maxYawRate, maxYawRate);
162
+ const requestedWheelDragYawRate = wheelDragYawRate(car);
163
+ car.yawRate = clamp(steeringYawRate + requestedWheelDragYawRate, -maxYawRate, maxYawRate);
164
+ car.wheelDragYawRate = car.yawRate - steeringYawRate;
165
+ car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
166
+ car.heading = normalizeAngle(car.heading + car.yawRate * dt);
167
+ car.x += Math.cos(car.heading) * car.speed * dt;
168
+ car.y += Math.sin(car.heading) * car.speed * dt;
169
+ car.throttle = throttle;
170
+ car.brake = brake;
171
+ car.steerSaturation = rawYawRate === 0 ? 0 : Math.abs(steeringYawRate / rawYawRate);
172
+ setPhysicsTelemetry(car, {
173
+ lateralAcceleration: speedMetersPerSecond * car.yawRate,
174
+ longitudinalAcceleration: acceleration / SIM_UNITS_PER_METER,
175
+ gripUsage: Math.min(1, Math.abs(speedMetersPerSecond * car.yawRate) /
176
+ Math.max((car.tireGrip * tyreConditionGrip * G + downforceGrip) * surfaceGrip, 1)),
177
+ tractionLimited: Math.abs(rawYawRate) > Math.abs(steeringYawRate) + 1e-6,
178
+ stabilityState: Math.abs(rawYawRate) > Math.abs(steeringYawRate) + 1e-6 ? 'understeer' : 'stable',
179
+ });
180
+ if (options.tireDegradationEnabled !== false) {
181
+ const tyreLoad = Math.abs(car.lateralAcceleration) / G;
182
+ const tireCare = clamp(Number(car.tireCare) || 1, 0.45, 1.8);
183
+ const wearRate = (0.035 + tyreLoad * 0.11 + brake * 0.07 + throttle * 0.025) / tireCare;
184
+ car.tireEnergy = clamp((car.tireEnergy ?? 100) - wearRate * dt, 1, 100);
185
+ }
186
+
187
+ return car;
188
+ }
189
+
190
+ function simulatorSurface(surfaceName) {
191
+ return SIMULATOR_SURFACE_MODEL[surfaceName] ?? SIMULATOR_SURFACE_MODEL.track;
192
+ }
193
+
194
+ function aggregateSimulatorSurface(car) {
195
+ const wheels = Array.isArray(car.wheelStates) ? car.wheelStates : [];
196
+ if (!wheels.length) return simulatorSurface(car.trackState?.surface ?? 'track');
197
+ const total = wheels.reduce((sum, wheel) => {
198
+ const surface = simulatorSurface(wheel.surface);
199
+ return {
200
+ grip: sum.grip + surface.grip,
201
+ drag: sum.drag + surface.drag,
202
+ rollingResistance: sum.rollingResistance + surface.rollingResistance,
203
+ scrub: sum.scrub + surface.scrub,
204
+ };
205
+ }, { grip: 0, drag: 0, rollingResistance: 0, scrub: 0 });
206
+ return {
207
+ grip: total.grip / wheels.length,
208
+ drag: total.drag / wheels.length,
209
+ rollingResistance: total.rollingResistance / wheels.length,
210
+ scrub: total.scrub / wheels.length,
211
+ };
212
+ }
213
+
214
+ function simulatorStabilityState({ gripUsage, slipAngle, surfaceName, brake, throttle }) {
215
+ if (gripUsage > 1.32 || Math.abs(slipAngle) > 0.42) return 'spin-risk';
216
+ if (gripUsage > 1.02) return brake > 0.2 && throttle < 0.2 ? 'oversteer' : 'understeer';
217
+ if (surfaceName === 'kerb' && gripUsage > 0.72) return 'oversteer';
218
+ if ((surfaceName === 'gravel' || surfaceName === 'grass') && gripUsage > 0.5) return 'understeer';
219
+ return 'stable';
220
+ }
221
+
222
+ function ensureSimulatorVelocity(car) {
223
+ if (Number.isFinite(car.velocityX) && Number.isFinite(car.velocityY)) return;
224
+ const speed = clamp(Number(car.speed) || 0, 0, VEHICLE_LIMITS.maxSpeed);
225
+ car.velocityX = Math.cos(car.heading ?? 0) * speed;
226
+ car.velocityY = Math.sin(car.heading ?? 0) * speed;
227
+ }
228
+
229
+ function limitSimulatorSpeed(car) {
230
+ const speed = Math.hypot(car.velocityX, car.velocityY);
231
+ if (speed <= VEHICLE_LIMITS.maxSpeed || speed <= 0) return speed;
232
+ const scale = VEHICLE_LIMITS.maxSpeed / speed;
233
+ car.velocityX *= scale;
234
+ car.velocityY *= scale;
235
+ return VEHICLE_LIMITS.maxSpeed;
236
+ }
237
+
238
+ function integrateSimulatorVehiclePhysics(car, controls, dt, options = {}) {
239
+ ensureSimulatorVelocity(car);
240
+ const steeringTarget = clamp(controls.steering ?? 0, -VEHICLE_LIMITS.maxSteer, VEHICLE_LIMITS.maxSteer);
241
+ const steerDelta = clamp(
242
+ steeringTarget - car.steeringAngle,
243
+ -VEHICLE_LIMITS.steerRate * dt,
244
+ VEHICLE_LIMITS.steerRate * dt,
245
+ );
246
+ car.steeringAngle += steerDelta;
247
+
248
+ const throttle = clamp(controls.throttle ?? 0, 0, 1);
249
+ const brake = clamp(controls.brake ?? 0, 0, 1);
250
+ const surfaceName = car.trackState?.surface ?? 'track';
251
+ const surface = aggregateSimulatorSurface(car);
252
+ const tireFactor = tirePerformanceFactor(car.tireEnergy ?? 100);
253
+ const surfaceGrip = surface.grip * tireFactor;
254
+ const speedBefore = Math.hypot(car.velocityX, car.velocityY);
255
+ const speedBeforeMps = simUnitsToMeters(speedBefore);
256
+ const speedRatio = clamp(speedBefore / VEHICLE_LIMITS.maxSpeed, 0, 1);
257
+ const downforceGrip = car.downforceCoefficient * speedBeforeMps * speedBeforeMps / car.mass;
258
+ const totalGripAcceleration = Math.max(0.1, (car.tireGrip * tireFactor * G + downforceGrip) * surfaceGrip);
259
+ const axes = vehicleAxes(car.heading ?? 0);
260
+ const forwardVelocity = car.velocityX * axes.forward.x + car.velocityY * axes.forward.y;
261
+ const lateralVelocity = car.velocityX * axes.right.x + car.velocityY * axes.right.y;
262
+ const forwardSpeedMps = simUnitsToMeters(forwardVelocity);
263
+ const lateralSpeedMps = simUnitsToMeters(lateralVelocity);
264
+ const currentSlipAngle = Math.atan2(lateralSpeedMps, Math.max(Math.abs(forwardSpeedMps), 0.1));
265
+ const engineForce = throttle *
266
+ car.powerNewtons *
267
+ (car.drsActive ? 1.07 : 1) *
268
+ Math.max(0.08, 1 - speedRatio ** 1.22);
269
+ const brakeForce = brake * car.brakeNewtons;
270
+ const driveAcceleration = Math.min(
271
+ engineForce / car.mass,
272
+ accelerationLimit(car.speed, surfaceGrip) / SIM_UNITS_PER_METER,
273
+ );
274
+ const brakeDeceleration = Math.min(
275
+ brakeForce / car.mass,
276
+ brakingLimit(car.speed, surfaceGrip) / SIM_UNITS_PER_METER,
277
+ );
278
+ const dragMultiplier = car.drsActive ? 0.46 : 1;
279
+ const dragAcceleration = (
280
+ (car.dragCoefficient * dragMultiplier * 0.28 + surface.drag * 0.018) *
281
+ speedBeforeMps * speedBeforeMps
282
+ ) / car.mass;
283
+ const rollingAcceleration = surface.rollingResistance * G;
284
+ const normalizedSteer = Math.abs(car.steeringAngle) / VEHICLE_LIMITS.maxSteer;
285
+ const speedSensitiveSteer = 1 / (1 + (Math.abs(forwardSpeedMps) / 58) ** 1.85 * 2.35);
286
+ const effectiveSteeringAngle = car.steeringAngle * clamp(speedSensitiveSteer, 0.16, 1);
287
+ const rawYawRate = Math.abs(forwardSpeedMps) <= 0.05
288
+ ? 0
289
+ : (forwardSpeedMps / REAL_F1_WHEELBASE_METERS) * Math.tan(effectiveSteeringAngle);
290
+ const sideSlipDamping = 4.2 + surfaceGrip * 2.1 + speedRatio * 1.6;
291
+ const desiredLateralAcceleration = forwardSpeedMps * rawYawRate - lateralSpeedMps * sideSlipDamping;
292
+ const longitudinalDemand = Math.max(driveAcceleration, brakeDeceleration);
293
+ const longitudinalUsage = clamp(longitudinalDemand / Math.max(totalGripAcceleration * 0.52, 1), 0, 0.94);
294
+ const lateralCapacity = totalGripAcceleration * Math.sqrt(Math.max(0.08, 1 - longitudinalUsage ** 2));
295
+ const desiredLateralAbs = Math.abs(desiredLateralAcceleration);
296
+ const lateralUsage = desiredLateralAbs / Math.max(lateralCapacity, 0.1);
297
+ const gripUsage = Math.sqrt(lateralUsage ** 2 + longitudinalUsage ** 2);
298
+ const tractionLimited = gripUsage > 1 || longitudinalUsage > 0.9;
299
+ const actualLateralAcceleration = clamp(desiredLateralAcceleration, -lateralCapacity, lateralCapacity);
300
+ const steeringYawRate = Math.abs(forwardSpeedMps) <= 0.05 ? 0 : actualLateralAcceleration / Math.max(Math.abs(forwardSpeedMps), 0.1);
301
+ const steeringScrubAcceleration = normalizedSteer ** 1.55 *
302
+ speedRatio ** 1.05 *
303
+ (42 + surface.scrub + Math.max(0, gripUsage - 0.55) * 18);
304
+ const tractionPowerScale = tractionLimited
305
+ ? clamp(1 - (gripUsage - 1) * 0.55 - longitudinalUsage * 0.18, 0.12, 1)
306
+ : 1;
307
+ const brakingDirection = forwardSpeedMps >= -0.2 ? 1 : -1;
308
+ const scrubDirection = forwardSpeedMps >= 0 ? 1 : -1;
309
+ const longitudinalAcceleration =
310
+ driveAcceleration * tractionPowerScale -
311
+ brakeDeceleration * brakingDirection -
312
+ dragAcceleration * Math.sign(forwardSpeedMps || 1) -
313
+ rollingAcceleration * Math.sign(forwardSpeedMps || 1) -
314
+ steeringScrubAcceleration * scrubDirection;
315
+
316
+ const requestedWheelDragYawRate = wheelDragYawRate(car, SIMULATOR_SURFACE_MODEL) * (surfaceName === 'kerb' ? 1.6 : 1);
317
+ const maxYawRate = speedBeforeMps <= 0.05 ? 0 : totalGripAcceleration / Math.max(speedBeforeMps, 0.1);
318
+ const slipSteeringAuthority = clamp(1 - Math.abs(currentSlipAngle) * 1.8, 0.35, 1);
319
+ const yawTarget = clamp(steeringYawRate * slipSteeringAuthority + requestedWheelDragYawRate, -maxYawRate, maxYawRate);
320
+ const yawResponse = 1.6 + surfaceGrip * 0.9 + speedRatio * 0.5;
321
+ const yawAccelerationCap = speedRatio < 0.18 ? 6.2 : 3.2;
322
+ const maxYawDelta = Math.min(Math.max(0.08, maxYawRate * yawResponse), yawAccelerationCap) * dt;
323
+ car.yawRate = clamp(
324
+ (car.yawRate ?? 0) + clamp(yawTarget - (car.yawRate ?? 0), -maxYawDelta, maxYawDelta),
325
+ -maxYawRate,
326
+ maxYawRate,
327
+ );
328
+ car.wheelDragYawRate = requestedWheelDragYawRate;
329
+ car.heading = normalizeAngle(car.heading + car.yawRate * dt);
330
+
331
+ const accelerationX = axes.forward.x * longitudinalAcceleration + axes.right.x * actualLateralAcceleration;
332
+ const accelerationY = axes.forward.y * longitudinalAcceleration + axes.right.y * actualLateralAcceleration;
333
+ car.velocityX += metersToSimUnits(accelerationX) * dt;
334
+ car.velocityY += metersToSimUnits(accelerationY) * dt;
335
+ car.speed = limitSimulatorSpeed(car);
336
+ car.x += car.velocityX * dt;
337
+ car.y += car.velocityY * dt;
338
+ car.throttle = throttle;
339
+ car.brake = brake;
340
+ car.steerSaturation = rawYawRate === 0 ? 0 : Math.abs(steeringYawRate / rawYawRate);
341
+ car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
342
+ const velocityHeading = car.speed <= 0.01 ? car.heading : Math.atan2(car.velocityY, car.velocityX);
343
+ const slipAngle = normalizeAngle(velocityHeading - car.heading);
344
+ setPhysicsTelemetry(car, {
345
+ lateralAcceleration: actualLateralAcceleration,
346
+ longitudinalAcceleration,
347
+ gripUsage,
348
+ slipAngleRadians: slipAngle,
349
+ tractionLimited,
350
+ stabilityState: simulatorStabilityState({ gripUsage, slipAngle, surfaceName, brake, throttle }),
351
+ });
352
+
353
+ if (options.tireDegradationEnabled !== false) {
354
+ const tyreLoad = Math.abs(car.lateralAcceleration) / G;
355
+ const tireCare = clamp(Number(car.tireCare) || 1, 0.45, 1.8);
356
+ const heatPenalty = Math.max(0, gripUsage - 0.72) * 0.24 + normalizedSteer * speedRatio * 0.08;
357
+ const wearRate = (0.045 + tyreLoad * 0.16 + brake * 0.09 + throttle * 0.035 + heatPenalty) / tireCare;
358
+ car.tireEnergy = clamp((car.tireEnergy ?? 100) - wearRate * dt, 1, 100);
359
+ }
360
+
361
+ return car;
362
+ }
363
+
364
+ export { getCarCorners };