@inventure71/paddockjs 2.0.1 → 4.0.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 (136) hide show
  1. package/CHANGELOG.md +176 -1
  2. package/INSTALL_AND_UPDATE.md +24 -4
  3. package/README.md +84 -512
  4. package/docs/architecture.md +38 -24
  5. package/docs/component_inventory.md +72 -0
  6. package/docs/composable-layouts.md +77 -0
  7. package/docs/concepts.md +2 -2
  8. package/docs/custom_model_controller.md +6 -6
  9. package/docs/data-helpers.md +50 -0
  10. package/docs/data_contract.md +104 -27
  11. package/docs/getting-started.md +75 -0
  12. package/docs/headless-environment.md +68 -0
  13. package/docs/index.md +18 -4
  14. package/docs/learnings.md +6 -6
  15. package/docs/rules.md +12 -8
  16. package/docs/sense_contract.md +1 -1
  17. package/docs/system_specs.md +71 -21
  18. package/docs/theming.md +64 -0
  19. package/docs/training.md +26 -19
  20. package/docs/troubleshooting.md +76 -0
  21. package/docs/upgrading-hosts-to-3.md +42 -0
  22. package/examples/python/README.md +6 -0
  23. package/examples/python/base_policy_server.py +19 -0
  24. package/package.json +10 -3
  25. package/src/api/PaddockSimulatorController.js +150 -24
  26. package/src/api/runtimeTheme.js +59 -0
  27. package/src/app/F1SimulatorApp.js +213 -29
  28. package/src/app/banners/raceDataBannerController.js +71 -36
  29. package/src/app/banners/stewardMessageController.js +5 -4
  30. package/src/app/camera/cameraConstants.js +2 -0
  31. package/src/app/camera/cameraController.js +86 -29
  32. package/src/app/camera/cameraGeometry.js +12 -1
  33. package/src/app/domBindings.js +89 -9
  34. package/src/app/layoutScheduler.js +27 -0
  35. package/src/app/layoutSupport.js +165 -0
  36. package/src/app/raceOverlayClearanceSupport.js +158 -0
  37. package/src/app/readouts/carOverviewRenderer.js +16 -8
  38. package/src/app/readouts/controlStateRenderer.js +1 -1
  39. package/src/app/readouts/raceStatusRenderer.js +30 -21
  40. package/src/app/readouts/readoutFormatters.js +2 -0
  41. package/src/app/readouts/telemetryRenderer.js +17 -8
  42. package/src/app/readouts/timingTowerRenderer.js +262 -49
  43. package/src/app/runtime/frameLoop.js +5 -1
  44. package/src/app/runtime/runtimeVisibility.js +66 -5
  45. package/src/config/cssValues.js +16 -0
  46. package/src/config/defaultOptions.js +58 -38
  47. package/src/config/themeOptions.js +830 -0
  48. package/src/config/timingGapMode.js +13 -0
  49. package/src/config/urlOptions.js +25 -0
  50. package/src/data/championship.js +14 -9
  51. package/src/data/index.d.ts +212 -0
  52. package/src/data/index.js +14 -0
  53. package/src/environment/controllerLoop.js +17 -3
  54. package/src/environment/episode.js +11 -1
  55. package/src/environment/index.d.ts +1 -2
  56. package/src/environment/metrics.js +59 -16
  57. package/src/environment/observationVector.js +50 -33
  58. package/src/environment/observations.js +89 -20
  59. package/src/environment/options.js +1 -1
  60. package/src/environment/runtime.js +44 -11
  61. package/src/environment/scenarios.js +1 -1
  62. package/src/environment/sensors/carRays.js +48 -37
  63. package/src/environment/sensors/index.js +501 -97
  64. package/src/environment/sensors/indexedRayBands.js +134 -46
  65. package/src/environment/sensors/rayBandTrace.js +1171 -0
  66. package/src/environment/sensors/rayChannels.js +108 -0
  67. package/src/environment/sensors/rayConfig.js +71 -25
  68. package/src/environment/sensors/rayGeometry.js +23 -17
  69. package/src/environment/sensors/sensorTargets.js +67 -1
  70. package/src/environment/sensors/surfaceRays.js +167 -118
  71. package/src/environment/sensors/trackRays.js +157 -48
  72. package/src/index.d.ts +153 -9
  73. package/src/index.js +52 -3
  74. package/src/simulation/collisionGeometry.js +260 -96
  75. package/src/simulation/driver/driverController.js +1 -1
  76. package/src/simulation/driver/edgeRecovery.js +1 -1
  77. package/src/simulation/driver/racingControls.js +2 -2
  78. package/src/simulation/driver/rejoinControls.js +2 -2
  79. package/src/simulation/pit/pitFlow.js +21 -9
  80. package/src/simulation/pit/pitKinematics.js +2 -2
  81. package/src/simulation/pit/pitQueue.js +5 -3
  82. package/src/simulation/pit/pitRouteMovement.js +9 -4
  83. package/src/simulation/pit/pitRouting.js +79 -46
  84. package/src/simulation/pit/pitState.js +1 -0
  85. package/src/simulation/pit/pitTireService.js +7 -3
  86. package/src/simulation/race/raceFinish.js +25 -8
  87. package/src/simulation/race/raceOrder.js +189 -32
  88. package/src/simulation/race/raceProgress.js +129 -37
  89. package/src/simulation/race/raceSetup.js +4 -6
  90. package/src/simulation/race/raceStep.js +26 -14
  91. package/src/simulation/race/safetyCar.js +1 -1
  92. package/src/simulation/race/stalledDnf.js +1 -1
  93. package/src/simulation/raceSimulation.js +20 -17
  94. package/src/simulation/rules/collisionSteward.js +40 -23
  95. package/src/simulation/rules/rulesReview.js +77 -56
  96. package/src/simulation/snapshots/raceSnapshots.js +37 -0
  97. package/src/simulation/timing/drsTiming.js +143 -30
  98. package/src/simulation/timing/gapEstimation.js +25 -3
  99. package/src/simulation/timing/lapTelemetry.js +138 -40
  100. package/src/simulation/timing/raceTiming.js +5 -1
  101. package/src/simulation/timing/sectorPerformance.js +104 -26
  102. package/src/simulation/timing/timingHistory.js +229 -14
  103. package/src/simulation/timing/timingLines.js +65 -7
  104. package/src/simulation/track/pitLaneLayout.js +30 -3
  105. package/src/simulation/track/pitLaneState.js +218 -19
  106. package/src/simulation/track/spatialQueries.js +66 -33
  107. package/src/simulation/track/trackMath.js +91 -24
  108. package/src/simulation/track/trackModel.js +2 -0
  109. package/src/simulation/track/trackQueryIndex.js +1468 -189
  110. package/src/simulation/track/trackQueryPitIndex.js +138 -14
  111. package/src/simulation/track/trackQueryScratch.js +7 -0
  112. package/src/simulation/track/trackQueryStats.js +25 -0
  113. package/src/simulation/track/trackSnapshotJson.js +139 -0
  114. package/src/simulation/track/trackStatePolicy.js +249 -2
  115. package/src/simulation/vehicle/contactResolution.js +99 -62
  116. package/src/simulation/vehicle/mainTrackWheelSurface.js +73 -34
  117. package/src/simulation/vehicle/pitWheelSurface.js +81 -28
  118. package/src/simulation/vehicle/runoffResponse.js +54 -29
  119. package/src/simulation/vehicle/vehicleGeometry.js +245 -51
  120. package/src/simulation/vehicle/vehicleKinematics.js +2 -2
  121. package/src/simulation/vehicle/vehiclePhysics.js +45 -30
  122. package/src/simulation/vehicle/vehicleSnapshots.js +35 -1
  123. package/src/simulation/vehicle/vehicleState.js +7 -1
  124. package/src/simulation/vehicle/wheelSurface.js +807 -92
  125. package/src/simulation/warmup/runtimeWarmup.js +0 -1
  126. package/src/styles.css +1568 -519
  127. package/src/ui/bannerTemplates.js +38 -28
  128. package/src/ui/cameraControlsTemplate.js +16 -5
  129. package/src/ui/carOverviewTemplate.js +12 -5
  130. package/src/ui/raceCanvasTemplate.js +31 -8
  131. package/src/ui/raceControlsTemplate.js +27 -10
  132. package/src/ui/shellTemplate.js +3 -1
  133. package/src/ui/telemetryDrawerTemplate.js +23 -10
  134. package/src/ui/telemetryTemplates.js +58 -32
  135. package/src/ui/templateUtils.js +48 -0
  136. package/src/ui/timingTowerTemplate.js +29 -12
