@inventure71/paddockjs 2.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 (62) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/INSTALL_AND_UPDATE.md +1 -1
  3. package/README.md +30 -5
  4. package/docs/architecture.md +15 -9
  5. package/docs/concepts.md +6 -4
  6. package/docs/data_contract.md +28 -15
  7. package/docs/data_model.md +1 -1
  8. package/docs/index.md +9 -0
  9. package/docs/rules.md +37 -7
  10. package/docs/sense_contract.md +59 -0
  11. package/docs/system_specs.md +56 -7
  12. package/docs/training.md +16 -10
  13. package/package.json +1 -1
  14. package/src/api/PaddockSimulatorController.js +2 -33
  15. package/src/app/BrowserExpertAdapter.js +9 -300
  16. package/src/app/BrowserExpertExternalRenderer.js +303 -0
  17. package/src/app/F1SimulatorApp.js +1 -42
  18. package/src/config/restartOptions.js +34 -0
  19. package/src/environment/controllerLoop.js +17 -2
  20. package/src/environment/episode.js +14 -2
  21. package/src/environment/index.d.ts +16 -2
  22. package/src/environment/observationVector.js +200 -0
  23. package/src/environment/observations.js +58 -285
  24. package/src/environment/recorder.js +13 -6
  25. package/src/environment/rewards.js +13 -2
  26. package/src/environment/runtime.js +62 -14
  27. package/src/environment/scenarios.js +22 -3
  28. package/src/environment/sensors/index.js +3 -3
  29. package/src/environment/sensors/opponentRadar.js +13 -3
  30. package/src/environment/sensors/sensorTargets.js +8 -2
  31. package/src/environment/sensors/surfaceRays.js +90 -26
  32. package/src/environment/sensors/trackRays.js +185 -22
  33. package/src/environment/specs.js +78 -40
  34. package/src/index.d.ts +2 -0
  35. package/src/index.js +1 -32
  36. package/src/simulation/pit/pitFlow.js +5 -0
  37. package/src/simulation/pit/pitKinematics.js +24 -0
  38. package/src/simulation/pit/pitQueue.js +5 -0
  39. package/src/simulation/pit/pitRouteMovement.js +7 -0
  40. package/src/simulation/pit/pitService.js +2 -0
  41. package/src/simulation/pit/pitTireService.js +2 -0
  42. package/src/simulation/race/raceLifecycle.js +2 -6
  43. package/src/simulation/race/raceProgress.js +6 -1
  44. package/src/simulation/race/raceStep.js +1 -1
  45. package/src/simulation/race/redFlag.js +3 -3
  46. package/src/simulation/race/safetyCar.js +10 -4
  47. package/src/simulation/race/stalledDnf.js +8 -17
  48. package/src/simulation/raceSimulation.js +1 -0
  49. package/src/simulation/rules/rulesReview.js +3 -0
  50. package/src/simulation/snapshots/raceSnapshots.js +1 -0
  51. package/src/simulation/track/trackQueryGrid.js +94 -0
  52. package/src/simulation/track/trackQueryIndex.js +16 -302
  53. package/src/simulation/track/trackQueryPitIndex.js +140 -0
  54. package/src/simulation/track/trackQueryScratch.js +46 -0
  55. package/src/simulation/track/trackQueryStats.js +28 -0
  56. package/src/simulation/vehicle/contactResolution.js +67 -2
  57. package/src/simulation/vehicle/runoffResponse.js +11 -12
  58. package/src/simulation/vehicle/vehicleKinematics.js +37 -0
  59. package/src/simulation/vehicle/vehiclePhysics.js +2 -1
  60. package/src/simulation/vehicle/vehicleSnapshots.js +11 -0
  61. package/src/simulation/vehicle/vehicleState.js +36 -2
  62. package/src/simulation/vehicle/wheelSurface.js +3 -1
@@ -1,11 +1,12 @@
1
1
  import { VEHICLE_GEOMETRY, getVehicleGeometryState, vehicleAxes } from './vehicleGeometry.js';
2
2
  import { applyWheelSurfaceState } from './wheelSurface.js';
3
+ import { freezeVehicleMotion } from './vehicleKinematics.js';
3
4
  import { nearestTrackStateForCar, pitOverrideAllowedForCar } from '../track/trackStatePolicy.js';
4
5
  import { markCarDnf } from '../race/retirements.js';
5
6
 
