@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.
- package/CHANGELOG.md +6 -0
- package/INSTALL_AND_UPDATE.md +1 -1
- package/README.md +30 -5
- package/docs/architecture.md +15 -9
- package/docs/concepts.md +6 -4
- package/docs/data_contract.md +28 -15
- package/docs/data_model.md +1 -1
- package/docs/index.md +9 -0
- package/docs/rules.md +37 -7
- package/docs/sense_contract.md +59 -0
- package/docs/system_specs.md +56 -7
- package/docs/training.md +16 -10
- package/package.json +1 -1
- package/src/api/PaddockSimulatorController.js +2 -33
- package/src/app/BrowserExpertAdapter.js +9 -300
- package/src/app/BrowserExpertExternalRenderer.js +303 -0
- package/src/app/F1SimulatorApp.js +1 -42
- package/src/config/restartOptions.js +34 -0
- package/src/environment/controllerLoop.js +17 -2
- package/src/environment/episode.js +14 -2
- package/src/environment/index.d.ts +16 -2
- package/src/environment/observationVector.js +200 -0
- package/src/environment/observations.js +58 -285
- package/src/environment/recorder.js +13 -6
- package/src/environment/rewards.js +13 -2
- package/src/environment/runtime.js +62 -14
- package/src/environment/scenarios.js +22 -3
- package/src/environment/sensors/index.js +3 -3
- package/src/environment/sensors/opponentRadar.js +13 -3
- package/src/environment/sensors/sensorTargets.js +8 -2
- package/src/environment/sensors/surfaceRays.js +90 -26
- package/src/environment/sensors/trackRays.js +185 -22
- package/src/environment/specs.js +78 -40
- package/src/index.d.ts +2 -0
- package/src/index.js +1 -32
- package/src/simulation/pit/pitFlow.js +5 -0
- package/src/simulation/pit/pitKinematics.js +24 -0
- package/src/simulation/pit/pitQueue.js +5 -0
- package/src/simulation/pit/pitRouteMovement.js +7 -0
- package/src/simulation/pit/pitService.js +2 -0
- package/src/simulation/pit/pitTireService.js +2 -0
- package/src/simulation/race/raceLifecycle.js +2 -6
- package/src/simulation/race/raceProgress.js +6 -1
- package/src/simulation/race/raceStep.js +1 -1
- package/src/simulation/race/redFlag.js +3 -3
- package/src/simulation/race/safetyCar.js +10 -4
- package/src/simulation/race/stalledDnf.js +8 -17
- package/src/simulation/raceSimulation.js +1 -0
- package/src/simulation/rules/rulesReview.js +3 -0
- package/src/simulation/snapshots/raceSnapshots.js +1 -0
- package/src/simulation/track/trackQueryGrid.js +94 -0
- package/src/simulation/track/trackQueryIndex.js +16 -302
- package/src/simulation/track/trackQueryPitIndex.js +140 -0
- package/src/simulation/track/trackQueryScratch.js +46 -0
- package/src/simulation/track/trackQueryStats.js +28 -0
- package/src/simulation/vehicle/contactResolution.js +67 -2
- package/src/simulation/vehicle/runoffResponse.js +11 -12
- package/src/simulation/vehicle/vehicleKinematics.js +37 -0
- package/src/simulation/vehicle/vehiclePhysics.js +2 -1
- package/src/simulation/vehicle/vehicleSnapshots.js +11 -0
- package/src/simulation/vehicle/vehicleState.js +36 -2
- 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.
|
|
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.
|
|
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;
|