@@ -49,77 +49,221 @@ export function vehicleAxes(heading) {
49
49
  };
50
50
  }
51
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
- };
52
+ function writePoint(target, x, y) {
53
+ const point = target ?? {};
54
+ point.x = x;
55
+ point.y = y;
56
+ return point;
57
57
  }
58
58
 
59
- export function createOrientedRect({ id, type, center, heading, length, width }) {
60
- const axes = vehicleAxes(heading);
59
+ function writePose(target, pose) {
60
+ const next = target ?? {};
61
+ next.x = pose.x;
62
+ next.y = pose.y;
63
+ next.heading = pose.heading;
64
+ return next;
65
+ }
66
+
67
+ function writeCorner(target, centerX, centerY, forwardX, forwardY, rightX, rightY, longitudinal, lateral) {
68
+ return writePoint(
69
+ target,
70
+ centerX + forwardX * longitudinal + rightX * lateral,
71
+ centerY + forwardY * longitudinal + rightY * lateral,
72
+ );
73
+ }
74
+
75
+ function writeOrientedRectValues(target, {
76
+ id,
77
+ type,
78
+ centerX,
79
+ centerY,
80
+ heading,
81
+ length,
82
+ width,
83
+ forwardX,
84
+ forwardY,
85
+ rightX,
86
+ rightY,
87
+ }, { writeCorners = true } = {}) {
88
+ const rect = target ?? {};
61
89
  const halfLength = length / 2;
62
90
  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
- ];
91
+ const corners = writeCorners || rect.corners ? (rect.corners ?? new Array(4)) : null;
69
92
 
70
- return {
93
+ rect.id = id;
94
+ rect.type = type;
95
+ rect.center = writePoint(rect.center, centerX, centerY);
96
+ rect.heading = heading;
97
+ rect.length = length;
98
+ rect.width = width;
99
+ rect.halfLength = halfLength;
100
+ rect.halfWidth = halfWidth;
101
+ rect.forward = writePoint(rect.forward, forwardX, forwardY);
102
+ rect.right = writePoint(rect.right, rightX, rightY);
103
+ if (corners) {
104
+ corners[0] = writeCorner(corners[0], centerX, centerY, forwardX, forwardY, rightX, rightY, halfLength, halfWidth);
105
+ corners[1] = writeCorner(corners[1], centerX, centerY, forwardX, forwardY, rightX, rightY, halfLength, -halfWidth);
106
+ corners[2] = writeCorner(corners[2], centerX, centerY, forwardX, forwardY, rightX, rightY, -halfLength, -halfWidth);
107
+ corners[3] = writeCorner(corners[3], centerX, centerY, forwardX, forwardY, rightX, rightY, -halfLength, halfWidth);
108
+ rect.corners = corners;
109
+ }
110
+ return rect;
111
+ }
112
+
113
+ function writeOrientedRect(target, { id, type, center, heading, length, width }) {
114
+ const cos = Math.cos(heading);
115
+ const sin = Math.sin(heading);
116
+ return writeOrientedRectValues(target, {
71
117
  id,
72
118
  type,
73
- center,
119
+ centerX: center.x,
120
+ centerY: center.y,
74
121
  heading,
75
122
  length,
76
123
  width,
77
- halfLength,
78
- halfWidth,
79
- forward: axes.forward,
80
- right: axes.right,
81
- corners,
82
- };
124
+ forwardX: cos,
125
+ forwardY: sin,
126
+ rightX: -sin,
127
+ rightY: cos,
128
+ });
83
129
  }