6
7
  export function applyRunoffResponseForSimulation(sim, car) {
7
8
  if (car.destroyed) {
8
- car.speed = 0;
9
+ freezeBarrierMotion(car, sim.physicsMode);
9
10
  applyWheelSurfaceState(car, sim.track);
10
11
  return;
11
12
  }
@@ -67,17 +68,7 @@ function destroyCarOnBarrier(sim, car) {
67
68
  car.drsEligible = false;
68
69
  car.drsZoneId = null;
69
70
  car.drsZoneEnabled = false;
70
- car.speed = 0;
71
- car.throttle = 0;
72
- car.brake = 1;
73
- car.steeringAngle = 0;
74
- car.yawRate = 0;
75
- car.longitudinalAcceleration = 0;
76
- car.lateralAcceleration = 0;
77
- car.longitudinalG = 0;
78
- car.lateralG = 0;
79
- car.tractionLimited = false;
80
- car.stabilityState = 'destroyed';
71
+ freezeBarrierMotion(car, sim.physicsMode);
81
72
  sim.events.unshift({
82
73
  type: 'car-destroyed',
83
74
  at: sim.time,
@@ -86,3 +77,11 @@ function destroyCarOnBarrier(sim, car) {
86
77
  reason: 'barrier',
87
78
  });
88
79
  }
80
+
81
+ function freezeBarrierMotion(car, physicsMode) {
82
+ freezeVehicleMotion(car, {
83
+ clearManualControls: true,
84
+ physicsMode,
85
+ stabilityState: 'destroyed',
86
+ });
87
+ }
@@ -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
+ }
@@ -305,12 +305,13 @@ function integrateSimulatorVehiclePhysics(car, controls, dt, options = {}) {
305
305
  ? clamp(1 - (gripUsage - 1) * 0.55 - longitudinalUsage * 0.18, 0.12, 1)
306
306
  : 1;
307
307
  const brakingDirection = forwardSpeedMps >= -0.2 ? 1 : -1;
308
+ const scrubDirection = forwardSpeedMps >= 0 ? 1 : -1;
308
309
  const longitudinalAcceleration =
309
310
  driveAcceleration * tractionPowerScale -
310
311
  brakeDeceleration * brakingDirection -
311
312
  dragAcceleration * Math.sign(forwardSpeedMps || 1) -
312
313
  rollingAcceleration * Math.sign(forwardSpeedMps || 1) -
313
- steeringScrubAcceleration;
314
+ steeringScrubAcceleration * scrubDirection;
314
315
 
315
316
  const requestedWheelDragYawRate = wheelDragYawRate(car, SIMULATOR_SURFACE_MODEL) * (surfaceName === 'kerb' ? 1.6 : 1);
316
317
  const maxYawRate = speedBeforeMps <= 0.05 ? 0 : totalGripAcceleration / Math.max(speedBeforeMps, 0.1);
@@ -16,6 +16,11 @@ function serializeAppliedControls(car, dependencies) {
16
16
  };
17
17
  }
18
18
 
19
+ function serializeVelocityComponent(car, component, dependencies) {
20
+ if (dependencies.physicsMode !== 'simulator') return null;
21
+ return finiteOrNull(car[component]);
22
+ }
23
+
19
24
  export function serializeWheels(wheels = []) {
20
25
  return wheels.map((wheel) => ({
21
26
  id: wheel.id,
@@ -99,6 +104,8 @@ export function serializeCar(car, rank, penaltySeconds = 0, dependencies) {
99
104
  heading: car.heading,
100
105
  steeringAngle: car.steeringAngle,
101
106
  yawRate: car.yawRate,
107
+ velocityX: serializeVelocityComponent(car, 'velocityX', dependencies),
108
+ velocityY: serializeVelocityComponent(car, 'velocityY', dependencies),
102
109
  turnRadius: car.turnRadius,
103
110
  speed: car.speed,
104
111
  speedKph: simSpeedToKph(car.speed),
@@ -193,6 +200,8 @@ export function serializeObservationCar(car, rank, dependencies) {
193
200
  heading: car.heading,
194
201
  steeringAngle: car.steeringAngle,
195
202
  yawRate: car.yawRate,
203
+ velocityX: serializeVelocityComponent(car, 'velocityX', dependencies),
204
+ velocityY: serializeVelocityComponent(car, 'velocityY', dependencies),
196
205
  speed: car.speed,
197
206
  speedKph: simSpeedToKph(car.speed),
198
207
  throttle: car.throttle,
@@ -261,6 +270,8 @@ export function serializeTrainingCar(car, rank, dependencies) {
261
270
  heading: car.heading,
262
271
  steeringAngle: car.steeringAngle,
263
272
  yawRate: car.yawRate,
273
+ velocityX: serializeVelocityComponent(car, 'velocityX', dependencies),
274
+ velocityY: serializeVelocityComponent(car, 'velocityY', dependencies),
264
275
  speed: car.speed,
265
276
  speedKph: simSpeedToKph(car.speed),
266
277
  throttle: car.throttle,
@@ -121,6 +121,7 @@ export function applyExternalCarState(car, partial, context) {
121
121
  cars,
122
122
  computeLap,
123
123
  nearestDistanceOnRoute,
124
+ physicsMode,
124
125
  progressDelta,
125
126
  raceControl,
126
127
  releaseRaceStart,
@@ -137,6 +138,7 @@ export function applyExternalCarState(car, partial, context) {
137
138
  if (partial.destroyed === false) nextPartial.destroyed = car.destroyed;
138
139
  if (partial.outOfRace === false) nextPartial.outOfRace = car.outOfRace;
139
140
  }
141
+ assertFiniteExternalCarState(nextPartial);
140
142
  Object.assign(car, nextPartial);
141
143
  if (
142
144
  !raceControl.finished &&
@@ -160,8 +162,9 @@ export function applyExternalCarState(car, partial, context) {
160
162
  releaseRaceStart();
161
163
  }
162
164
  }
163
- car.speed = clamp(car.speed, 0, VEHICLE_LIMITS.maxSpeed);
164
- car.heading = normalizeAngle(car.heading);
165
+ car.speed = clamp(Number.isFinite(car.speed) ? car.speed : 0, 0, VEHICLE_LIMITS.maxSpeed);
166
+ car.heading = normalizeAngle(Number.isFinite(car.heading) ? car.heading : 0);
167
+ syncExternalVelocityState(car, nextPartial, physicsMode === 'simulator');
165
168
  const centerState = nearestTrackStateForCar(track, car, car, car.progress ?? car.raceDistance);
166
169
  applyWheelSurfaceState(car, track, { centerState });
167
170
  if (
@@ -184,3 +187,34 @@ export function applyExternalCarState(car, partial, context) {
184
187
  resetTimingLineCrossings(car, time);
185
188
  resetLapTelemetry(car, time, track, totalLaps);
186
189
  }
190
+
191
+ function assertFiniteExternalCarState(partial) {
192
+ Object.entries(partial ?? {}).forEach(([key, value]) => {
193
+ if (typeof value === 'number' && !Number.isFinite(value)) {
194
+ throw new Error(`External car state ${key} must be a finite number.`);
195
+ }
196
+ });
197
+ }
198
+
199
+ function syncExternalVelocityState(car, partial, simulatorMode = false) {
200
+ const hasVelocityX = Object.hasOwn(partial, 'velocityX');
201
+ const hasVelocityY = Object.hasOwn(partial, 'velocityY');
202
+ const hasVelocity = hasVelocityX || hasVelocityY;
203
+ if (hasVelocity && (partial.velocityX == null || partial.velocityY == null)) {
204
+ car.velocityX = null;
205
+ car.velocityY = null;
206
+ return;
207
+ }
208
+ if (hasVelocity) {
209
+ const fallbackX = Math.cos(car.heading) * car.speed;
210
+ const fallbackY = Math.sin(car.heading) * car.speed;
211
+ car.velocityX = Number.isFinite(car.velocityX) ? car.velocityX : fallbackX;
212
+ car.velocityY = Number.isFinite(car.velocityY) ? car.velocityY : fallbackY;
213
+ car.speed = clamp(Math.hypot(car.velocityX, car.velocityY), 0, VEHICLE_LIMITS.maxSpeed);
214
+ return;
215
+ }
216
+ if (simulatorMode && (Object.hasOwn(partial, 'speed') || Object.hasOwn(partial, 'heading'))) {
217
+ car.velocityX = Math.cos(car.heading) * car.speed;
218
+ car.velocityY = Math.sin(car.heading) * car.speed;
219
+ }
220
+ }
@@ -121,12 +121,14 @@ export function applyWheelSurfaceState(car, track, options = {}) {
121
121
  track,
122
122
  geometrySignature: geometry.signature,
123
123
  centerSignature: centerStateSignature(options.centerState),
124
+ pitOverrideAllowed: pitOverrideAllowedForCar(car),
124
125
  };
125
126
  const cached = car.wheelSurfaceCache;
126
127
  if (
127
128
  cached?.track === cacheKey.track &&
128
129
  cached.geometrySignature === cacheKey.geometrySignature &&
129
- cached.centerSignature === cacheKey.centerSignature
130
+ cached.centerSignature === cacheKey.centerSignature &&
131
+ cached.pitOverrideAllowed === cacheKey.pitOverrideAllowed
130
132
  ) {
131
133
  car.wheelStates = cached.result.wheels;
132
134
  car.trackLimitState = cached.result.trackLimits;