@inventure71/paddockjs 3.0.0 → 4.1.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.
- package/CHANGELOG.md +152 -1
- package/INSTALL_AND_UPDATE.md +21 -4
- package/README.md +103 -551
- package/docs/architecture.md +28 -18
- package/docs/component_inventory.md +5 -4
- package/docs/composable-layouts.md +79 -0
- package/docs/custom_model_controller.md +4 -4
- package/docs/data-helpers.md +50 -0
- package/docs/data_contract.md +13 -7
- package/docs/getting-started.md +105 -0
- package/docs/headless-environment.md +68 -0
- package/docs/index.md +17 -4
- package/docs/rules.md +6 -2
- package/docs/system_specs.md +60 -7
- package/docs/theming.md +64 -0
- package/docs/training.md +20 -14
- package/docs/troubleshooting.md +76 -0
- package/docs/upgrading-hosts-to-3.md +42 -0
- package/examples/python/README.md +6 -0
- package/examples/python/base_policy_server.py +19 -0
- package/package.json +21 -5
- package/src/api/PaddockSimulatorController.js +78 -22
- package/src/api/runtimeTheme.js +59 -0
- package/src/app/F1SimulatorApp.js +24 -3
- package/src/app/readouts/timingTowerRenderer.js +203 -42
- package/src/app/runtime/frameLoop.js +5 -1
- package/src/config/defaultOptions.js +8 -1
- package/src/config/themeOptions.js +5 -0
- package/src/data/index.d.ts +212 -0
- package/src/data/index.js +14 -0
- package/src/environment/episode.js +11 -1
- package/src/environment/metrics.js +59 -16
- package/src/environment/observationVector.js +50 -33
- package/src/environment/observations.js +89 -20
- package/src/environment/runtime.js +41 -0
- package/src/environment/sensors/carRays.js +48 -37
- package/src/environment/sensors/index.js +501 -97
- package/src/environment/sensors/indexedRayBands.js +134 -46
- package/src/environment/sensors/rayBandTrace.js +1171 -0
- package/src/environment/sensors/rayChannels.js +108 -0
- package/src/environment/sensors/rayConfig.js +71 -25
- package/src/environment/sensors/rayGeometry.js +23 -17
- package/src/environment/sensors/sensorTargets.js +67 -1
- package/src/environment/sensors/surfaceRays.js +130 -89
- package/src/environment/sensors/trackRays.js +115 -1
- package/src/index.d.ts +43 -0
- package/src/index.js +27 -2
- package/src/placeholder/index.d.ts +11 -0
- package/src/placeholder/index.js +61 -0
- package/src/placeholder.css +72 -0
- package/src/simulation/collisionGeometry.js +260 -96
- package/src/simulation/pit/pitFlow.js +21 -9
- package/src/simulation/pit/pitQueue.js +5 -3
- package/src/simulation/pit/pitRouteMovement.js +9 -4
- package/src/simulation/pit/pitRouting.js +77 -42
- package/src/simulation/pit/pitState.js +1 -0
- package/src/simulation/pit/pitTireService.js +7 -3
- package/src/simulation/race/raceFinish.js +25 -8
- package/src/simulation/race/raceOrder.js +189 -32
- package/src/simulation/race/raceProgress.js +129 -37
- package/src/simulation/race/raceSetup.js +2 -0
- package/src/simulation/race/raceStep.js +25 -13
- package/src/simulation/raceSimulation.js +20 -17
- package/src/simulation/rules/collisionSteward.js +40 -23
- package/src/simulation/rules/rulesReview.js +77 -56
- package/src/simulation/snapshots/raceSnapshots.js +37 -0
- package/src/simulation/timing/drsTiming.js +143 -30
- package/src/simulation/timing/gapEstimation.js +25 -3
- package/src/simulation/timing/lapTelemetry.js +115 -36
- package/src/simulation/timing/raceTiming.js +5 -1
- package/src/simulation/timing/sectorPerformance.js +104 -26
- package/src/simulation/timing/timingHistory.js +229 -14
- package/src/simulation/timing/timingLines.js +65 -7
- package/src/simulation/track/pitLaneLayout.js +23 -0
- package/src/simulation/track/pitLaneState.js +218 -19
- package/src/simulation/track/spatialQueries.js +67 -11
- package/src/simulation/track/trackMath.js +91 -24
- package/src/simulation/track/trackModel.js +2 -0
- package/src/simulation/track/trackQueryIndex.js +1334 -100
- package/src/simulation/track/trackQueryPitIndex.js +138 -14
- package/src/simulation/track/trackQueryScratch.js +7 -0
- package/src/simulation/track/trackQueryStats.js +25 -0
- package/src/simulation/track/trackSnapshotJson.js +139 -0
- package/src/simulation/track/trackStatePolicy.js +249 -1
- package/src/simulation/vehicle/contactResolution.js +96 -59
- package/src/simulation/vehicle/mainTrackWheelSurface.js +73 -34
- package/src/simulation/vehicle/pitWheelSurface.js +81 -28
- package/src/simulation/vehicle/runoffResponse.js +54 -29
- package/src/simulation/vehicle/vehicleGeometry.js +245 -51
- package/src/simulation/vehicle/vehiclePhysics.js +42 -27
- package/src/simulation/vehicle/vehicleSnapshots.js +23 -0
- package/src/simulation/vehicle/vehicleState.js +6 -0
- package/src/simulation/vehicle/wheelSurface.js +807 -92
- package/src/styles.css +497 -497
- package/src/ui/raceControlsTemplate.js +1 -0
- package/src/ui/telemetryDrawerTemplate.js +1 -0
- package/src/ui/telemetryTemplates.js +1 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { pointAt } from '../track/trackModel.js';
|
|
2
2
|
import { applyWheelSurfaceState } from '../vehicle/wheelSurface.js';
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
clearRunoffCenterState,
|
|
5
|
+
takeValidRunoffCenterState,
|
|
6
|
+
} from '../vehicle/runoffResponse.js';
|
|
7
|
+
import { affectsRaceOrder } from '../participants/participantInteractions.js';
|
|
6
8
|
import {
|
|
7
9
|
estimateGapAheadSeconds,
|
|
8
10
|
recordTimingLineCrossings,
|
|
@@ -13,13 +15,14 @@ import {
|
|
|
13
15
|
wholeLapGap,
|
|
14
16
|
} from '../timing/raceTiming.js';
|
|
15
17
|
import { progressDelta } from './raceDistance.js';
|
|
18
|
+
import { assignDrsReferenceCarsForSimulation } from './raceOrder.js';
|
|
16
19
|
import { isRaceDnf } from './retirements.js';
|
|
17
20
|
import { freezeVehicleMotion } from '../vehicle/vehicleKinematics.js';
|
|
18
21
|
|
|
19
|
-
export function
|
|
22
|
+
export function refreshLocalRaceStateForSimulation(sim) {
|
|
20
23
|
sim.cars.forEach((car) => {
|
|
21
|
-
const previousRaceDistance = car.raceDistance;
|
|
22
24
|
if (car.destroyed || car.outOfRace) {
|
|
25
|
+
clearRunoffCenterState(car);
|
|
23
26
|
applyWheelSurfaceState(car, sim.track);
|
|
24
27
|
freezeVehicleMotion(car, {
|
|
25
28
|
clearManualControls: true,
|
|
@@ -33,52 +36,134 @@ export function recalculateRaceStateForSimulation(sim, { updateDrs = true } = {}
|
|
|
33
36
|
car.drsZoneEnabled = false;
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
39
|
+
const centerState = takeValidRunoffCenterState(car);
|
|
40
|
+
if (centerState) {
|
|
41
|
+
applyWheelSurfaceState(car, sim.track, { centerState, cacheAsAuto: true });
|
|
42
|
+
} else {
|
|
43
|
+
applyWheelSurfaceState(car, sim.track);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveLeaderGapSeconds({
|
|
49
|
+
sim,
|
|
50
|
+
leader,
|
|
51
|
+
ahead,
|
|
52
|
+
car,
|
|
53
|
+
leaderGapLaps,
|
|
54
|
+
gapAheadSeconds,
|
|
55
|
+
currentTime,
|
|
56
|
+
track,
|
|
57
|
+
}) {
|
|
58
|
+
if (leaderGapLaps > 0) return Infinity;
|
|
59
|
+
if (!leader || leader === car) return 0;
|
|
60
|
+
if (!ahead) return Infinity;
|
|
61
|
+
if (ahead === leader) {
|
|
62
|
+
if (sim?.runtimeBenchmarkStats) {
|
|
63
|
+
sim.runtimeBenchmarkStats.leaderGapAccumulated = (sim.runtimeBenchmarkStats.leaderGapAccumulated ?? 0) + 1;
|
|
64
|
+
}
|
|
65
|
+
return gapAheadSeconds;
|
|
66
|
+
}
|
|
67
|
+
if (Number.isFinite(gapAheadSeconds) && Number.isFinite(ahead.leaderGapSeconds)) {
|
|
68
|
+
if (sim?.runtimeBenchmarkStats) {
|
|
69
|
+
sim.runtimeBenchmarkStats.leaderGapAccumulated = (sim.runtimeBenchmarkStats.leaderGapAccumulated ?? 0) + 1;
|
|
70
|
+
}
|
|
71
|
+
return ahead.leaderGapSeconds + gapAheadSeconds;
|
|
72
|
+
}
|
|
73
|
+
if (sim?.runtimeBenchmarkStats) {
|
|
74
|
+
sim.runtimeBenchmarkStats.leaderGapFallbackEstimates = (sim.runtimeBenchmarkStats.leaderGapFallbackEstimates ?? 0) + 1;
|
|
75
|
+
}
|
|
76
|
+
return estimateGapAheadSeconds(leader, car, currentTime, track);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function recordTimingStateForCar(car, currentTime, track, previousRaceDistanceForTiming, runtimeBenchmarkStats) {
|
|
80
|
+
recordTimingSample(car, currentTime);
|
|
81
|
+
recordTimingLineCrossings(car, previousRaceDistanceForTiming, currentTime, track);
|
|
82
|
+
car.previousRaceDistanceForTiming = car.raceDistance;
|
|
83
|
+
if (runtimeBenchmarkStats) {
|
|
84
|
+
runtimeBenchmarkStats.timingStateUpdates = (runtimeBenchmarkStats.timingStateUpdates ?? 0) + 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function recalculateRaceStateForSimulation(sim, { updateDrs = true, refreshSurfaces = true } = {}) {
|
|
89
|
+
let sectorPerformanceDirty = false;
|
|
90
|
+
sim.cars.forEach((car) => {
|
|
91
|
+
const previousRaceDistance = car.raceDistance;
|
|
92
|
+
const previousRaceDistanceForTiming = car.previousRaceDistanceForTiming;
|
|
93
|
+
if (car.destroyed || car.outOfRace) {
|
|
94
|
+
if (refreshSurfaces) applyWheelSurfaceState(car, sim.track);
|
|
95
|
+
freezeVehicleMotion(car, {
|
|
96
|
+
clearManualControls: true,
|
|
97
|
+
physicsMode: sim.physicsMode,
|
|
98
|
+
stabilityState: car.destroyed ? 'destroyed' : car.stabilityState,
|
|
99
|
+
});
|
|
100
|
+
car.canAttack = false;
|
|
101
|
+
car.drsEligible = false;
|
|
102
|
+
car.drsActive = false;
|
|
103
|
+
car.drsZoneId = null;
|
|
104
|
+
car.drsZoneEnabled = false;
|
|
105
|
+
recordTimingStateForCar(car, sim.time, sim.track, previousRaceDistanceForTiming, sim.runtimeBenchmarkStats);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
36
108
|
if (car.gridLocked) {
|
|
37
109
|
const gridPoint = pointAt(sim.track, car.gridDistance);
|
|
38
|
-
applyWheelSurfaceState(car, sim.track);
|
|
110
|
+
if (refreshSurfaces) applyWheelSurfaceState(car, sim.track);
|
|
39
111
|
car.progress = gridPoint.distance;
|
|
40
112
|
car.raceDistance = car.gridDistance;
|
|
41
113
|
car.lap = 1;
|
|
42
114
|
resetLapTelemetry(car, sim.time, sim.track, sim.totalLaps);
|
|
115
|
+
sectorPerformanceDirty = true;
|
|
116
|
+
recordTimingStateForCar(car, sim.time, sim.track, previousRaceDistanceForTiming, sim.runtimeBenchmarkStats);
|
|
43
117
|
return;
|
|
44
118
|
}
|
|
45
119
|
|
|
46
|
-
applyWheelSurfaceState(car, sim.track);
|
|
120
|
+
if (refreshSurfaces) applyWheelSurfaceState(car, sim.track);
|
|
47
121
|
const previousProgress = car.progress ?? car.trackState.distance;
|
|
48
122
|
const delta = progressDelta(car.trackState.distance, previousProgress, sim.track.length);
|
|
49
123
|
car.raceDistance = (car.raceDistance ?? previousProgress) + delta;
|
|
50
124
|
car.progress = car.trackState.distance;
|
|
51
125
|
car.lap = sim.computeLap(car.raceDistance);
|
|
52
|
-
updateLapTelemetry(car, previousRaceDistance, sim.time, sim.track, sim.totalLaps);
|
|
126
|
+
sectorPerformanceDirty = updateLapTelemetry(car, previousRaceDistance, sim.time, sim.track, sim.totalLaps) || sectorPerformanceDirty;
|
|
127
|
+
recordTimingStateForCar(car, sim.time, sim.track, previousRaceDistanceForTiming, sim.runtimeBenchmarkStats);
|
|
53
128
|
});
|
|
54
129
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
car.previousRaceDistanceForTiming = car.raceDistance;
|
|
60
|
-
});
|
|
130
|
+
if (sectorPerformanceDirty) {
|
|
131
|
+
sim.runtimeBenchmarkStats && (sim.runtimeBenchmarkStats.sectorPerformanceDirtyCommits = (sim.runtimeBenchmarkStats.sectorPerformanceDirtyCommits ?? 0) + 1);
|
|
132
|
+
}
|
|
133
|
+
updateSectorPerformance(sim.cars, sim.runtimeBenchmarkStats);
|
|
61
134
|
|
|
62
135
|
const ordered = sim.orderedCars();
|
|
63
|
-
const
|
|
64
|
-
sim.cars.
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
car
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
136
|
+
const drsReferenceByIndex = updateDrs ? assignDrsReferenceCarsForSimulation(sim, ordered) : null;
|
|
137
|
+
if (ordered.length !== sim.cars.length) {
|
|
138
|
+
if (sim?.runtimeBenchmarkStats) {
|
|
139
|
+
sim.runtimeBenchmarkStats.excludedCarResetScans = (sim.runtimeBenchmarkStats.excludedCarResetScans ?? 0) + 1;
|
|
140
|
+
}
|
|
141
|
+
sim.cars.forEach((car) => {
|
|
142
|
+
if (affectsRaceOrder(car)) return;
|
|
143
|
+
car.rank = null;
|
|
144
|
+
car.classifiedRank = null;
|
|
145
|
+
car.gapAhead = Infinity;
|
|
146
|
+
car.gapAheadLaps = 0;
|
|
147
|
+
car.intervalAheadLaps = 0;
|
|
148
|
+
car.leaderGapLaps = 0;
|
|
149
|
+
car.gapAheadSeconds = Infinity;
|
|
150
|
+
car.intervalAheadSeconds = Infinity;
|
|
151
|
+
car.leaderGapSeconds = Infinity;
|
|
152
|
+
car.drsEligible = false;
|
|
153
|
+
car.drsActive = false;
|
|
154
|
+
car.drsZoneId = null;
|
|
155
|
+
car.drsZoneEnabled = false;
|
|
156
|
+
car.canAttack = false;
|
|
157
|
+
});
|
|
158
|
+
} else if (sim?.runtimeBenchmarkStats) {
|
|
159
|
+
sim.runtimeBenchmarkStats.excludedCarResetSkips = (sim.runtimeBenchmarkStats.excludedCarResetSkips ?? 0) + 1;
|
|
160
|
+
}
|
|
81
161
|
const leader = ordered[0];
|
|
162
|
+
let liveRaceOrderCount = ordered.length;
|
|
163
|
+
while (liveRaceOrderCount > 0 && isRaceDnf(ordered[liveRaceOrderCount - 1])) {
|
|
164
|
+
liveRaceOrderCount -= 1;
|
|
165
|
+
}
|
|
166
|
+
const aggressionFieldDepth = Math.max(1, liveRaceOrderCount - 1);
|
|
82
167
|
ordered.forEach((car, index) => {
|
|
83
168
|
if (isRaceDnf(car)) {
|
|
84
169
|
car.rank = index + 1;
|
|
@@ -98,7 +183,7 @@ export function recalculateRaceStateForSimulation(sim, { updateDrs = true } = {}
|
|
|
98
183
|
return;
|
|
99
184
|
}
|
|
100
185
|
const ahead = ordered[index - 1];
|
|
101
|
-
const drsReference =
|
|
186
|
+
const drsReference = drsReferenceByIndex?.[car.index] ?? null;
|
|
102
187
|
const gap = ahead ? ahead.raceDistance - car.raceDistance : Infinity;
|
|
103
188
|
const intervalAheadLaps = ahead ? wholeLapGap(ahead.raceDistance, car.raceDistance, sim.track.length) : 0;
|
|
104
189
|
const leaderGapLaps = leader ? wholeLapGap(leader.raceDistance, car.raceDistance, sim.track.length) : 0;
|
|
@@ -112,11 +197,18 @@ export function recalculateRaceStateForSimulation(sim, { updateDrs = true } = {}
|
|
|
112
197
|
? estimateGapAheadSeconds(ahead, car, sim.time, sim.track)
|
|
113
198
|
: Infinity;
|
|
114
199
|
car.intervalAheadSeconds = car.gapAheadSeconds;
|
|
115
|
-
car.leaderGapSeconds =
|
|
116
|
-
|
|
117
|
-
|
|
200
|
+
car.leaderGapSeconds = resolveLeaderGapSeconds({
|
|
201
|
+
sim,
|
|
202
|
+
leader,
|
|
203
|
+
ahead,
|
|
204
|
+
car,
|
|
205
|
+
leaderGapLaps,
|
|
206
|
+
gapAheadSeconds: car.gapAheadSeconds,
|
|
207
|
+
currentTime: sim.time,
|
|
208
|
+
track: sim.track,
|
|
209
|
+
});
|
|
118
210
|
car.canAttack = !sim.safetyCar.deployed && !car.finished && !activePitStop;
|
|
119
|
-
car.aggression = sim.computeAggression(car, index);
|
|
211
|
+
car.aggression = sim.computeAggression(car, index, aggressionFieldDepth);
|
|
120
212
|
if (activePitStop) {
|
|
121
213
|
car.drsEligible = false;
|
|
122
214
|
car.drsActive = false;
|
|
@@ -124,6 +216,6 @@ export function recalculateRaceStateForSimulation(sim, { updateDrs = true } = {}
|
|
|
124
216
|
car.drsZoneEnabled = false;
|
|
125
217
|
} else if (updateDrs) sim.updateDrsLatch(car, drsReference, Boolean(drsReference));
|
|
126
218
|
});
|
|
127
|
-
sim.evaluateRaceFinish();
|
|
219
|
+
sim.evaluateRaceFinish(ordered);
|
|
128
220
|
return ordered;
|
|
129
221
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildTrackModel, createProceduralTrack, TRACK } from '../track/trackModel.js';
|
|
2
2
|
import { attachTrackQueryIndex, createTrackQueryIndex, forkTrackQueryIndex } from '../track/trackQueryIndex.js';
|
|
3
|
+
import { attachTrackSnapshotJsonSerializer } from '../track/trackSnapshotJson.js';
|
|
3
4
|
import { createMulberry32 } from '../simMath.js';
|
|
4
5
|
import { createCar } from '../vehicle/vehicleState.js';
|
|
5
6
|
import { normalizePhysicsMode } from '../vehicle/vehiclePhysics.js';
|
|
@@ -78,6 +79,7 @@ export function initializeRaceSimulation(simulation, {
|
|
|
78
79
|
tireCompounds: simulation.rules.modules?.tireStrategy?.compounds,
|
|
79
80
|
PIT_INTENT_NONE,
|
|
80
81
|
});
|
|
82
|
+
attachTrackSnapshotJsonSerializer(simulation.track);
|
|
81
83
|
simulation.recalculateRaceState({ updateDrs: false });
|
|
82
84
|
simulation.cars.forEach((car) => resetTimingHistory(car, simulation.time));
|
|
83
85
|
simulation.cars.forEach((car) => resetTimingLineCrossings(car, simulation.time));
|
|
@@ -6,6 +6,13 @@ import { applyWheelSurfaceState } from '../vehicle/wheelSurface.js';
|
|
|
6
6
|
import { applyRedFlagHoldForSimulation } from './redFlag.js';
|
|
7
7
|
import { freezeRetiredCar, updateStalledDnfForSimulation } from './stalledDnf.js';
|
|
8
8
|
|
|
9
|
+
function measureRuntimePhase(simulation, name, run) {
|
|
10
|
+
const profiler = simulation.runtimeProfiler;
|
|
11
|
+
return typeof profiler?.measure === 'function'
|
|
12
|
+
? profiler.measure(name, run)
|
|
13
|
+
: run();
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export function runRaceStep(simulation, dt) {
|
|
10
17
|
const delta = clamp(dt, 0, 1 / 20);
|
|
11
18
|
if (!Number.isFinite(delta) || delta <= 0) return;
|
|
@@ -14,22 +21,22 @@ export function runRaceStep(simulation, dt) {
|
|
|
14
21
|
simulation.events = [];
|
|
15
22
|
updateReplayGhosts(simulation.replayGhosts, simulation.time);
|
|
16
23
|
simulation.updateStartSequence();
|
|
17
|
-
const orderedCars = simulation.recalculateRaceState({ updateDrs: false }) ?? simulation.orderedCars();
|
|
18
24
|
|
|
19
25
|
if (simulation.raceControl.mode === 'pre-start' && simulation.cars.every((car) => car.gridLocked)) {
|
|
20
26
|
simulation.holdGridCars();
|
|
21
|
-
simulation.recalculateRaceState({ updateDrs: false });
|
|
27
|
+
measureRuntimePhase(simulation, 'broadRaceCommit', () => simulation.recalculateRaceState({ updateDrs: false }));
|
|
22
28
|
return;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
if (simulation.raceControl.redFlag) {
|
|
26
32
|
applyRedFlagHoldForSimulation(simulation);
|
|
27
|
-
simulation.recalculateRaceState({ updateDrs: false });
|
|
33
|
+
measureRuntimePhase(simulation, 'broadRaceCommit', () => simulation.recalculateRaceState({ updateDrs: false }));
|
|
28
34
|
return;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
simulation.updateSafetyCar(delta);
|
|
32
38
|
|
|
39
|
+
const orderedCars = simulation.orderedCars();
|
|
33
40
|
const raceContext = simulation.driverRaceContext(orderedCars);
|
|
34
41
|
const orderedIndexById = new Map(orderedCars.map((car, index) => [car.id, index]));
|
|
35
42
|
const driveCars = orderedCars.length === simulation.cars.length
|
|
@@ -60,21 +67,26 @@ export function runRaceStep(simulation, dt) {
|
|
|
60
67
|
brake: controls.brake ?? 0,
|
|
61
68
|
};
|
|
62
69
|
if (simulation.physicsMode === 'advanced') {
|
|
63
|
-
|
|
70
|
+
measureRuntimePhase(simulation, 'prePhysicsWheelSurface', () => {
|
|
71
|
+
applyWheelSurfaceState(car, simulation.track);
|
|
72
|
+
});
|
|
64
73
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
measureRuntimePhase(simulation, 'physicsIntegration', () => {
|
|
75
|
+
integrateVehiclePhysics(car, controls, delta, {
|
|
76
|
+
physicsMode: simulation.physicsMode,
|
|
77
|
+
tireDegradationEnabled: simulation.rules.modules?.tireDegradation?.enabled !== false,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
measureRuntimePhase(simulation, 'runoffResponse', () => {
|
|
81
|
+
simulation.applyRunoffResponse(car);
|
|
68
82
|
});
|
|
69
|
-
simulation.applyRunoffResponse(car);
|
|
70
83
|
car.contactCooldown = Math.max(0, car.contactCooldown - delta);
|
|
71
84
|
});
|
|
72
85
|
|
|
73
|
-
simulation.resolveCollisions();
|
|
74
|
-
simulation.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
86
|
+
measureRuntimePhase(simulation, 'collisions', () => simulation.resolveCollisions());
|
|
87
|
+
measureRuntimePhase(simulation, 'localSurfaceRefresh', () => simulation.refreshLocalRaceState());
|
|
88
|
+
updateStalledDnfForSimulation(simulation, delta);
|
|
89
|
+
measureRuntimePhase(simulation, 'broadRaceCommit', () => simulation.recalculateRaceState({ refreshSurfaces: false }));
|
|
78
90
|
simulation.reviewTrackLimits();
|
|
79
91
|
simulation.reviewPitLaneSpeeding();
|
|
80
92
|
}
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from './vehicle/contactResolution.js';
|
|
22
22
|
import { applyRunoffResponseForSimulation } from './vehicle/runoffResponse.js';
|
|
23
23
|
import { resetLapTelemetry, resetTimingHistory, resetTimingLineCrossings } from './timing/raceTiming.js';
|
|
24
|
-
import {
|
|
24
|
+
import { updateDrsLatchForSimulation as updateDrsLatchState } from './timing/drsTiming.js';
|
|
25
25
|
import {
|
|
26
26
|
PIT_INTENT_NONE,
|
|
27
27
|
setPitIntentForSimulation,
|
|
@@ -87,7 +87,7 @@ import {
|
|
|
87
87
|
finishDistanceForRace,
|
|
88
88
|
progressDelta,
|
|
89
89
|
} from './race/raceDistance.js';
|
|
90
|
-
import { recalculateRaceStateForSimulation } from './race/raceProgress.js';
|
|
90
|
+
import { recalculateRaceStateForSimulation, refreshLocalRaceStateForSimulation } from './race/raceProgress.js';
|
|
91
91
|
import { evaluateRaceFinishForSimulation } from './race/raceFinish.js';
|
|
92
92
|
import { applyGridDropForSimulation } from './race/gridPenalties.js';
|
|
93
93
|
import { initializeRaceSimulation, normalizePitIntentForRace } from './race/raceSetup.js';
|
|
@@ -98,6 +98,7 @@ import {
|
|
|
98
98
|
snapshotRace,
|
|
99
99
|
snapshotRaceObservation,
|
|
100
100
|
snapshotRaceRender,
|
|
101
|
+
snapshotRaceRenderInto,
|
|
101
102
|
snapshotRaceTraining,
|
|
102
103
|
} from './snapshots/raceSnapshots.js';
|
|
103
104
|
import {
|
|
@@ -370,8 +371,8 @@ export class F1RaceSimulation {
|
|
|
370
371
|
applyGridDropForSimulation(this, driverId, positions);
|
|
371
372
|
}
|
|
372
373
|
|
|
373
|
-
reviewCollision(first, second, collision) {
|
|
374
|
-
reviewCollisionForSimulation(this, first, second, collision);
|
|
374
|
+
reviewCollision(first, second, collision, options) {
|
|
375
|
+
reviewCollisionForSimulation(this, first, second, collision, options);
|
|
375
376
|
}
|
|
376
377
|
|
|
377
378
|
reviewTireRequirement(car) {
|
|
@@ -400,6 +401,10 @@ export class F1RaceSimulation {
|
|
|
400
401
|
return snapshotRaceRender(this);
|
|
401
402
|
}
|
|
402
403
|
|
|
404
|
+
snapshotRenderInto(target = {}) {
|
|
405
|
+
return snapshotRaceRenderInto(this, target);
|
|
406
|
+
}
|
|
407
|
+
|
|
403
408
|
snapshotObservation() {
|
|
404
409
|
return snapshotRaceObservation(this);
|
|
405
410
|
}
|
|
@@ -420,8 +425,8 @@ export class F1RaceSimulation {
|
|
|
420
425
|
return driverRaceContextForSimulation(this, orderedCars);
|
|
421
426
|
}
|
|
422
427
|
|
|
423
|
-
computeAggression(car, orderIndex = Math.max(0, (car.rank ?? 1) - 1)) {
|
|
424
|
-
return computeAggressionForSimulation(this, car, orderIndex);
|
|
428
|
+
computeAggression(car, orderIndex = Math.max(0, (car.rank ?? 1) - 1), fieldDepth = null) {
|
|
429
|
+
return computeAggressionForSimulation(this, car, orderIndex, fieldDepth);
|
|
425
430
|
}
|
|
426
431
|
|
|
427
432
|
updateStartSequence() {
|
|
@@ -448,12 +453,16 @@ export class F1RaceSimulation {
|
|
|
448
453
|
applyRunoffResponseForSimulation(this, car);
|
|
449
454
|
}
|
|
450
455
|
|
|
451
|
-
recalculateRaceState({ updateDrs = true } = {}) {
|
|
452
|
-
return recalculateRaceStateForSimulation(this, { updateDrs });
|
|
456
|
+
recalculateRaceState({ updateDrs = true, refreshSurfaces = true } = {}) {
|
|
457
|
+
return recalculateRaceStateForSimulation(this, { updateDrs, refreshSurfaces });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
refreshLocalRaceState() {
|
|
461
|
+
refreshLocalRaceStateForSimulation(this);
|
|
453
462
|
}
|
|
454
463
|
|
|
455
|
-
evaluateRaceFinish() {
|
|
456
|
-
evaluateRaceFinishForSimulation(this);
|
|
464
|
+
evaluateRaceFinish(orderedCars = undefined) {
|
|
465
|
+
evaluateRaceFinishForSimulation(this, orderedCars);
|
|
457
466
|
}
|
|
458
467
|
|
|
459
468
|
applyOutstandingServicePenalties() {
|
|
@@ -481,13 +490,7 @@ export class F1RaceSimulation {
|
|
|
481
490
|
}
|
|
482
491
|
|
|
483
492
|
updateDrsLatch(car, ahead, hasReference = Boolean(ahead)) {
|
|
484
|
-
updateDrsLatchState(car, ahead,
|
|
485
|
-
hasReference,
|
|
486
|
-
safetyCarDeployed: this.safetyCar.deployed,
|
|
487
|
-
time: this.time,
|
|
488
|
-
track: this.track,
|
|
489
|
-
rules: this.rules,
|
|
490
|
-
});
|
|
493
|
+
updateDrsLatchState(this, car, ahead, hasReference);
|
|
491
494
|
}
|
|
492
495
|
|
|
493
496
|
resolveCollisions() {
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { simSpeedToKph } from '../units.js';
|
|
2
2
|
|
|
3
3
|
export function calculateCollisionPenalties({ first, second, collision = null, rule }) {
|
|
4
|
-
|
|
4
|
+
const penalties = [];
|
|
5
|
+
emitCollisionPenalties({
|
|
6
|
+
first,
|
|
7
|
+
second,
|
|
8
|
+
collision,
|
|
9
|
+
rule,
|
|
10
|
+
emit: (penalty) => {
|
|
11
|
+
penalties.push(penalty);
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
return penalties;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function emitCollisionPenalties({ first, second, collision = null, rule, emit, emitContext = null }) {
|
|
18
|
+
if (!rule || typeof emit !== 'function') return 0;
|
|
5
19
|
const severity = collision?.depth ?? 0;
|
|
6
20
|
const severityThreshold = (rule.minimumSeverity ?? 0) + (rule.relaxedSeverityMargin ?? 0) * (1 - rule.strictness);
|
|
7
21
|
const impactSpeed = collision?.impactSpeed ?? 0;
|
|
@@ -12,41 +26,44 @@ export function calculateCollisionPenalties({ first, second, collision = null, r
|
|
|
12
26
|
? collision.sharedFaultDriverIds
|
|
13
27
|
: [];
|
|
14
28
|
|
|
15
|
-
if (severity < severityThreshold) return
|
|
16
|
-
if (impactSpeed < impactSpeedThreshold) return
|
|
29
|
+
if (severity < severityThreshold) return 0;
|
|
30
|
+
if (impactSpeed < impactSpeedThreshold) return 0;
|
|
17
31
|
|
|
32
|
+
let emitted = 0;
|
|
18
33
|
if (sharedFaultDriverIds.length > 0) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
for (let index = 0; index < sharedFaultDriverIds.length; index += 1) {
|
|
35
|
+
const driverId = sharedFaultDriverIds[index];
|
|
36
|
+
const car = first.id === driverId ? first : second.id === driverId ? second : null;
|
|
37
|
+
if (!car) continue;
|
|
38
|
+
const other = first.id === driverId ? second : first;
|
|
39
|
+
emit(buildCollisionPenalty(car, other, rule, {
|
|
40
|
+
severity,
|
|
41
|
+
severityThreshold,
|
|
42
|
+
impactSpeed,
|
|
43
|
+
impactSpeedThreshold,
|
|
44
|
+
aheadDriverId: null,
|
|
45
|
+
atFaultDriverId: driverId,
|
|
46
|
+
sharedFault: true,
|
|
47
|
+
reason: 'Unclear collision responsibility',
|
|
48
|
+
}), emitContext);
|
|
49
|
+
emitted += 1;
|
|
50
|
+
}
|
|
51
|
+
return emitted;
|
|
36
52
|
}
|
|
37
53
|
|
|
38
|
-
if (!atFaultDriverId || !aheadDriverId) return
|
|
54
|
+
if (!atFaultDriverId || !aheadDriverId) return 0;
|
|
39
55
|
|
|
40
56
|
const atFault = first.id === atFaultDriverId ? first : second;
|
|
41
57
|
const other = first.id === atFaultDriverId ? second : first;
|
|
42
|
-
|
|
58
|
+
emit(buildCollisionPenalty(atFault, other, rule, {
|
|
43
59
|
severity,
|
|
44
60
|
severityThreshold,
|
|
45
61
|
impactSpeed,
|
|
46
62
|
impactSpeedThreshold,
|
|
47
63
|
aheadDriverId,
|
|
48
64
|
atFaultDriverId,
|
|
49
|
-
})
|
|
65
|
+
}), emitContext);
|
|
66
|
+
return 1;
|
|
50
67
|
}
|
|
51
68
|
|
|
52
69
|
function buildCollisionPenalty(car, other, rule, context) {
|