@inventure71/paddockjs 3.0.0 → 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.
- package/CHANGELOG.md +146 -1
- package/INSTALL_AND_UPDATE.md +15 -4
- package/README.md +84 -562
- package/docs/architecture.md +28 -18
- package/docs/composable-layouts.md +77 -0
- package/docs/custom_model_controller.md +4 -4
- package/docs/data-helpers.md +50 -0
- package/docs/data_contract.md +9 -7
- package/docs/getting-started.md +75 -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 +49 -6
- 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 +10 -3
- 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/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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { emitCollisionPenalties } from './collisionSteward.js';
|
|
2
2
|
import { calculateTireRequirementPenalty } from './tireRequirementSteward.js';
|
|
3
3
|
import { calculateTrackLimitReview } from './trackLimitsSteward.js';
|
|
4
4
|
import { calculatePitLaneSpeedingReview } from './pitLaneSpeedingSteward.js';
|
|
@@ -6,25 +6,16 @@ import { getPenaltyRule } from '../rulesConfig.js';
|
|
|
6
6
|
import { simSpeedToKph } from '../units.js';
|
|
7
7
|
import { VEHICLE_LIMITS } from '../vehicle/vehiclePhysics.js';
|
|
8
8
|
|
|
9
|
-
function
|
|
10
|
-
return
|
|
9
|
+
function velocityComponentX(car) {
|
|
10
|
+
return Number.isFinite(car.velocityX) && Number.isFinite(car.velocityY)
|
|
11
|
+
? car.velocityX
|
|
12
|
+
: Math.cos(car.heading) * car.speed;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const forward = forwardVector(car);
|
|
18
|
-
return { x: forward.x * car.speed, y: forward.y * car.speed };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function dot(a, b) {
|
|
22
|
-
return a.x * b.x + a.y * b.y;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function normalizeVector(vector) {
|
|
26
|
-
const length = Math.hypot(vector.x, vector.y) || 1;
|
|
27
|
-
return { x: vector.x / length, y: vector.y / length };
|
|
15
|
+
function velocityComponentY(car) {
|
|
16
|
+
return Number.isFinite(car.velocityX) && Number.isFinite(car.velocityY)
|
|
17
|
+
? car.velocityY
|
|
18
|
+
: Math.sin(car.heading) * car.speed;
|
|
28
19
|
}
|
|
29
20
|
|
|
30
21
|
function progressDelta(a, b, trackLength) {
|
|
@@ -34,48 +25,62 @@ function progressDelta(a, b, trackLength) {
|
|
|
34
25
|
return delta;
|
|
35
26
|
}
|
|
36
27
|
|
|
37
|
-
function
|
|
38
|
-
const
|
|
39
|
-
?
|
|
28
|
+
function prepareCollisionStewardContext(scratch = null) {
|
|
29
|
+
const target = scratch
|
|
30
|
+
? (scratch.collisionStewardContext ??= { sharedFaultDriverIds: [] })
|
|
31
|
+
: { sharedFaultDriverIds: [] };
|
|
32
|
+
target.sharedFaultDriverIds ??= [];
|
|
33
|
+
return target;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function writeCollisionFacts(target, collision, trackLength) {
|
|
37
|
+
target.depth = collision?.depth ?? 0;
|
|
38
|
+
target.trackLength = trackLength ?? collision?.trackLength ?? null;
|
|
39
|
+
target.firstShapeId = collision?.firstShapeId ?? null;
|
|
40
|
+
target.secondShapeId = collision?.secondShapeId ?? null;
|
|
41
|
+
target.contactType = collision?.contactType ?? null;
|
|
42
|
+
target.timeOfImpact = collision?.timeOfImpact ?? null;
|
|
43
|
+
target.swept = Boolean(collision?.swept);
|
|
44
|
+
target.axis = collision?.axis ?? null;
|
|
45
|
+
target.impactSpeed = 0;
|
|
46
|
+
target.aheadDriverId = null;
|
|
47
|
+
target.atFaultDriverId = null;
|
|
48
|
+
target.sharedFault = false;
|
|
49
|
+
target.sharedFaultDriverIds.length = 0;
|
|
50
|
+
return target;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeCollisionStewardContext(target, first, second, collision, trackLength = null) {
|
|
54
|
+
writeCollisionFacts(target, collision, trackLength);
|
|
55
|
+
const distanceDelta = target.trackLength
|
|
56
|
+
? progressDelta(second.progress ?? second.raceDistance ?? 0, first.progress ?? first.raceDistance ?? 0, target.trackLength)
|
|
40
57
|
: (second.raceDistance ?? 0) - (first.raceDistance ?? 0);
|
|
41
58
|
const sideBySideTolerance = VEHICLE_LIMITS.carLength * 0.18;
|
|
42
59
|
if (Math.abs(distanceDelta) <= sideBySideTolerance) {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
impactSpeed: Math.hypot(relativeVelocity.x, relativeVelocity.y),
|
|
52
|
-
aheadDriverId: null,
|
|
53
|
-
atFaultDriverId: null,
|
|
54
|
-
sharedFault: true,
|
|
55
|
-
sharedFaultDriverIds: [first.id, second.id],
|
|
56
|
-
};
|
|
60
|
+
const relativeVelocityX = velocityComponentX(first) - velocityComponentX(second);
|
|
61
|
+
const relativeVelocityY = velocityComponentY(first) - velocityComponentY(second);
|
|
62
|
+
target.impactSpeed = Math.hypot(relativeVelocityX, relativeVelocityY);
|
|
63
|
+
target.sharedFault = true;
|
|
64
|
+
target.sharedFaultDriverIds[0] = first.id;
|
|
65
|
+
target.sharedFaultDriverIds[1] = second.id;
|
|
66
|
+
target.sharedFaultDriverIds.length = 2;
|
|
67
|
+
return target;
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
const firstBehind = distanceDelta > 0;
|
|
60
71
|
const behind = firstBehind ? first : second;
|
|
61
72
|
const ahead = firstBehind ? second : first;
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
...collision,
|
|
75
|
-
impactSpeed: Math.max(0, dot(relativeVelocity, directionBehindToAhead)),
|
|
76
|
-
aheadDriverId: ahead.id,
|
|
77
|
-
atFaultDriverId: behind.id,
|
|
78
|
-
};
|
|
73
|
+
const directionX = ahead.x - behind.x;
|
|
74
|
+
const directionY = ahead.y - behind.y;
|
|
75
|
+
const directionLength = Math.hypot(directionX, directionY) || 1;
|
|
76
|
+
const normalX = directionX / directionLength;
|
|
77
|
+
const normalY = directionY / directionLength;
|
|
78
|
+
const relativeVelocityX = velocityComponentX(behind) - velocityComponentX(ahead);
|
|
79
|
+
const relativeVelocityY = velocityComponentY(behind) - velocityComponentY(ahead);
|
|
80
|
+
target.impactSpeed = Math.max(0, relativeVelocityX * normalX + relativeVelocityY * normalY);
|
|
81
|
+
target.aheadDriverId = ahead.id;
|
|
82
|
+
target.atFaultDriverId = behind.id;
|
|
83
|
+
return target;
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
function isLegallyInsidePitLaneForTrackLimits(car) {
|
|
@@ -84,14 +89,30 @@ function isLegallyInsidePitLaneForTrackLimits(car) {
|
|
|
84
89
|
return wheels.length > 0 && wheels.some((wheel) => wheel.inPitLane);
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
|
|
92
|
+
function recordPenaltyOnSimulation(penalty, sim) {
|
|
93
|
+
sim.recordPenalty(penalty);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function reviewCollisionForSimulation(sim, first, second, collision, options = {}) {
|
|
88
97
|
const rule = getPenaltyRule(sim.rules, 'collision');
|
|
89
|
-
|
|
98
|
+
const stats = options.stats ?? sim.runtimeBenchmarkStats ?? null;
|
|
99
|
+
if (stats) stats.collisionStewardReviews = (stats.collisionStewardReviews ?? 0) + 1;
|
|
100
|
+
const context = writeCollisionStewardContext(
|
|
101
|
+
prepareCollisionStewardContext(options.scratch ?? null),
|
|
102
|
+
first,
|
|
103
|
+
second,
|
|
104
|
+
collision,
|
|
105
|
+
options.trackLength ?? null,
|
|
106
|
+
);
|
|
107
|
+
emitCollisionPenalties({
|
|
90
108
|
first,
|
|
91
109
|
second,
|
|
92
|
-
collision:
|
|
110
|
+
collision: context,
|
|
93
111
|
rule,
|
|
94
|
-
|
|
112
|
+
emit: recordPenaltyOnSimulation,
|
|
113
|
+
emitContext: sim,
|
|
114
|
+
});
|
|
115
|
+
if (stats) stats.collisionStewardPenaltyArrayAllocations = stats.collisionStewardPenaltyArrayAllocations ?? 0;
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
export function reviewTireRequirementForSimulation(sim, car) {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
serializeCar,
|
|
14
14
|
serializeObservationCar,
|
|
15
15
|
serializeRenderCar,
|
|
16
|
+
serializeRenderCarInto,
|
|
16
17
|
serializeTrainingCar,
|
|
17
18
|
} from '../vehicle/vehicleSnapshots.js';
|
|
18
19
|
import { createLapTelemetry, serializeLapTelemetry } from '../timing/raceTiming.js';
|
|
@@ -113,6 +114,33 @@ export function snapshotRaceRender(sim) {
|
|
|
113
114
|
};
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
export function snapshotRaceRenderInto(sim, target = {}) {
|
|
118
|
+
const pitLaneStatus = pitLaneStatusSnapshot(sim.raceControl, sim.track.pitLane, sim.rules.modules?.pitStops);
|
|
119
|
+
const vehicleSnapshotDependencies = createVehicleSnapshotDependencies(sim);
|
|
120
|
+
target.time = sim.time;
|
|
121
|
+
target.world = WORLD;
|
|
122
|
+
target.physicsMode = sim.physicsMode;
|
|
123
|
+
target.track = sim.track;
|
|
124
|
+
target.totalLaps = sim.totalLaps;
|
|
125
|
+
target.raceControl ??= {};
|
|
126
|
+
target.raceControl.mode = sim.raceControl.mode;
|
|
127
|
+
target.raceControl.redFlag = Boolean(sim.raceControl.redFlag);
|
|
128
|
+
target.raceControl.pitLaneOpen = pitLaneStatus.open;
|
|
129
|
+
target.raceControl.pitLaneStatus = pitLaneStatus;
|
|
130
|
+
target.raceControl.finished = sim.raceControl.finished;
|
|
131
|
+
target.raceControl.start = visibleStartState(sim);
|
|
132
|
+
target.pitLaneStatus = pitLaneStatus;
|
|
133
|
+
target.safetyCar = Object.assign(target.safetyCar ?? {}, sim.safetyCar);
|
|
134
|
+
target.cars = renderCarsForSnapshotInto(
|
|
135
|
+
target.cars ?? [],
|
|
136
|
+
sim,
|
|
137
|
+
sim.orderedCars(),
|
|
138
|
+
vehicleSnapshotDependencies,
|
|
139
|
+
);
|
|
140
|
+
target.replayGhosts = serializeReplayGhosts(sim.replayGhosts);
|
|
141
|
+
return target;
|
|
142
|
+
}
|
|
143
|
+
|
|
116
144
|
export function snapshotRaceObservation(sim) {
|
|
117
145
|
const pitLaneStatus = pitLaneStatusSnapshot(sim.raceControl, sim.track.pitLane, sim.rules.modules?.pitStops);
|
|
118
146
|
const vehicleSnapshotDependencies = createVehicleSnapshotDependencies(sim);
|
|
@@ -191,3 +219,12 @@ function carsForSnapshot(sim, ordered) {
|
|
|
191
219
|
.map((car) => ({ car, rank: affectsRaceOrder(car) ? (car.rank ?? null) : null }));
|
|
192
220
|
return [...ranked, ...unranked];
|
|
193
221
|
}
|
|
222
|
+
|
|
223
|
+
function renderCarsForSnapshotInto(targetCars, sim, ordered, dependencies) {
|
|
224
|
+
const entries = carsForSnapshot(sim, ordered);
|
|
225
|
+
targetCars.length = entries.length;
|
|
226
|
+
entries.forEach(({ car }, index) => {
|
|
227
|
+
targetCars[index] = serializeRenderCarInto(targetCars[index] ?? {}, car, dependencies);
|
|
228
|
+
});
|
|
229
|
+
return targetCars;
|
|
230
|
+
}
|
|
@@ -25,63 +25,176 @@ function isProgressInZone(track, progress, zone) {
|
|
|
25
25
|
: wrapped >= start || wrapped <= end;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function getZoneById(zones, zoneId) {
|
|
29
|
+
for (let index = 0; index < zones.length; index += 1) {
|
|
30
|
+
if (zones[index].id === zoneId) return zones[index];
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function findNextDrsZoneIndex(track, progress) {
|
|
36
|
+
const zones = track?.drsZones ?? [];
|
|
37
|
+
if (!zones.length) return -1;
|
|
38
|
+
let bestIndex = -1;
|
|
39
|
+
let bestDistance = Infinity;
|
|
40
|
+
for (let index = 0; index < zones.length; index += 1) {
|
|
41
|
+
const distance = distanceForward(progress, zones[index].start, track.length);
|
|
42
|
+
if (distance <= 1e-6 || distance >= bestDistance) continue;
|
|
43
|
+
bestDistance = distance;
|
|
44
|
+
bestIndex = index;
|
|
45
|
+
}
|
|
46
|
+
return bestIndex >= 0 ? bestIndex : 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveNextDrsZone(track, car, progress, runtimeBenchmarkStats = null) {
|
|
50
|
+
const zones = track?.drsZones ?? [];
|
|
51
|
+
if (!zones.length) {
|
|
52
|
+
car._drsNextZoneIndex = null;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const cachedIndex = car._drsNextZoneIndex;
|
|
56
|
+
if (Number.isInteger(cachedIndex) && cachedIndex >= 0 && cachedIndex < zones.length) {
|
|
57
|
+
if (runtimeBenchmarkStats) {
|
|
58
|
+
runtimeBenchmarkStats.drsNextZoneCacheHits = (runtimeBenchmarkStats.drsNextZoneCacheHits ?? 0) + 1;
|
|
59
|
+
}
|
|
60
|
+
return zones[cachedIndex];
|
|
61
|
+
}
|
|
62
|
+
const nextIndex = findNextDrsZoneIndex(track, progress);
|
|
63
|
+
car._drsNextZoneIndex = nextIndex;
|
|
64
|
+
if (runtimeBenchmarkStats) {
|
|
65
|
+
runtimeBenchmarkStats.drsNextZoneScans = (runtimeBenchmarkStats.drsNextZoneScans ?? 0) + 1;
|
|
66
|
+
}
|
|
67
|
+
return nextIndex >= 0 ? zones[nextIndex] : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function tryCachedDrsZoneNoCrossingFastPath(track, car, runtimeBenchmarkStats = null) {
|
|
71
|
+
const zones = track?.drsZones ?? [];
|
|
72
|
+
const cachedIndex = car._drsNextZoneIndex;
|
|
73
|
+
if (!Number.isInteger(cachedIndex) || cachedIndex < 0 || cachedIndex >= zones.length) return false;
|
|
74
|
+
|
|
75
|
+
const previousProgress = car.previousProgress ?? car.progress;
|
|
76
|
+
const travelled = car.progress - previousProgress;
|
|
77
|
+
if (travelled <= 0 || travelled > track.length / 2) return false;
|
|
78
|
+
|
|
79
|
+
const startOffset = zones[cachedIndex].start - previousProgress;
|
|
80
|
+
if (startOffset > 0 && startOffset <= travelled + 0.001) return false;
|
|
81
|
+
|
|
82
|
+
if (runtimeBenchmarkStats) {
|
|
83
|
+
runtimeBenchmarkStats.drsNextZoneCacheHits = (runtimeBenchmarkStats.drsNextZoneCacheHits ?? 0) + 1;
|
|
84
|
+
runtimeBenchmarkStats.drsNextZoneFastPathChecks = (runtimeBenchmarkStats.drsNextZoneFastPathChecks ?? 0) + 1;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
28
89
|
export function recordDrsDetection(car, zoneId, currentTime) {
|
|
29
|
-
const
|
|
30
|
-
const next = { passage:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
90
|
+
const detections = car.drsDetection ?? (car.drsDetection = {});
|
|
91
|
+
const next = detections[zoneId] ?? { passage: 0, time: -Infinity };
|
|
92
|
+
next.passage += 1;
|
|
93
|
+
next.time = currentTime;
|
|
94
|
+
detections[zoneId] = next;
|
|
35
95
|
return next;
|
|
36
96
|
}
|
|
37
97
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
98
|
+
function updateDrsLatchCore(
|
|
99
|
+
car,
|
|
100
|
+
ahead,
|
|
101
|
+
hasReference,
|
|
102
|
+
safetyCarDeployed,
|
|
41
103
|
time,
|
|
42
104
|
track,
|
|
43
105
|
rules,
|
|
44
|
-
|
|
106
|
+
runtimeBenchmarkStats = null,
|
|
107
|
+
) {
|
|
45
108
|
if (safetyCarDeployed || car.finished) {
|
|
46
109
|
car.drsEligible = false;
|
|
47
110
|
car.drsActive = false;
|
|
48
111
|
car.drsZoneId = null;
|
|
49
112
|
car.drsZoneEnabled = false;
|
|
113
|
+
car._drsZoneRef = null;
|
|
50
114
|
return;
|
|
51
115
|
}
|
|
52
116
|
|
|
53
117
|
const previousProgress = car.previousProgress ?? car.progress;
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
118
|
+
const zones = track.drsZones;
|
|
119
|
+
let activeZone = null;
|
|
120
|
+
if (car.drsZoneId) {
|
|
121
|
+
activeZone = car._drsZoneRef?.id === car.drsZoneId ? car._drsZoneRef : null;
|
|
122
|
+
if (!activeZone) {
|
|
123
|
+
activeZone = getZoneById(zones, car.drsZoneId);
|
|
124
|
+
if (activeZone) car._drsZoneRef = activeZone;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
57
127
|
|
|
58
|
-
if (
|
|
128
|
+
if (activeZone && !isProgressInZone(track, car.progress, activeZone)) {
|
|
59
129
|
car.drsZoneId = null;
|
|
60
130
|
car.drsZoneEnabled = false;
|
|
131
|
+
car._drsZoneRef = null;
|
|
132
|
+
activeZone = null;
|
|
61
133
|
}
|
|
62
134
|
|
|
63
135
|
if (!car.drsZoneId) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
136
|
+
if (tryCachedDrsZoneNoCrossingFastPath(track, car, runtimeBenchmarkStats)) {
|
|
137
|
+
car.drsEligible = false;
|
|
138
|
+
car.drsActive = false;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const crossedZone = resolveNextDrsZone(track, car, previousProgress, runtimeBenchmarkStats);
|
|
67
142
|
if (crossedZone) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
crossing
|
|
77
|
-
|
|
143
|
+
const nextZoneIndex = car._drsNextZoneIndex;
|
|
144
|
+
if (crossesDistance(previousProgress, car.progress, crossedZone.start, track.length)) {
|
|
145
|
+
car.drsZoneId = crossedZone.id;
|
|
146
|
+
car._drsZoneRef = crossedZone;
|
|
147
|
+
activeZone = crossedZone;
|
|
148
|
+
if (Number.isInteger(nextZoneIndex) && nextZoneIndex >= 0 && nextZoneIndex < zones.length) {
|
|
149
|
+
car._drsNextZoneIndex = findNextDrsZoneIndex(track, car.progress);
|
|
150
|
+
}
|
|
151
|
+
const crossing = recordDrsDetection(car, crossedZone.id, time);
|
|
152
|
+
const aheadCrossing = ahead?.drsDetection?.[crossedZone.id];
|
|
153
|
+
car.drsZoneEnabled = Boolean(
|
|
154
|
+
hasReference &&
|
|
155
|
+
aheadCrossing &&
|
|
156
|
+
aheadCrossing.passage === crossing.passage &&
|
|
157
|
+
crossing.time >= aheadCrossing.time &&
|
|
158
|
+
crossing.time - aheadCrossing.time <= rules.drsDetectionSeconds + 1e-6
|
|
159
|
+
);
|
|
160
|
+
}
|
|
78
161
|
}
|
|
79
162
|
}
|
|
80
163
|
|
|
81
|
-
const activeZone = car.drsZoneId
|
|
82
|
-
? track.drsZones.find((zone) => zone.id === car.drsZoneId)
|
|
83
|
-
: null;
|
|
84
164
|
const inLatchedZone = activeZone ? isProgressInZone(track, car.progress, activeZone) : false;
|
|
85
165
|
car.drsEligible = Boolean(car.drsZoneEnabled && inLatchedZone);
|
|
86
166
|
car.drsActive = car.drsEligible;
|
|
87
167
|
}
|
|
168
|
+
|
|
169
|
+
export function updateDrsLatch(car, ahead, {
|
|
170
|
+
hasReference = Boolean(ahead),
|
|
171
|
+
safetyCarDeployed = false,
|
|
172
|
+
time,
|
|
173
|
+
track,
|
|
174
|
+
rules,
|
|
175
|
+
runtimeBenchmarkStats = null,
|
|
176
|
+
} = {}) {
|
|
177
|
+
return updateDrsLatchCore(
|
|
178
|
+
car,
|
|
179
|
+
ahead,
|
|
180
|
+
hasReference,
|
|
181
|
+
safetyCarDeployed,
|
|
182
|
+
time,
|
|
183
|
+
track,
|
|
184
|
+
rules,
|
|
185
|
+
runtimeBenchmarkStats,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function updateDrsLatchForSimulation(sim, car, ahead, hasReference = Boolean(ahead)) {
|
|
190
|
+
return updateDrsLatchCore(
|
|
191
|
+
car,
|
|
192
|
+
ahead,
|
|
193
|
+
hasReference,
|
|
194
|
+
sim.safetyCar.deployed,
|
|
195
|
+
sim.time,
|
|
196
|
+
sim.track,
|
|
197
|
+
sim.rules,
|
|
198
|
+
sim.runtimeBenchmarkStats,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
@@ -3,8 +3,12 @@ import { getTimingLineCrossingTime, getTimingLineNumber } from './timingLines.js
|
|
|
3
3
|
|
|
4
4
|
export function estimateGapAheadSeconds(ahead, car, currentTime, track) {
|
|
5
5
|
if (!ahead) return Infinity;
|
|
6
|
-
const
|
|
7
|
-
|
|
6
|
+
const aheadLastStored = ahead.timingLineLastStored;
|
|
7
|
+
const carLastStored = car.timingLineLastStored;
|
|
8
|
+
if (Number.isInteger(aheadLastStored) && Number.isInteger(carLastStored)) {
|
|
9
|
+
const timingLineGap = estimateTimingLineGapSeconds(ahead, car, currentTime, track);
|
|
10
|
+
if (Number.isFinite(timingLineGap)) return timingLineGap;
|
|
11
|
+
}
|
|
8
12
|
|
|
9
13
|
const crossingTime = interpolateTimeAtDistance(ahead.timingHistory, car.raceDistance);
|
|
10
14
|
if (Number.isFinite(crossingTime)) {
|
|
@@ -22,11 +26,29 @@ export function wholeLapGap(aheadDistance, carDistance, trackLength) {
|
|
|
22
26
|
|
|
23
27
|
export function estimateTimingLineGapSeconds(ahead, car, currentTime, track) {
|
|
24
28
|
if (!ahead || !car) return Infinity;
|
|
29
|
+
const aheadLastStored = ahead.timingLineLastStored;
|
|
30
|
+
const carLastStored = car.timingLineLastStored;
|
|
31
|
+
if (!Number.isInteger(aheadLastStored) || !Number.isInteger(carLastStored)) return null;
|
|
32
|
+
const aheadFirstStored = ahead.timingLineFirstStored;
|
|
33
|
+
const carFirstStored = car.timingLineFirstStored;
|
|
34
|
+
if (!Number.isInteger(aheadFirstStored) || !Number.isInteger(carFirstStored)) return null;
|
|
25
35
|
const currentLine = getTimingLineNumber(track, Math.min(ahead.raceDistance ?? 0, car.raceDistance ?? 0));
|
|
26
36
|
if (currentLine == null) return null;
|
|
27
37
|
const oldestLine = Math.max(0, currentLine - (track.timingLines?.count ?? 0));
|
|
38
|
+
const startLine = Math.min(
|
|
39
|
+
currentLine,
|
|
40
|
+
aheadLastStored,
|
|
41
|
+
carLastStored,
|
|
42
|
+
);
|
|
43
|
+
const earliestLine = Math.max(
|
|
44
|
+
oldestLine,
|
|
45
|
+
aheadFirstStored,
|
|
46
|
+
carFirstStored,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (startLine < earliestLine) return null;
|
|
28
50
|
|
|
29
|
-
for (let lineNumber =
|
|
51
|
+
for (let lineNumber = startLine; lineNumber >= earliestLine; lineNumber -= 1) {
|
|
30
52
|
const aheadTime = getTimingLineCrossingTime(ahead, lineNumber);
|
|
31
53
|
const carTime = getTimingLineCrossingTime(car, lineNumber);
|
|
32
54
|
if (Number.isFinite(aheadTime) && Number.isFinite(carTime)) {
|