@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
@@ -1,247 +1 @@
1
- import { normalizeAngle } from './simMath.js';
2
- import {
3
- REAL_F1_CAR_LENGTH_METERS,
4
- REAL_F1_CAR_WIDTH_METERS,
5
- REAL_F1_WHEELBASE_METERS,
6
- metersToSimUnits,
7
- } from './units.js';
8
-
9
- export const VEHICLE_GEOMETRY = {
10
- visualLength: metersToSimUnits(REAL_F1_CAR_LENGTH_METERS),
11
- visualWidth: metersToSimUnits(REAL_F1_CAR_WIDTH_METERS),
12
- bodyLength: metersToSimUnits(4.65),
13
- bodyWidth: metersToSimUnits(1.24),
14
- wheelLength: metersToSimUnits(0.72),
15
- wheelWidth: metersToSimUnits(0.38),
16
- wheelLongitudinalOffset: metersToSimUnits(REAL_F1_WHEELBASE_METERS / 2),
17
- wheelLateralOffset: metersToSimUnits((REAL_F1_CAR_WIDTH_METERS - 0.38) / 2),
18
- };
19
-
20
- const WHEEL_SPECS = [
21
- { id: 'front-left', longitudinal: 1, lateral: -1 },
22
- { id: 'front-right', longitudinal: 1, lateral: 1 },
23
- { id: 'rear-left', longitudinal: -1, lateral: -1 },
24
- { id: 'rear-right', longitudinal: -1, lateral: 1 },
25
- ];
26
-
27
- function finiteOr(value, fallback) {
28
- return Number.isFinite(value) ? value : fallback;
29
- }
30
-
31
- function vehiclePose(car, { previous = false } = {}) {
32
- const x = previous ? finiteOr(car.previousX, car.x) : car.x;
33
- const y = previous ? finiteOr(car.previousY, car.y) : car.y;
34
- const heading = previous ? finiteOr(car.previousHeading, car.heading) : car.heading;
35
-
36
- return {
37
- x: finiteOr(x, 0),
38
- y: finiteOr(y, 0),
39
- heading: normalizeAngle(finiteOr(heading, 0)),
40
- };
41
- }
42
-
43
- export function vehicleAxes(heading) {
44
- const cos = Math.cos(heading);
45
- const sin = Math.sin(heading);
46
- return {
47
- forward: { x: cos, y: sin },
48
- right: { x: -sin, y: cos },
49
- };
50
- }
51
-
52
- function offsetPoint(center, axes, longitudinal, lateral) {
53
- return {
54
- x: center.x + axes.forward.x * longitudinal + axes.right.x * lateral,
55
- y: center.y + axes.forward.y * longitudinal + axes.right.y * lateral,
56
- };
57
- }
58
-
59
- export function createOrientedRect({ id, type, center, heading, length, width }) {
60
- const axes = vehicleAxes(heading);
61
- const halfLength = length / 2;
62
- const halfWidth = width / 2;
63
- const corners = [
64
- offsetPoint(center, axes, halfLength, halfWidth),
65
- offsetPoint(center, axes, halfLength, -halfWidth),
66
- offsetPoint(center, axes, -halfLength, -halfWidth),
67
- offsetPoint(center, axes, -halfLength, halfWidth),
68
- ];
69
-
70
- return {
71
- id,
72
- type,
73
- center,
74
- heading,
75
- length,
76
- width,
77
- halfLength,
78
- halfWidth,
79
- forward: axes.forward,
80
- right: axes.right,
81
- corners,
82
- };
83
- }
84
-
85
- export function createVehicleGeometry(car, options = {}) {
86
- const pose = vehiclePose(car, options);
87
- const axes = vehicleAxes(pose.heading);
88
- const center = { x: pose.x, y: pose.y };
89
- const body = createOrientedRect({
90
- id: 'body',
91
- type: 'body',
92
- center,
93
- heading: pose.heading,
94
- length: VEHICLE_GEOMETRY.bodyLength,
95
- width: VEHICLE_GEOMETRY.bodyWidth,
96
- });
97
- const wheels = WHEEL_SPECS.map((spec) => {
98
- const wheelCenter = offsetPoint(
99
- center,
100
- axes,
101
- spec.longitudinal * VEHICLE_GEOMETRY.wheelLongitudinalOffset,
102
- spec.lateral * VEHICLE_GEOMETRY.wheelLateralOffset,
103
- );
104
- return createOrientedRect({
105
- id: spec.id,
106
- type: 'wheel',
107
- center: wheelCenter,
108
- heading: pose.heading,
109
- length: VEHICLE_GEOMETRY.wheelLength,
110
- width: VEHICLE_GEOMETRY.wheelWidth,
111
- });
112
- });
113
-
114
- return {
115
- body,
116
- wheels,
117
- contactPatches: wheels,
118
- shapes: [body, ...wheels],
119
- };
120
- }
121
-
122
- function geometryPoseSignature(pose) {
123
- return [
124
- pose.x,
125
- pose.y,
126
- pose.heading,
127
- pose.previousX,
128
- pose.previousY,
129
- pose.previousHeading,
130
- ].join(':');
131
- }
132
-
133
- function geometryPose(car) {
134
- const x = finiteOr(car.x, 0);
135
- const y = finiteOr(car.y, 0);
136
- const heading = normalizeAngle(finiteOr(car.heading, 0));
137
- return {
138
- x,
139
- y,
140
- heading,
141
- previousX: finiteOr(car.previousX, x),
142
- previousY: finiteOr(car.previousY, y),
143
- previousHeading: normalizeAngle(finiteOr(car.previousHeading, heading)),
144
- };
145
- }
146
-
147
- function geometryStateMatches(car, state) {
148
- const pose = state?.pose;
149
- if (!pose) return false;
150
- const x = finiteOr(car.x, 0);
151
- const y = finiteOr(car.y, 0);
152
- const heading = normalizeAngle(finiteOr(car.heading, 0));
153
- return (
154
- pose.x === x &&
155
- pose.y === y &&
156
- pose.heading === heading &&
157
- pose.previousX === finiteOr(car.previousX, x) &&
158
- pose.previousY === finiteOr(car.previousY, y) &&
159
- pose.previousHeading === normalizeAngle(finiteOr(car.previousHeading, heading))
160
- );
161
- }
162
-
163
- export function createVehicleShapeAabb(shape) {
164
- let minX = Infinity;
165
- let maxX = -Infinity;
166
- let minY = Infinity;
167
- let maxY = -Infinity;
168
- for (let index = 0; index < shape.corners.length; index += 1) {
169
- const corner = shape.corners[index];
170
- if (corner.x < minX) minX = corner.x;
171
- if (corner.x > maxX) maxX = corner.x;
172
- if (corner.y < minY) minY = corner.y;
173
- if (corner.y > maxY) maxY = corner.y;
174
- }
175
- return {
176
- minX,
177
- maxX,
178
- minY,
179
- maxY,
180
- };
181
- }
182
-
183
- export function mergeAabbs(aabbs) {
184
- let minX = Infinity;
185
- let maxX = -Infinity;
186
- let minY = Infinity;
187
- let maxY = -Infinity;
188
- for (let index = 0; index < aabbs.length; index += 1) {
189
- const aabb = aabbs[index];
190
- if (aabb.minX < minX) minX = aabb.minX;
191
- if (aabb.maxX > maxX) maxX = aabb.maxX;
192
- if (aabb.minY < minY) minY = aabb.minY;
193
- if (aabb.maxY > maxY) maxY = aabb.maxY;
194
- }
195
- return { minX, maxX, minY, maxY };
196
- }
197
-
198
- export function createVehicleGeometryState(car) {
199
- const pose = geometryPose(car);
200
- const current = createVehicleGeometry(car);
201
- const previous = createVehicleGeometry(car, { previous: true });
202
- const bodyAabb = createVehicleShapeAabb(current.body);
203
- const previousBodyAabb = createVehicleShapeAabb(previous.body);
204
- const sweptBodyAabb = mergeAabbs([previousBodyAabb, bodyAabb]);
205
-
206
- return {
207
- pose,
208
- signature: geometryPoseSignature(pose),
209
- current,
210
- previous,
211
- body: current.body,
212
- wheels: current.wheels,
213
- contactPatches: current.contactPatches,
214
- bodyAabb,
215
- previousBodyAabb,
216
- sweptBodyAabb,
217
- };
218
- }
219
-
220
- export function getVehicleGeometryState(car) {
221
- if (geometryStateMatches(car, car.geometryState)) return car.geometryState;
222
- car.geometryState = createVehicleGeometryState(car);
223
- return car.geometryState;
224
- }
225
-
226
- export function createVehicleAabb(car, options = {}) {
227
- const geometry = createVehicleGeometry(car, options);
228
- return mergeAabbs(geometry.shapes.map(createVehicleShapeAabb));
229
- }
230
-
231
- export function interpolateVehiclePose(car, amount) {
232
- const previousX = finiteOr(car.previousX, car.x);
233
- const previousY = finiteOr(car.previousY, car.y);
234
- const previousHeading = finiteOr(car.previousHeading, car.heading);
235
- const headingDelta = normalizeAngle(finiteOr(car.heading, 0) - previousHeading);
236
-
237
- return {
238
- ...car,
239
- x: previousX + (finiteOr(car.x, previousX) - previousX) * amount,
240
- y: previousY + (finiteOr(car.y, previousY) - previousY) * amount,
241
- heading: normalizeAngle(previousHeading + headingDelta * amount),
242
- };
243
- }
244
-
245
- export function getCarCorners(car) {
246
- return createVehicleGeometry(car).body.corners;
247
- }
1
+ export * from './vehicle/vehicleGeometry.js';
@@ -1,136 +1 @@
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
- simUnitsToMeters,
7
- metersToSimUnits,
8
- } from './units.js';
9
- import { VEHICLE_GEOMETRY, getCarCorners } from './vehicleGeometry.js';
10
-
11
- const G = 9.80665;
12
-
13
- export const VEHICLE_LIMITS = {
14
- wheelbase: metersToSimUnits(REAL_F1_WHEELBASE_METERS),
15
- maxSteer: 0.56,
16
- steerRate: 2.35,
17
- maxSpeed: TOP_SPEED_SIM_UNITS_PER_SECOND,
18
- carLength: VEHICLE_GEOMETRY.visualLength,
19
- carWidth: VEHICLE_GEOMETRY.visualWidth,
20
- };
21
-
22
- const SURFACE_MODEL = {
23
- track: { grip: 1, drag: 0, rollingResistance: 0 },
24
- 'pit-entry': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
25
- 'pit-lane': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
26
- 'pit-exit': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
27
- 'pit-box': { grip: 0.94, drag: 0.08, rollingResistance: 0.025 },
28
- kerb: { grip: 0.92, drag: 0.12, rollingResistance: 0.045 },
29
- gravel: { grip: 0.43, drag: 4.0, rollingResistance: 0.68 },
30
- grass: { grip: 0.34, drag: 1.9, rollingResistance: 0.36 },
31
- barrier: { grip: 0.18, drag: 9, rollingResistance: 1.2 },
32
- };
33
- const WHEEL_DRAG_YAW_GAIN = 0.08;
34
- const MAX_WHEEL_DRAG_YAW_RATE = 0.22;
35
-
36
- function accelerationLimit(speed, surfaceGrip) {
37
- const speedRatio = clamp(speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
38
- return SIM_UNITS_PER_METER * surfaceGrip * (17 * (1 - speedRatio ** 2.1) + 1.2);
39
- }
40
-
41
- function brakingLimit(speed, surfaceGrip) {
42
- const speedRatio = clamp(speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
43
- return SIM_UNITS_PER_METER * surfaceGrip * (20 + speedRatio * 14);
44
- }
45
-
46
- export function tirePerformanceFactor(tireEnergy = 100) {
47
- const normalized = clamp((Number(tireEnergy) || 1) / 100, 0.01, 1);
48
- return clamp(0.45 + 0.55 * normalized ** 0.72, 0.45, 1);
49
- }
50
-
51
- function surfaceResistance(surfaceName) {
52
- const surface = SURFACE_MODEL[surfaceName] ?? SURFACE_MODEL.track;
53
- return surface.drag * 0.14 + surface.rollingResistance * 2.4 + (1 - surface.grip) * 0.65;
54
- }
55
-
56
- function sideWheelResistance(wheels, side) {
57
- const sideWheels = wheels.filter((wheel) => wheel.id?.endsWith(`-${side}`));
58
- if (!sideWheels.length) return 0;
59
- return sideWheels.reduce((total, wheel) => total + surfaceResistance(wheel.surface), 0) / sideWheels.length;
60
- }
61
-
62
- function wheelDragYawRate(car) {
63
- const wheels = Array.isArray(car.wheelStates) ? car.wheelStates : [];
64
- if (!wheels.length) return 0;
65
- const leftResistance = sideWheelResistance(wheels, 'left');
66
- const rightResistance = sideWheelResistance(wheels, 'right');
67
- const speedFactor = clamp(car.speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
68
- return clamp(
69
- (rightResistance - leftResistance) * speedFactor * WHEEL_DRAG_YAW_GAIN,
70
- -MAX_WHEEL_DRAG_YAW_RATE,
71
- MAX_WHEEL_DRAG_YAW_RATE,
72
- );
73
- }
74
-
75
- export function integrateVehiclePhysics(car, controls, dt) {
76
- const steeringTarget = clamp(controls.steering ?? 0, -VEHICLE_LIMITS.maxSteer, VEHICLE_LIMITS.maxSteer);
77
- const steerDelta = clamp(
78
- steeringTarget - car.steeringAngle,
79
- -VEHICLE_LIMITS.steerRate * dt,
80
- VEHICLE_LIMITS.steerRate * dt,
81
- );
82
- car.steeringAngle += steerDelta;
83
-
84
- const throttle = clamp(controls.throttle ?? 0, 0, 1);
85
- const brake = clamp(controls.brake ?? 0, 0, 1);
86
- const surface = SURFACE_MODEL[car.trackState?.surface] ?? SURFACE_MODEL.track;
87
- const tireFactor = tirePerformanceFactor(car.tireEnergy ?? 100);
88
- const surfaceGrip = surface.grip * tireFactor;
89
- const dragMultiplier = car.drsActive ? 0.42 : 1;
90
- const speedRatio = clamp(car.speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
91
- const engineForce = throttle *
92
- car.powerNewtons *
93
- (car.drsActive ? 1.08 : 1) *
94
- Math.max(0.12, 1 - speedRatio ** 1.15);
95
- const brakeForce = brake * car.brakeNewtons;
96
- const speedMetersPerSecondBefore = simUnitsToMeters(car.speed);
97
- const dragForce = (car.dragCoefficient * dragMultiplier * 0.12 + surface.drag) *
98
- speedMetersPerSecondBefore * speedMetersPerSecondBefore;
99
- const rollingForce = surface.rollingResistance * car.mass * G;
100
- const driveAcceleration = clamp((engineForce / car.mass) * SIM_UNITS_PER_METER, 0, accelerationLimit(car.speed, surfaceGrip));
101
- const drsAcceleration = car.drsActive ? driveAcceleration * 0.06 : 0;
102
- const brakeDeceleration = clamp((brakeForce / car.mass) * SIM_UNITS_PER_METER, 0, brakingLimit(car.speed, surfaceGrip));
103
- const dragDeceleration = ((dragForce + rollingForce) / car.mass) * SIM_UNITS_PER_METER;
104
- const acceleration = driveAcceleration + drsAcceleration - brakeDeceleration - dragDeceleration;
105
-
106
- car.speed = clamp(car.speed + acceleration * dt, 0, VEHICLE_LIMITS.maxSpeed);
107
-
108
- const speedMetersPerSecond = simUnitsToMeters(car.speed);
109
- const rawYawRate = (speedMetersPerSecond / REAL_F1_WHEELBASE_METERS) *
110
- Math.tan(car.steeringAngle) *
111
- tireFactor;
112
- const downforceGrip = car.downforceCoefficient * speedMetersPerSecond * speedMetersPerSecond / car.mass;
113
- const tyreConditionGrip = tireFactor;
114
- const maxYawRate = ((car.tireGrip * tyreConditionGrip * G + downforceGrip) * surfaceGrip) /
115
- Math.max(speedMetersPerSecond, 1);
116
- const steeringYawRate = clamp(rawYawRate, -maxYawRate, maxYawRate);
117
- const requestedWheelDragYawRate = wheelDragYawRate(car);
118
- car.yawRate = clamp(steeringYawRate + requestedWheelDragYawRate, -maxYawRate, maxYawRate);
119
- car.wheelDragYawRate = car.yawRate - steeringYawRate;
120
- car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
121
- car.heading = normalizeAngle(car.heading + car.yawRate * dt);
122
- car.x += Math.cos(car.heading) * car.speed * dt;
123
- car.y += Math.sin(car.heading) * car.speed * dt;
124
- car.throttle = throttle;
125
- car.brake = brake;
126
- car.lateralAcceleration = speedMetersPerSecond * car.yawRate;
127
- car.steerSaturation = rawYawRate === 0 ? 0 : Math.abs(steeringYawRate / rawYawRate);
128
- const tyreLoad = Math.abs(car.lateralAcceleration) / G;
129
- const tireCare = clamp(Number(car.tireCare) || 1, 0.45, 1.8);
130
- const wearRate = (0.035 + tyreLoad * 0.11 + brake * 0.07 + throttle * 0.025) / tireCare;
131
- car.tireEnergy = clamp((car.tireEnergy ?? 100) - wearRate * dt, 1, 100);
132
-
133
- return car;
134
- }
135
-
136
- export { getCarCorners };
1
+ export * from './vehicle/vehiclePhysics.js';
@@ -0,0 +1,224 @@
1
+ const DEFAULT_WARMUP_POLICY = 'config-change';
2
+ const DEFAULT_WARMUP_STEPS = Object.freeze({
3
+ simulation: 20,
4
+ browser: 20,
5
+ environment: 20,
6
+ });
7
+ const SUPPORTED_WARMUP_POLICIES = new Set(['config-change', 'always', 'never']);
8
+ const MAX_CACHE_ENTRIES_PER_SURFACE = 256;
9
+ const warmupFingerprintCache = new Map();
10
+
11
+ export function normalizeWarmupOptions(input, surface = 'simulation') {
12
+ const fallbackSteps = DEFAULT_WARMUP_STEPS[surface] ?? DEFAULT_WARMUP_STEPS.simulation;
13
+ if (input === false) {
14
+ return {
15
+ enabled: false,
16
+ policy: 'never',
17
+ steps: fallbackSteps,
18
+ surface,
19
+ };
20
+ }
21
+ if (input === true || input == null) {
22
+ return {
23
+ enabled: true,
24
+ policy: DEFAULT_WARMUP_POLICY,
25
+ steps: fallbackSteps,
26
+ surface,
27
+ };
28
+ }
29
+ const enabled = input.enabled == null ? true : Boolean(input.enabled);
30
+ const policy = normalizeWarmupPolicy(input.policy, enabled);
31
+ return {
32
+ enabled,
33
+ policy,
34
+ steps: normalizeWarmupSteps(input.steps, fallbackSteps),
35
+ surface: typeof input.surface === 'string' && input.surface.length > 0 ? input.surface : surface,
36
+ };
37
+ }
38
+
39
+ export function withWarmupSurfaceOptions(options = {}, surface = 'simulation') {
40
+ return {
41
+ ...options,
42
+ warmup: normalizeWarmupOptions(options.warmup, surface),
43
+ };
44
+ }
45
+
46
+ export function disableWarmupOptions(options = {}, surface = 'simulation') {
47
+ const warmup = normalizeWarmupOptions(options.warmup, surface);
48
+ return {
49
+ ...options,
50
+ warmup: {
51
+ ...warmup,
52
+ enabled: false,
53
+ policy: 'never',
54
+ surface,
55
+ },
56
+ };
57
+ }
58
+
59
+ export function runWarmupWithGuard({
60
+ options = {},
61
+ surface = 'simulation',
62
+ execute,
63
+ } = {}) {
64
+ const warmup = normalizeWarmupOptions(options.warmup, surface);
65
+ const fingerprint = createWarmupFingerprint(options, {
66
+ surface: warmup.surface,
67
+ steps: warmup.steps,
68
+ });
69
+ if (!shouldRunWarmup(warmup, fingerprint)) {
70
+ return { ran: false, warmup, fingerprint };
71
+ }
72
+ try {
73
+ execute?.({ warmup, fingerprint });
74
+ markWarmupComplete(warmup.surface, fingerprint);
75
+ return { ran: true, warmup, fingerprint };
76
+ } catch {
77
+ return { ran: false, warmup, fingerprint };
78
+ }
79
+ }
80
+
81
+ export function resetWarmupRuntimeCache() {
82
+ warmupFingerprintCache.clear();
83
+ }
84
+
85
+ function shouldRunWarmup(warmup, fingerprint) {
86
+ if (!warmup.enabled) return false;
87
+ if (warmup.policy === 'never') return false;
88
+ if (warmup.policy === 'always') return true;
89
+ return !hasWarmupFingerprint(warmup.surface, fingerprint);
90
+ }
91
+
92
+ function hasWarmupFingerprint(surface, fingerprint) {
93
+ return warmupFingerprintCache.get(surface)?.has(fingerprint) ?? false;
94
+ }
95
+
96
+ function markWarmupComplete(surface, fingerprint) {
97
+ let entries = warmupFingerprintCache.get(surface);
98
+ if (!entries) {
99
+ entries = new Map();
100
+ warmupFingerprintCache.set(surface, entries);
101
+ }
102
+ entries.set(fingerprint, Date.now());
103
+ while (entries.size > MAX_CACHE_ENTRIES_PER_SURFACE) {
104
+ const firstKey = entries.keys().next().value;
105
+ entries.delete(firstKey);
106
+ }
107
+ }
108
+
109
+ function createWarmupFingerprint(options, { surface, steps }) {
110
+ return stableStringify({
111
+ surface,
112
+ steps,
113
+ seed: finiteOrNull(options.seed),
114
+ trackSeed: finiteOrNull(options.trackSeed),
115
+ totalLaps: finiteOrNull(options.totalLaps),
116
+ trackGeneration: options.trackGeneration ?? null,
117
+ track: summarizeTrack(options.track),
118
+ physicsMode: options.physicsMode ?? null,
119
+ trackQueryIndex: options.trackQueryIndex !== false,
120
+ rules: options.rules ?? null,
121
+ participantInteractions: options.participantInteractions ?? null,
122
+ replayGhosts: summarizeReplayGhosts(options.replayGhosts),
123
+ drivers: summarizeDrivers(options.drivers),
124
+ entries: summarizeEntries(options.entries),
125
+ controlledDrivers: summarizeStringArray(options.controlledDrivers),
126
+ sensors: options.sensors ?? null,
127
+ sensorsByDriver: summarizeSensorsByDriver(options.sensorsByDriver),
128
+ scenario: summarizeScenario(options.scenario),
129
+ frameSkip: finiteOrNull(options.frameSkip),
130
+ });
131
+ }
132
+
133
+ function normalizeWarmupPolicy(value, enabled) {
134
+ if (!enabled) return 'never';
135
+ if (typeof value !== 'string') return DEFAULT_WARMUP_POLICY;
136
+ return SUPPORTED_WARMUP_POLICIES.has(value) ? value : DEFAULT_WARMUP_POLICY;
137
+ }
138
+
139
+ function normalizeWarmupSteps(value, fallback) {
140
+ if (value == null) return fallback;
141
+ const steps = Math.floor(Number(value));
142
+ if (!Number.isFinite(steps) || steps < 1) return fallback;
143
+ return steps;
144
+ }
145
+
146
+ function summarizeTrack(track) {
147
+ if (!track || typeof track !== 'object') return null;
148
+ const samples = Array.isArray(track.samples) ? track.samples.length : null;
149
+ const centerlineControls = Array.isArray(track.centerlineControls) ? track.centerlineControls.length : null;
150
+ const drsZones = Array.isArray(track.drsZones) ? track.drsZones.length : null;
151
+ return {
152
+ name: track.name ?? null,
153
+ seed: finiteOrNull(track.seed),
154
+ length: finiteOrNull(track.length),
155
+ width: finiteOrNull(track.width),
156
+ sampleCount: finiteOrNull(track.sampleCount) ?? samples,
157
+ centerlineControls,
158
+ drsZones,
159
+ hasPitLane: Boolean(track.pitLane?.enabled),
160
+ };
161
+ }
162
+
163
+ function summarizeReplayGhosts(replayGhosts) {
164
+ if (!Array.isArray(replayGhosts)) return null;
165
+ return replayGhosts.map((ghost) => ({
166
+ id: ghost?.id ?? null,
167
+ visible: ghost?.visible !== false,
168
+ samples: Array.isArray(ghost?.trajectory) ? ghost.trajectory.length : 0,
169
+ }));
170
+ }
171
+
172
+ function summarizeDrivers(drivers) {
173
+ if (!Array.isArray(drivers)) return null;
174
+ return {
175
+ count: drivers.length,
176
+ ids: drivers.map((driver) => driver?.id ?? null),
177
+ };
178
+ }
179
+
180
+ function summarizeEntries(entries) {
181
+ if (!Array.isArray(entries)) return null;
182
+ return {
183
+ count: entries.length,
184
+ driverIds: entries.map((entry) => entry?.driverId ?? null),
185
+ };
186
+ }
187
+
188
+ function summarizeStringArray(values) {
189
+ if (!Array.isArray(values)) return null;
190
+ return values.map((value) => String(value));
191
+ }
192
+
193
+ function summarizeSensorsByDriver(sensorsByDriver) {
194
+ if (!sensorsByDriver || typeof sensorsByDriver !== 'object') return null;
195
+ const ids = Object.keys(sensorsByDriver);
196
+ return {
197
+ count: ids.length,
198
+ ids: ids.sort(),
199
+ };
200
+ }
201
+
202
+ function summarizeScenario(scenario) {
203
+ if (!scenario || typeof scenario !== 'object') return null;
204
+ return {
205
+ participants: scenario.participants ?? null,
206
+ preset: scenario.preset ?? null,
207
+ placements: scenario.placements ?? null,
208
+ traffic: scenario.traffic ?? null,
209
+ };
210
+ }
211
+
212
+ function stableStringify(value) {
213
+ if (value == null) return 'null';
214
+ if (typeof value === 'number' || typeof value === 'boolean') return JSON.stringify(value);
215
+ if (typeof value === 'string') return JSON.stringify(value);
216
+ if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(',')}]`;
217
+ if (typeof value !== 'object') return JSON.stringify(String(value));
218
+ const keys = Object.keys(value).sort();
219
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
220
+ }
221
+
222
+ function finiteOrNull(value) {
223
+ return Number.isFinite(Number(value)) ? Number(value) : null;
224
+ }