84
130
 
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({
131
+ function writeVehicleGeometry(target, pose) {
132
+ const geometry = target ?? {};
133
+ const forwardX = Math.cos(pose.heading);
134
+ const forwardY = Math.sin(pose.heading);
135
+ const rightX = -forwardY;
136
+ const rightY = forwardX;
137
+ const wheels = geometry.wheels ?? new Array(WHEEL_SPECS.length);
138
+ const shapes = geometry.shapes ?? new Array(1 + WHEEL_SPECS.length);
139
+
140
+ const body = writeOrientedRectValues(geometry.body, {
90
141
  id: 'body',
91
142
  type: 'body',
92
- center,
143
+ centerX: pose.x,
144
+ centerY: pose.y,
93
145
  heading: pose.heading,
94
146
  length: VEHICLE_GEOMETRY.bodyLength,
95
147
  width: VEHICLE_GEOMETRY.bodyWidth,
148
+ forwardX,
149
+ forwardY,
150
+ rightX,
151
+ rightY,
96
152
  });
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({
153
+
154
+ for (let index = 0; index < WHEEL_SPECS.length; index += 1) {
155
+ const spec = WHEEL_SPECS[index];
156
+ const wheelCenterX =
157
+ pose.x +
158
+ forwardX * (spec.longitudinal * VEHICLE_GEOMETRY.wheelLongitudinalOffset) +
159
+ rightX * (spec.lateral * VEHICLE_GEOMETRY.wheelLateralOffset);
160
+ const wheelCenterY =
161
+ pose.y +
162
+ forwardY * (spec.longitudinal * VEHICLE_GEOMETRY.wheelLongitudinalOffset) +
163
+ rightY * (spec.lateral * VEHICLE_GEOMETRY.wheelLateralOffset);
164
+ wheels[index] = writeOrientedRectValues(wheels[index], {
105
165
  id: spec.id,
106
166
  type: 'wheel',
107
- center: wheelCenter,
167
+ centerX: wheelCenterX,
168
+ centerY: wheelCenterY,
108
169
  heading: pose.heading,
109
170
  length: VEHICLE_GEOMETRY.wheelLength,
110
171
  width: VEHICLE_GEOMETRY.wheelWidth,
172
+ forwardX,
173
+ forwardY,
174
+ rightX,
175
+ rightY,
111
176
  });
112
- });
177
+ shapes[index + 1] = wheels[index];
178
+ }
113
179
 
180
+ shapes[0] = body;
181
+ geometry.body = body;
182
+ geometry.wheels = wheels;
183
+ geometry.contactPatches = wheels;
184
+ geometry.shapes = shapes;
185
+ return geometry;
186
+ }
187
+
188
+ function writeCurrentVehicleGeometry(target, pose) {
189
+ const geometry = target ?? {};
190
+ const forwardX = Math.cos(pose.heading);
191
+ const forwardY = Math.sin(pose.heading);
192
+ const rightX = -forwardY;
193
+ const rightY = forwardX;
194
+ const wheels = geometry.wheels ?? new Array(WHEEL_SPECS.length);
195
+
196
+ const body = writeOrientedRectValues(geometry.body, {
197
+ id: 'body',
198
+ type: 'body',
199
+ centerX: pose.x,
200
+ centerY: pose.y,
201
+ heading: pose.heading,
202
+ length: VEHICLE_GEOMETRY.bodyLength,
203
+ width: VEHICLE_GEOMETRY.bodyWidth,
204
+ forwardX,
205
+ forwardY,
206
+ rightX,
207
+ rightY,
208
+ }, { writeCorners: false });
209
+
210
+ for (let index = 0; index < WHEEL_SPECS.length; index += 1) {
211
+ const spec = WHEEL_SPECS[index];
212
+ const wheelCenterX =
213
+ pose.x +
214
+ forwardX * (spec.longitudinal * VEHICLE_GEOMETRY.wheelLongitudinalOffset) +
215
+ rightX * (spec.lateral * VEHICLE_GEOMETRY.wheelLateralOffset);
216
+ const wheelCenterY =
217
+ pose.y +
218
+ forwardY * (spec.longitudinal * VEHICLE_GEOMETRY.wheelLongitudinalOffset) +
219
+ rightY * (spec.lateral * VEHICLE_GEOMETRY.wheelLateralOffset);
220
+ wheels[index] = writeOrientedRectValues(wheels[index], {
221
+ id: spec.id,
222
+ type: 'wheel',
223
+ centerX: wheelCenterX,
224
+ centerY: wheelCenterY,
225
+ heading: pose.heading,
226
+ length: VEHICLE_GEOMETRY.wheelLength,
227
+ width: VEHICLE_GEOMETRY.wheelWidth,
228
+ forwardX,
229
+ forwardY,
230
+ rightX,
231
+ rightY,
232
+ }, { writeCorners: false });
233
+ }
234
+
235
+ geometry.body = body;
236
+ geometry.wheels = wheels;
237
+ geometry.contactPatches = wheels;
238
+ return geometry;
239
+ }
240
+
241
+ export function createOrientedRect({ id, type, center, heading, length, width }) {
242
+ return writeOrientedRect(null, { id, type, center, heading, length, width });
243
+ }
244
+
245
+ export function createVehicleGeometry(car, options = {}) {
246
+ const pose = vehiclePose(car, options);
247
+ return writeVehicleGeometry(null, pose);
248
+ }
249
+
250
+ function currentPoseSignature(pose) {
251
+ return [
252
+ pose.x,
253
+ pose.y,
254
+ pose.heading,
255
+ ].join(':');
256
+ }
257
+
258
+ function currentPoseState(car) {
114
259
  return {
115
- body,
116
- wheels,
117
- contactPatches: wheels,
118
- shapes: [body, ...wheels],
260
+ x: finiteOr(car.x, 0),
261
+ y: finiteOr(car.y, 0),
262
+ heading: normalizeAngle(finiteOr(car.heading, 0)),
119
263
  };
120
264
  }
121
265
 
122
- function geometryPoseSignature(pose) {
266
+ function sweptPoseSignature(pose) {
123
267
  return [
124
268
  pose.x,
125
269
  pose.y,
@@ -131,9 +275,7 @@ function geometryPoseSignature(pose) {
131
275
  }
132
276
 
133
277
  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));
278
+ const { x, y, heading } = currentPoseState(car);
137
279
  return {
138
280
  x,
139
281
  y,
@@ -144,12 +286,21 @@ function geometryPose(car) {
144
286
  };
145
287
  }
146
288
 
289
+ function currentGeometryStateMatches(car, state) {
290
+ const pose = state?.pose;
291
+ if (!pose) return false;
292
+ const currentPose = currentPoseState(car);
293
+ return (
294
+ pose.x === currentPose.x &&
295
+ pose.y === currentPose.y &&
296
+ pose.heading === currentPose.heading
297
+ );
298
+ }
299
+
147
300
  function geometryStateMatches(car, state) {
148
301
  const pose = state?.pose;
149
302
  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));
303
+ const { x, y, heading } = currentPoseState(car);
153
304
  return (
154
305
  pose.x === x &&
155
306
  pose.y === y &&
@@ -197,15 +348,21 @@ export function mergeAabbs(aabbs) {
197
348
 
198
349
  export function createVehicleGeometryState(car) {
199
350
  const pose = geometryPose(car);
200
- const current = createVehicleGeometry(car);
201
- const previous = createVehicleGeometry(car, { previous: true });
351
+ const current = writeVehicleGeometry(null, pose);
352
+ const previous = writeVehicleGeometry(null, {
353
+ x: pose.previousX,
354
+ y: pose.previousY,
355
+ heading: pose.previousHeading,
356
+ });
202
357
  const bodyAabb = createVehicleShapeAabb(current.body);
203
358
  const previousBodyAabb = createVehicleShapeAabb(previous.body);
204
359
  const sweptBodyAabb = mergeAabbs([previousBodyAabb, bodyAabb]);
205
360
 
206
361
  return {
207
362
  pose,
208
- signature: geometryPoseSignature(pose),
363
+ currentSignature: currentPoseSignature(pose),
364
+ sweptSignature: sweptPoseSignature(pose),
365
+ signature: sweptPoseSignature(pose),
209
366
  current,
210
367
  previous,
211
368
  body: current.body,
@@ -217,12 +374,49 @@ export function createVehicleGeometryState(car) {
217
374
  };
218
375
  }
219
376
 
377
+ export function createCurrentVehicleGeometryState(car, target = null) {
378
+ const pose = currentPoseState(car);
379
+ const current = writeCurrentVehicleGeometry(target?.current ?? null, pose);
380
+ const state = target ?? {};
381
+ state.pose = writePose(state.pose, pose);
382
+ state.current = current;
383
+ state.body = current.body;
384
+ state.wheels = current.wheels;
385
+ state.contactPatches = current.contactPatches;
386
+ return state;
387
+ }
388
+
220
389
  export function getVehicleGeometryState(car) {
221
390
  if (geometryStateMatches(car, car.geometryState)) return car.geometryState;
222
391
  car.geometryState = createVehicleGeometryState(car);
223
392
  return car.geometryState;
224
393
  }
225
394
 
395
+ export function getCurrentVehicleGeometryState(car) {
396
+ if (currentGeometryStateMatches(car, car.currentGeometryState)) return car.currentGeometryState;
397
+ if (currentGeometryStateMatches(car, car.geometryState)) return car.geometryState;
398
+ car.currentGeometryState = createCurrentVehicleGeometryState(car, car.currentGeometryState ?? null);
399
+ return car.currentGeometryState;
400
+ }
401
+
402
+ export function ensureOrientedRectCorners(rect) {
403
+ if (!rect) return [];
404
+ if (rect.corners) return rect.corners;
405
+ return writeOrientedRectValues(rect, {
406
+ id: rect.id ?? null,
407
+ type: rect.type ?? null,
408
+ centerX: rect.center?.x ?? 0,
409
+ centerY: rect.center?.y ?? 0,
410
+ heading: rect.heading ?? 0,
411
+ length: rect.length ?? 0,
412
+ width: rect.width ?? 0,
413
+ forwardX: rect.forward?.x ?? Math.cos(rect.heading ?? 0),
414
+ forwardY: rect.forward?.y ?? Math.sin(rect.heading ?? 0),
415
+ rightX: rect.right?.x ?? -Math.sin(rect.heading ?? 0),
416
+ rightY: rect.right?.y ?? Math.cos(rect.heading ?? 0),
417
+ }).corners;
418
+ }
419
+
226
420
  export function createVehicleAabb(car, options = {}) {
227
421
  const geometry = createVehicleGeometry(car, options);
228
422
  return mergeAabbs(geometry.shapes.map(createVehicleShapeAabb));
@@ -1,11 +1,11 @@
1
1
  export function freezeVehicleMotion(car, {
2
2
  brake = 1,
3
3
  clearManualControls = false,
4
- physicsMode = 'simulator',
4
+ physicsMode = 'arcade',
5
5
  stabilityState = car.stabilityState ?? 'stable',
6
6
  } = {}) {
7
7
  car.speed = 0;
8
- if (physicsMode === 'simulator') {
8
+ if (physicsMode === 'advanced') {
9
9
  car.velocityX = 0;
10
10
  car.velocityY = 0;
11
11
  } else {
@@ -7,11 +7,11 @@ import {
7
7
  simUnitsToMeters,
8
8
  metersToSimUnits,
9
9
  } from '../units.js';
10
- import { VEHICLE_GEOMETRY, getCarCorners, vehicleAxes } from './vehicleGeometry.js';
10
+ import { VEHICLE_GEOMETRY, getCarCorners } from './vehicleGeometry.js';
11
11
 
12
12
  const G = 9.80665;
13
13
 
14
- export const PHYSICS_MODES = ['arcade', 'simulator'];
14
+ export const PHYSICS_MODES = ['arcade', 'advanced'];
15
15
  export const DEFAULT_PHYSICS_MODE = 'arcade';
16
16
 
17
17
  export const VEHICLE_LIMITS = {
@@ -50,7 +50,7 @@ const SIMULATOR_SURFACE_MODEL = {
50
50
  };
51
51
 
52
52
  export function normalizePhysicsMode(value) {
53
- return value === 'simulator' ? 'simulator' : DEFAULT_PHYSICS_MODE;
53
+ return value === 'advanced' ? 'advanced' : DEFAULT_PHYSICS_MODE;
54
54
  }
55
55
 
56
56
  function accelerationLimit(speed, surfaceGrip) {
@@ -73,17 +73,26 @@ function surfaceResistance(surfaceName, model = SURFACE_MODEL) {
73
73
  return surface.drag * 0.14 + surface.rollingResistance * 2.4 + (1 - surface.grip) * 0.65;
74
74
  }
75
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
76
  function wheelDragYawRate(car, model = SURFACE_MODEL) {
83
77
  const wheels = Array.isArray(car.wheelStates) ? car.wheelStates : [];
84
78
  if (!wheels.length) return 0;
85
- const leftResistance = sideWheelResistance(wheels, 'left', model);
86
- const rightResistance = sideWheelResistance(wheels, 'right', model);
79
+ let leftResistanceTotal = 0;
80
+ let rightResistanceTotal = 0;
81
+ let leftCount = 0;
82
+ let rightCount = 0;
83
+ for (let index = 0; index < wheels.length; index += 1) {
84
+ const wheel = wheels[index];
85
+ const resistance = surfaceResistance(wheel.surface, model);
86
+ if (wheel.id?.endsWith('-left')) {
87
+ leftResistanceTotal += resistance;
88
+ leftCount += 1;
89
+ } else if (wheel.id?.endsWith('-right')) {
90
+ rightResistanceTotal += resistance;
91
+ rightCount += 1;
92
+ }
93
+ }
94
+ const leftResistance = leftCount > 0 ? leftResistanceTotal / leftCount : 0;
95
+ const rightResistance = rightCount > 0 ? rightResistanceTotal / rightCount : 0;
87
96
  const speedFactor = clamp(car.speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
88
97
  return clamp(
89
98
  (rightResistance - leftResistance) * speedFactor * WHEEL_DRAG_YAW_GAIN,
@@ -111,7 +120,7 @@ function setPhysicsTelemetry(car, {
111
120
  }
112
121
 
113
122
  export function integrateVehiclePhysics(car, controls, dt, options = {}) {
114
- if (normalizePhysicsMode(options.physicsMode) === 'simulator') {
123
+ if (normalizePhysicsMode(options.physicsMode) === 'advanced') {
115
124
  return integrateSimulatorVehiclePhysics(car, controls, dt, options);
116
125
  }
117
126
  return integrateArcadeVehiclePhysics(car, controls, dt, options);
@@ -194,20 +203,22 @@ function simulatorSurface(surfaceName) {
194
203
  function aggregateSimulatorSurface(car) {
195
204
  const wheels = Array.isArray(car.wheelStates) ? car.wheelStates : [];
196
205
  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
+ let grip = 0;
207
+ let drag = 0;
208
+ let rollingResistance = 0;
209
+ let scrub = 0;
210
+ for (let index = 0; index < wheels.length; index += 1) {
211
+ const surface = simulatorSurface(wheels[index].surface);
212
+ grip += surface.grip;
213
+ drag += surface.drag;
214
+ rollingResistance += surface.rollingResistance;
215
+ scrub += surface.scrub;
216
+ }
206
217
  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,
218
+ grip: grip / wheels.length,
219
+ drag: drag / wheels.length,
220
+ rollingResistance: rollingResistance / wheels.length,
221
+ scrub: scrub / wheels.length,
211
222
  };
212
223
  }
213
224
 
@@ -256,9 +267,13 @@ function integrateSimulatorVehiclePhysics(car, controls, dt, options = {}) {
256
267
  const speedRatio = clamp(speedBefore / VEHICLE_LIMITS.maxSpeed, 0, 1);
257
268
  const downforceGrip = car.downforceCoefficient * speedBeforeMps * speedBeforeMps / car.mass;
258
269
  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;
270
+ const heading = car.heading ?? 0;
271
+ const forwardX = Math.cos(heading);
272
+ const forwardY = Math.sin(heading);
273
+ const rightX = -forwardY;
274
+ const rightY = forwardX;
275
+ const forwardVelocity = car.velocityX * forwardX + car.velocityY * forwardY;
276
+ const lateralVelocity = car.velocityX * rightX + car.velocityY * rightY;
262
277
  const forwardSpeedMps = simUnitsToMeters(forwardVelocity);
263
278
  const lateralSpeedMps = simUnitsToMeters(lateralVelocity);
264
279
  const currentSlipAngle = Math.atan2(lateralSpeedMps, Math.max(Math.abs(forwardSpeedMps), 0.1));
@@ -328,8 +343,8 @@ function integrateSimulatorVehiclePhysics(car, controls, dt, options = {}) {
328
343
  car.wheelDragYawRate = requestedWheelDragYawRate;
329
344
  car.heading = normalizeAngle(car.heading + car.yawRate * dt);
330
345
 
331
- const accelerationX = axes.forward.x * longitudinalAcceleration + axes.right.x * actualLateralAcceleration;
332
- const accelerationY = axes.forward.y * longitudinalAcceleration + axes.right.y * actualLateralAcceleration;
346
+ const accelerationX = forwardX * longitudinalAcceleration + rightX * actualLateralAcceleration;
347
+ const accelerationY = forwardY * longitudinalAcceleration + rightY * actualLateralAcceleration;
333
348
  car.velocityX += metersToSimUnits(accelerationX) * dt;
334
349
  car.velocityY += metersToSimUnits(accelerationY) * dt;
335
350
  car.speed = limitSimulatorSpeed(car);
@@ -17,7 +17,7 @@ function serializeAppliedControls(car, dependencies) {
17
17
  }
18
18
 
19
19
  function serializeVelocityComponent(car, component, dependencies) {
20
- if (dependencies.physicsMode !== 'simulator') return null;
20
+ if (dependencies.physicsMode !== 'advanced') return null;
21
21
  return finiteOrNull(car[component]);
22
22
  }
23
23
 
@@ -141,6 +141,17 @@ export function serializeCar(car, rank, penaltySeconds = 0, dependencies) {
141
141
  signedOffset: car.trackState?.signedOffset ?? 0,
142
142
  crossTrackError: car.trackState?.crossTrackError ?? 0,
143
143
  surface: car.trackState?.surface ?? 'track',
144
+ trackState: car.trackState ? {
145
+ distance: car.trackState.distance,
146
+ signedOffset: car.trackState.signedOffset,
147
+ crossTrackError: car.trackState.crossTrackError,
148
+ surface: car.trackState.surface,
149
+ inPitLane: Boolean(car.trackState.inPitLane),
150
+ pitLanePart: car.trackState.pitLanePart ?? null,
151
+ pitBoxId: car.trackState.pitBoxId ?? null,
152
+ curvature: car.trackState.curvature ?? 0,
153
+ heading: car.trackState.heading,
154
+ } : null,
144
155
  wheels: serializeWheels(car.wheelStates),
145
156
  inPitLane: Boolean(car.trackState?.inPitLane),
146
157
  pitLanePart: car.trackState?.pitLanePart ?? null,
@@ -155,6 +166,29 @@ export function serializeCar(car, rank, penaltySeconds = 0, dependencies) {
155
166
  };
156
167
  }
157
168
 
169
+ export function serializeRenderCarInto(target, car, dependencies) {
170
+ const { serializeRenderPitStop } = dependencies;
171
+ target.id = car.id;
172
+ target.color = car.color;
173
+ target.previousX = car.previousX ?? car.x;
174
+ target.previousY = car.previousY ?? car.y;
175
+ target.x = car.x;
176
+ target.y = car.y;
177
+ target.previousHeading = car.previousHeading ?? car.heading;
178
+ target.heading = car.heading;
179
+ target.drsActive = Boolean(car.drsActive);
180
+ target.destroyed = Boolean(car.destroyed);
181
+ target.destroyReason = car.destroyReason ?? null;
182
+ target.dnf = Boolean(car.destroyed || car.outOfRace);
183
+ target.dnfReason = car.dnfReason ?? car.destroyReason ?? null;
184
+ target.dnfAt = car.dnfAt ?? car.destroyedAt ?? null;
185
+ target.dnfOrder = car.dnfOrder ?? null;
186
+ target.outOfRace = Boolean(car.outOfRace || car.destroyed);
187
+ target.pitStop = serializeRenderPitStop(car.pitStop);
188
+ target.interaction = serializeParticipantInteraction(car.interaction);
189
+ return target;
190
+ }
191
+
158
192
  export function serializeRenderCar(car, dependencies) {
159
193
  const { serializeRenderPitStop } = dependencies;
160
194
  return {
@@ -96,6 +96,8 @@ export function createCar(driver, index, random, track, { standingStart = false,
96
96
  drsActive: false,
97
97
  drsZoneId: null,
98
98
  drsZoneEnabled: false,
99
+ _drsZoneRef: null,
100
+ _drsNextZoneIndex: null,
99
101
  timingHistory: [],
100
102
  lapTelemetry: createLapTelemetry?.(track, 0, gridDistance) ?? null,
101
103
  drsDetection: {},
@@ -113,6 +115,8 @@ export function clearCarDrsState(car) {
113
115
  car.drsZoneEnabled = false;
114
116
  car.drsActive = false;
115
117
  car.drsEligible = false;
118
+ car._drsZoneRef = null;
119
+ car._drsNextZoneIndex = null;
116
120
  car.drsDetection = {};
117
121
  }
118
122
 
@@ -158,13 +162,15 @@ export function applyExternalCarState(car, partial, context) {
158
162
  nextPartial.raceDistance != null
159
163
  ) {
160
164
  car.gridLocked = false;
165
+ car._drsZoneRef = null;
166
+ car._drsNextZoneIndex = null;
161
167
  if (raceControl.mode === 'pre-start' && cars.every((item) => !item.gridLocked)) {
162
168
  releaseRaceStart();
163
169
  }
164
170
  }
165
171
  car.speed = clamp(Number.isFinite(car.speed) ? car.speed : 0, 0, VEHICLE_LIMITS.maxSpeed);
166
172
  car.heading = normalizeAngle(Number.isFinite(car.heading) ? car.heading : 0);
167
- syncExternalVelocityState(car, nextPartial, physicsMode === 'simulator');
173
+ syncExternalVelocityState(car, nextPartial, physicsMode === 'advanced');
168
174
  const centerState = nearestTrackStateForCar(track, car, car, car.progress ?? car.raceDistance);
169
175
  applyWheelSurfaceState(car, track, { centerState });
170
176
  if (