@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.
- package/CHANGELOG.md +176 -1
- package/INSTALL_AND_UPDATE.md +24 -4
- package/README.md +84 -512
- package/docs/architecture.md +38 -24
- package/docs/component_inventory.md +72 -0
- package/docs/composable-layouts.md +77 -0
- package/docs/concepts.md +2 -2
- package/docs/custom_model_controller.md +6 -6
- package/docs/data-helpers.md +50 -0
- package/docs/data_contract.md +104 -27
- package/docs/getting-started.md +75 -0
- package/docs/headless-environment.md +68 -0
- package/docs/index.md +18 -4
- package/docs/learnings.md +6 -6
- package/docs/rules.md +12 -8
- package/docs/sense_contract.md +1 -1
- package/docs/system_specs.md +71 -21
- package/docs/theming.md +64 -0
- package/docs/training.md +26 -19
- 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 +150 -24
- package/src/api/runtimeTheme.js +59 -0
- package/src/app/F1SimulatorApp.js +213 -29
- package/src/app/banners/raceDataBannerController.js +71 -36
- package/src/app/banners/stewardMessageController.js +5 -4
- package/src/app/camera/cameraConstants.js +2 -0
- package/src/app/camera/cameraController.js +86 -29
- package/src/app/camera/cameraGeometry.js +12 -1
- package/src/app/domBindings.js +89 -9
- package/src/app/layoutScheduler.js +27 -0
- package/src/app/layoutSupport.js +165 -0
- package/src/app/raceOverlayClearanceSupport.js +158 -0
- package/src/app/readouts/carOverviewRenderer.js +16 -8
- package/src/app/readouts/controlStateRenderer.js +1 -1
- package/src/app/readouts/raceStatusRenderer.js +30 -21
- package/src/app/readouts/readoutFormatters.js +2 -0
- package/src/app/readouts/telemetryRenderer.js +17 -8
- package/src/app/readouts/timingTowerRenderer.js +262 -49
- package/src/app/runtime/frameLoop.js +5 -1
- package/src/app/runtime/runtimeVisibility.js +66 -5
- package/src/config/cssValues.js +16 -0
- package/src/config/defaultOptions.js +58 -38
- package/src/config/themeOptions.js +830 -0
- package/src/config/timingGapMode.js +13 -0
- package/src/config/urlOptions.js +25 -0
- package/src/data/championship.js +14 -9
- package/src/data/index.d.ts +212 -0
- package/src/data/index.js +14 -0
- package/src/environment/controllerLoop.js +17 -3
- package/src/environment/episode.js +11 -1
- package/src/environment/index.d.ts +1 -2
- package/src/environment/metrics.js +59 -16
- package/src/environment/observationVector.js +50 -33
- package/src/environment/observations.js +89 -20
- package/src/environment/options.js +1 -1
- package/src/environment/runtime.js +44 -11
- package/src/environment/scenarios.js +1 -1
- 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 +167 -118
- package/src/environment/sensors/trackRays.js +157 -48
- package/src/index.d.ts +153 -9
- package/src/index.js +52 -3
- package/src/simulation/collisionGeometry.js +260 -96
- package/src/simulation/driver/driverController.js +1 -1
- package/src/simulation/driver/edgeRecovery.js +1 -1
- package/src/simulation/driver/racingControls.js +2 -2
- package/src/simulation/driver/rejoinControls.js +2 -2
- package/src/simulation/pit/pitFlow.js +21 -9
- package/src/simulation/pit/pitKinematics.js +2 -2
- package/src/simulation/pit/pitQueue.js +5 -3
- package/src/simulation/pit/pitRouteMovement.js +9 -4
- package/src/simulation/pit/pitRouting.js +79 -46
- 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 +4 -6
- package/src/simulation/race/raceStep.js +26 -14
- package/src/simulation/race/safetyCar.js +1 -1
- package/src/simulation/race/stalledDnf.js +1 -1
- 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 +138 -40
- 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 +30 -3
- package/src/simulation/track/pitLaneState.js +218 -19
- package/src/simulation/track/spatialQueries.js +66 -33
- package/src/simulation/track/trackMath.js +91 -24
- package/src/simulation/track/trackModel.js +2 -0
- package/src/simulation/track/trackQueryIndex.js +1468 -189
- 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 -2
- package/src/simulation/vehicle/contactResolution.js +99 -62
- 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/vehicleKinematics.js +2 -2
- package/src/simulation/vehicle/vehiclePhysics.js +45 -30
- package/src/simulation/vehicle/vehicleSnapshots.js +35 -1
- package/src/simulation/vehicle/vehicleState.js +7 -1
- package/src/simulation/vehicle/wheelSurface.js +807 -92
- package/src/simulation/warmup/runtimeWarmup.js +0 -1
- package/src/styles.css +1568 -519
- package/src/ui/bannerTemplates.js +38 -28
- package/src/ui/cameraControlsTemplate.js +16 -5
- package/src/ui/carOverviewTemplate.js +12 -5
- package/src/ui/raceCanvasTemplate.js +31 -8
- package/src/ui/raceControlsTemplate.js +27 -10
- package/src/ui/shellTemplate.js +3 -1
- package/src/ui/telemetryDrawerTemplate.js +23 -10
- package/src/ui/telemetryTemplates.js +58 -32
- package/src/ui/templateUtils.js +48 -0
- package/src/ui/timingTowerTemplate.js +29 -12
|
@@ -12,7 +12,7 @@ function getSectorLength(track) {
|
|
|
12
12
|
return track.length / TELEMETRY_SECTOR_COUNT;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function
|
|
15
|
+
function writeLapTelemetryPosition(target, track, raceDistance, totalLaps = Infinity) {
|
|
16
16
|
const positiveDistance = Math.max(0, raceDistance ?? 0);
|
|
17
17
|
const lapIndex = Math.min(
|
|
18
18
|
Math.floor(positiveDistance / track.length),
|
|
@@ -25,41 +25,40 @@ function getLapTelemetryPosition(track, raceDistance, totalLaps = Infinity) {
|
|
|
25
25
|
const sectorIndex = Math.min(TELEMETRY_SECTOR_COUNT - 1, Math.floor(lapProgress / sectorLength));
|
|
26
26
|
const sectorStart = sectorIndex * sectorLength;
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
28
|
+
target.completedLaps = Math.floor(positiveDistance / track.length);
|
|
29
|
+
target.currentLap = Math.max(1, lapIndex + 1);
|
|
30
|
+
target.currentSector = sectorIndex + 1;
|
|
31
|
+
target.currentSectorProgress = clamp((lapProgress - sectorStart) / sectorLength, 0, 1);
|
|
32
|
+
return target;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
function createSectorProgress(position, currentSectors = null) {
|
|
35
|
+
function createSectorProgress(position, currentSectors = null, { preserveCompletedSectors = false } = {}) {
|
|
37
36
|
const activeIndex = clamp((position.currentSector ?? 1) - 1, 0, TELEMETRY_SECTOR_COUNT - 1);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return 0;
|
|
42
|
-
});
|
|
37
|
+
const progress = createEmptySectorTimes();
|
|
38
|
+
writeSectorProgress(progress, position, currentSectors, { preserveCompletedSectors, activeIndex });
|
|
39
|
+
return progress;
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
export function createLapTelemetry(track, currentTime = 0, raceDistance = 0, totalLaps = Infinity) {
|
|
46
|
-
const
|
|
47
|
-
return {
|
|
48
|
-
...position,
|
|
43
|
+
const telemetry = {
|
|
49
44
|
currentLapStartedAt: currentTime,
|
|
50
45
|
currentSectorStartedAt: currentTime,
|
|
51
46
|
lastUpdatedAt: currentTime,
|
|
52
47
|
currentLapTime: 0,
|
|
53
48
|
currentSectorElapsed: 0,
|
|
54
49
|
currentSectors: createEmptySectorTimes(),
|
|
55
|
-
sectorProgress:
|
|
50
|
+
sectorProgress: createEmptySectorTimes(),
|
|
56
51
|
liveSectors: createEmptySectorTimes(),
|
|
57
52
|
lastLapTime: null,
|
|
58
53
|
bestLapTime: null,
|
|
59
54
|
lastSectors: createEmptySectorTimes(),
|
|
60
55
|
bestSectors: createEmptySectorTimes(),
|
|
61
56
|
sectorPerformance: createEmptySectorPerformance(),
|
|
57
|
+
sectorPerformanceRevision: 0,
|
|
62
58
|
};
|
|
59
|
+
writeLapTelemetryPosition(telemetry, track, raceDistance, totalLaps);
|
|
60
|
+
telemetry.sectorProgress = createSectorProgress(telemetry);
|
|
61
|
+
return telemetry;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
export function resetLapTelemetry(car, currentTime, track, totalLaps) {
|
|
@@ -99,12 +98,25 @@ export function updateLapTelemetry(car, previousRaceDistance, currentTime, track
|
|
|
99
98
|
const previousUpdateTime = Number.isFinite(telemetry.lastUpdatedAt) ? telemetry.lastUpdatedAt : currentTime;
|
|
100
99
|
const elapsedTime = Math.max(0, currentTime - previousUpdateTime);
|
|
101
100
|
|
|
102
|
-
if (
|
|
101
|
+
if (
|
|
102
|
+
!Number.isFinite(previousDistance) ||
|
|
103
|
+
!Number.isFinite(currentDistance) ||
|
|
104
|
+
Math.abs(travelled) > track.length / 2
|
|
105
|
+
) {
|
|
103
106
|
resetLapTelemetry(car, currentTime, track, totalLaps);
|
|
104
|
-
return;
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (travelled < -1e-3) {
|
|
111
|
+
syncLapTelemetryPosition(telemetry, currentTime, currentRaceDistance, track, totalLaps, {
|
|
112
|
+
preserveCompletedSectors: true,
|
|
113
|
+
});
|
|
114
|
+
telemetry.lastUpdatedAt = currentTime;
|
|
115
|
+
return false;
|
|
105
116
|
}
|
|
106
117
|
|
|
107
118
|
const sectorLength = getSectorLength(track);
|
|
119
|
+
let crossedBoundary = false;
|
|
108
120
|
if (travelled > 1e-6) {
|
|
109
121
|
const firstBoundary = Math.floor(previousDistance / sectorLength) + 1;
|
|
110
122
|
const lastBoundary = Math.floor(currentDistance / sectorLength);
|
|
@@ -112,6 +124,7 @@ export function updateLapTelemetry(car, previousRaceDistance, currentTime, track
|
|
|
112
124
|
for (let boundary = firstBoundary; boundary <= lastBoundary; boundary += 1) {
|
|
113
125
|
const boundaryDistance = boundary * sectorLength;
|
|
114
126
|
if (boundaryDistance <= previousDistance + 1e-3 || boundaryDistance > currentDistance + 1e-3) continue;
|
|
127
|
+
crossedBoundary = true;
|
|
115
128
|
|
|
116
129
|
const boundaryRatio = clamp((boundaryDistance - previousDistance) / travelled, 0, 1);
|
|
117
130
|
const crossingTime = previousUpdateTime + boundaryRatio * elapsedTime;
|
|
@@ -127,8 +140,8 @@ export function updateLapTelemetry(car, previousRaceDistance, currentTime, track
|
|
|
127
140
|
telemetry.bestLapTime = Number.isFinite(telemetry.bestLapTime)
|
|
128
141
|
? Math.min(telemetry.bestLapTime, lapTime)
|
|
129
142
|
: lapTime;
|
|
130
|
-
telemetry.lastSectors =
|
|
131
|
-
telemetry.currentSectors
|
|
143
|
+
telemetry.lastSectors = copySectorTimes(telemetry.lastSectors, telemetry.currentSectors);
|
|
144
|
+
clearSectorTimes(telemetry.currentSectors);
|
|
132
145
|
telemetry.currentLapStartedAt = crossingTime;
|
|
133
146
|
telemetry.completedLaps = Math.max(telemetry.completedLaps + 1, Math.min(Math.floor(boundaryDistance / track.length), totalLaps));
|
|
134
147
|
}
|
|
@@ -137,8 +150,18 @@ export function updateLapTelemetry(car, previousRaceDistance, currentTime, track
|
|
|
137
150
|
}
|
|
138
151
|
}
|
|
139
152
|
|
|
153
|
+
if (!crossedBoundary) {
|
|
154
|
+
syncInProgressLapTelemetry(telemetry, currentTime, currentRaceDistance, track, totalLaps);
|
|
155
|
+
telemetry.lastUpdatedAt = currentTime;
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
140
159
|
syncLapTelemetryPosition(telemetry, currentTime, currentRaceDistance, track, totalLaps);
|
|
141
160
|
telemetry.lastUpdatedAt = currentTime;
|
|
161
|
+
if (crossedBoundary) {
|
|
162
|
+
telemetry.sectorPerformanceRevision = (telemetry.sectorPerformanceRevision ?? 0) + 1;
|
|
163
|
+
}
|
|
164
|
+
return crossedBoundary;
|
|
142
165
|
}
|
|
143
166
|
|
|
144
167
|
function updateBestSector(telemetry, sectorIndex, sectorTime) {
|
|
@@ -155,28 +178,103 @@ function clearFutureSectorTelemetry(telemetry) {
|
|
|
155
178
|
}
|
|
156
179
|
}
|
|
157
180
|
|
|
158
|
-
function syncLiveSectorTelemetry(telemetry) {
|
|
181
|
+
function syncLiveSectorTelemetry(telemetry, { preserveCompletedSectors = false } = {}) {
|
|
159
182
|
const activeIndex = clamp((telemetry.currentSector ?? 1) - 1, 0, TELEMETRY_SECTOR_COUNT - 1);
|
|
183
|
+
const liveSectors = ensureSectorTimesArray(telemetry.liveSectors);
|
|
184
|
+
clearSectorTimes(liveSectors);
|
|
185
|
+
for (let index = 0; index < TELEMETRY_SECTOR_COUNT; index += 1) {
|
|
186
|
+
const time = telemetry.currentSectors[index];
|
|
187
|
+
if (!preserveCompletedSectors && index >= activeIndex) continue;
|
|
188
|
+
if (!Number.isFinite(time)) continue;
|
|
189
|
+
liveSectors[index] = time;
|
|
190
|
+
}
|
|
191
|
+
if (!Number.isFinite(liveSectors[activeIndex])) {
|
|
192
|
+
liveSectors[activeIndex] = telemetry.currentSectorElapsed;
|
|
193
|
+
}
|
|
194
|
+
telemetry.liveSectors = liveSectors;
|
|
195
|
+
}
|
|
160
196
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
197
|
+
function syncInProgressLapTelemetry(telemetry, currentTime, currentRaceDistance, track, totalLaps) {
|
|
198
|
+
const positiveDistance = Math.max(0, currentRaceDistance ?? 0);
|
|
199
|
+
const maxLapIndex = Math.max(0, totalLaps - 1);
|
|
200
|
+
const lapIndex = Math.min(Math.floor(positiveDistance / track.length), maxLapIndex);
|
|
201
|
+
const lapProgress = positiveDistance >= track.length * totalLaps
|
|
202
|
+
? track.length
|
|
203
|
+
: positiveDistance - lapIndex * track.length;
|
|
204
|
+
const sectorLength = getSectorLength(track);
|
|
205
|
+
const activeIndex = Math.min(TELEMETRY_SECTOR_COUNT - 1, Math.floor(lapProgress / sectorLength));
|
|
206
|
+
const sectorStart = activeIndex * sectorLength;
|
|
207
|
+
const sectorProgress = ensureSectorTimesArray(telemetry.sectorProgress);
|
|
208
|
+
const liveSectors = ensureSectorTimesArray(telemetry.liveSectors);
|
|
209
|
+
|
|
210
|
+
telemetry.completedLaps = Math.floor(positiveDistance / track.length);
|
|
211
|
+
telemetry.currentLap = Math.max(1, lapIndex + 1);
|
|
212
|
+
telemetry.currentSector = activeIndex + 1;
|
|
213
|
+
telemetry.currentSectorProgress = clamp((lapProgress - sectorStart) / sectorLength, 0, 1);
|
|
214
|
+
telemetry.currentLapTime = Math.max(0, currentTime - telemetry.currentLapStartedAt);
|
|
215
|
+
telemetry.currentSectorElapsed = Math.max(0, currentTime - telemetry.currentSectorStartedAt);
|
|
216
|
+
for (let index = activeIndex; index < TELEMETRY_SECTOR_COUNT; index += 1) {
|
|
217
|
+
telemetry.currentSectors[index] = null;
|
|
218
|
+
liveSectors[index] = null;
|
|
219
|
+
sectorProgress[index] = 0;
|
|
220
|
+
}
|
|
221
|
+
sectorProgress[activeIndex] = telemetry.currentSectorProgress;
|
|
222
|
+
liveSectors[activeIndex] = telemetry.currentSectorElapsed;
|
|
223
|
+
telemetry.sectorProgress = sectorProgress;
|
|
224
|
+
telemetry.liveSectors = liveSectors;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function syncLapTelemetryPosition(telemetry, currentTime, currentRaceDistance, track, totalLaps, {
|
|
228
|
+
preserveCompletedSectors = false,
|
|
229
|
+
} = {}) {
|
|
230
|
+
writeLapTelemetryPosition(telemetry, track, currentRaceDistance, totalLaps);
|
|
231
|
+
if (!preserveCompletedSectors) clearFutureSectorTelemetry(telemetry);
|
|
232
|
+
const sectorProgress = ensureSectorTimesArray(telemetry.sectorProgress);
|
|
233
|
+
writeSectorProgress(sectorProgress, telemetry, telemetry.currentSectors, {
|
|
234
|
+
preserveCompletedSectors,
|
|
166
235
|
});
|
|
167
|
-
telemetry.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
function syncLapTelemetryPosition(telemetry, currentTime, currentRaceDistance, track, totalLaps) {
|
|
171
|
-
const position = getLapTelemetryPosition(track, currentRaceDistance, totalLaps);
|
|
172
|
-
telemetry.completedLaps = position.completedLaps;
|
|
173
|
-
telemetry.currentLap = position.currentLap;
|
|
174
|
-
telemetry.currentSector = position.currentSector;
|
|
175
|
-
telemetry.currentSectorProgress = position.currentSectorProgress;
|
|
176
|
-
clearFutureSectorTelemetry(telemetry);
|
|
177
|
-
telemetry.sectorProgress = createSectorProgress(position, telemetry.currentSectors);
|
|
178
|
-
telemetry.completedLaps = Math.max(telemetry.completedLaps, Math.min(position.completedLaps, totalLaps));
|
|
236
|
+
telemetry.sectorProgress = sectorProgress;
|
|
237
|
+
telemetry.completedLaps = Math.max(telemetry.completedLaps, Math.min(telemetry.completedLaps, totalLaps));
|
|
179
238
|
telemetry.currentLapTime = Math.max(0, currentTime - telemetry.currentLapStartedAt);
|
|
180
239
|
telemetry.currentSectorElapsed = Math.max(0, currentTime - telemetry.currentSectorStartedAt);
|
|
181
|
-
syncLiveSectorTelemetry(telemetry);
|
|
240
|
+
syncLiveSectorTelemetry(telemetry, { preserveCompletedSectors });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function ensureSectorTimesArray(values) {
|
|
244
|
+
return Array.isArray(values) && values.length === TELEMETRY_SECTOR_COUNT
|
|
245
|
+
? values
|
|
246
|
+
: createEmptySectorTimes();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function clearSectorTimes(values) {
|
|
250
|
+
for (let index = 0; index < TELEMETRY_SECTOR_COUNT; index += 1) {
|
|
251
|
+
values[index] = null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function copySectorTimes(target, source) {
|
|
256
|
+
const nextTarget = ensureSectorTimesArray(target);
|
|
257
|
+
for (let index = 0; index < TELEMETRY_SECTOR_COUNT; index += 1) {
|
|
258
|
+
nextTarget[index] = finiteOrNull(source?.[index]);
|
|
259
|
+
}
|
|
260
|
+
return nextTarget;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function writeSectorProgress(target, position, currentSectors = null, { preserveCompletedSectors = false, activeIndex = null } = {}) {
|
|
264
|
+
const resolvedActiveIndex = activeIndex == null
|
|
265
|
+
? clamp((position.currentSector ?? 1) - 1, 0, TELEMETRY_SECTOR_COUNT - 1)
|
|
266
|
+
: activeIndex;
|
|
267
|
+
for (let index = 0; index < TELEMETRY_SECTOR_COUNT; index += 1) {
|
|
268
|
+
if (preserveCompletedSectors && Number.isFinite(currentSectors?.[index])) {
|
|
269
|
+
target[index] = 1;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (index < resolvedActiveIndex && Number.isFinite(currentSectors?.[index])) {
|
|
273
|
+
target[index] = 1;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
target[index] = index === resolvedActiveIndex
|
|
277
|
+
? clamp(position.currentSectorProgress ?? 0, 0, 1)
|
|
278
|
+
: 0;
|
|
279
|
+
}
|
|
182
280
|
}
|
|
@@ -5,7 +5,11 @@ export {
|
|
|
5
5
|
updateLapTelemetry,
|
|
6
6
|
} from './lapTelemetry.js';
|
|
7
7
|
export { estimateGapAheadSeconds, estimateTimingLineGapSeconds, wholeLapGap } from './gapEstimation.js';
|
|
8
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
createEmptySectorPerformance,
|
|
10
|
+
createEmptySectorTimes,
|
|
11
|
+
updateSectorPerformance,
|
|
12
|
+
} from './sectorPerformance.js';
|
|
9
13
|
export { recordTimingSample, resetTimingHistory, trimTimingHistory } from './timingHistory.js';
|
|
10
14
|
export {
|
|
11
15
|
createTimingLines,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { TELEMETRY_SECTOR_COUNT } from './timingConstants.js';
|
|
2
2
|
|
|
3
|
+
const OVERALL_BEST_0 = '_sectorPerformanceOverallBest0';
|
|
4
|
+
const OVERALL_BEST_1 = '_sectorPerformanceOverallBest1';
|
|
5
|
+
const OVERALL_BEST_2 = '_sectorPerformanceOverallBest2';
|
|
6
|
+
|
|
3
7
|
export function createEmptySectorTimes() {
|
|
4
8
|
return Array.from({ length: TELEMETRY_SECTOR_COUNT }, () => null);
|
|
5
9
|
}
|
|
@@ -20,32 +24,80 @@ export function serializeSectorPerformance(values) {
|
|
|
20
24
|
return createEmptySectorTimes().map((_, index) => values?.[index] ?? null);
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
export function updateSectorPerformance(cars) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
telemetry.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
27
|
+
export function updateSectorPerformance(cars, stats = null) {
|
|
28
|
+
let overallBest0 = null;
|
|
29
|
+
let overallBest1 = null;
|
|
30
|
+
let overallBest2 = null;
|
|
31
|
+
let changedCars = 0;
|
|
32
|
+
|
|
33
|
+
for (let index = 0; index < cars.length; index += 1) {
|
|
34
|
+
const telemetry = cars[index]?.lapTelemetry;
|
|
35
|
+
if (!telemetry) continue;
|
|
36
|
+
const best0 = finiteOrNull(telemetry.bestSectors?.[0]);
|
|
37
|
+
const best1 = finiteOrNull(telemetry.bestSectors?.[1]);
|
|
38
|
+
const best2 = finiteOrNull(telemetry.bestSectors?.[2]);
|
|
39
|
+
const revision = telemetry.sectorPerformanceRevision ?? 0;
|
|
40
|
+
if (telemetry._sectorPerfAppliedRevision !== revision) changedCars += 1;
|
|
41
|
+
if (Number.isFinite(best0) && (!Number.isFinite(overallBest0) || best0 < overallBest0)) overallBest0 = best0;
|
|
42
|
+
if (Number.isFinite(best1) && (!Number.isFinite(overallBest1) || best1 < overallBest1)) overallBest1 = best1;
|
|
43
|
+
if (Number.isFinite(best2) && (!Number.isFinite(overallBest2) || best2 < overallBest2)) overallBest2 = best2;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const overallBestChanged =
|
|
47
|
+
overallBest0 !== cars[OVERALL_BEST_0] ||
|
|
48
|
+
overallBest1 !== cars[OVERALL_BEST_1] ||
|
|
49
|
+
overallBest2 !== cars[OVERALL_BEST_2];
|
|
50
|
+
|
|
51
|
+
if (stats) {
|
|
52
|
+
stats.sectorPerformanceChangedCars = (stats.sectorPerformanceChangedCars ?? 0) + changedCars;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!overallBestChanged && changedCars === 0) {
|
|
56
|
+
if (stats) stats.sectorPerformanceSkippedRebuilds = (stats.sectorPerformanceSkippedRebuilds ?? 0) + 1;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
cars[OVERALL_BEST_0] = overallBest0;
|
|
61
|
+
cars[OVERALL_BEST_1] = overallBest1;
|
|
62
|
+
cars[OVERALL_BEST_2] = overallBest2;
|
|
63
|
+
|
|
64
|
+
let updatedCars = 0;
|
|
65
|
+
const overallBestSectors = [overallBest0, overallBest1, overallBest2];
|
|
66
|
+
for (let index = 0; index < cars.length; index += 1) {
|
|
67
|
+
const telemetry = cars[index]?.lapTelemetry;
|
|
68
|
+
if (!telemetry) continue;
|
|
69
|
+
const revision = telemetry.sectorPerformanceRevision ?? 0;
|
|
70
|
+
if (!overallBestChanged && telemetry._sectorPerfAppliedRevision === revision) continue;
|
|
71
|
+
const sectorPerformance = ensureSectorPerformanceShape(telemetry.sectorPerformance);
|
|
72
|
+
writeSectorPerformance(
|
|
73
|
+
sectorPerformance.current,
|
|
74
|
+
telemetry.currentSectors,
|
|
75
|
+
telemetry.bestSectors,
|
|
76
|
+
overallBestSectors,
|
|
77
|
+
);
|
|
78
|
+
writeSectorPerformance(
|
|
79
|
+
sectorPerformance.last,
|
|
80
|
+
telemetry.lastSectors,
|
|
81
|
+
telemetry.bestSectors,
|
|
82
|
+
overallBestSectors,
|
|
83
|
+
);
|
|
84
|
+
writeSectorPerformance(
|
|
85
|
+
sectorPerformance.best,
|
|
86
|
+
telemetry.bestSectors,
|
|
87
|
+
telemetry.bestSectors,
|
|
88
|
+
overallBestSectors,
|
|
89
|
+
);
|
|
90
|
+
telemetry.sectorPerformance = sectorPerformance;
|
|
91
|
+
telemetry._sectorPerfAppliedRevision = revision;
|
|
92
|
+
updatedCars += 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (stats) {
|
|
96
|
+
stats.sectorPerformanceRebuilds = (stats.sectorPerformanceRebuilds ?? 0) + 1;
|
|
97
|
+
stats.sectorPerformanceUpdatedCars = (stats.sectorPerformanceUpdatedCars ?? 0) + updatedCars;
|
|
98
|
+
if (overallBestChanged) stats.sectorPerformanceOverallBestChanges = (stats.sectorPerformanceOverallBestChanges ?? 0) + 1;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
49
101
|
}
|
|
50
102
|
|
|
51
103
|
export function finiteOrNull(value) {
|
|
@@ -58,3 +110,29 @@ function classifySectorPerformance(value, personalBest, overallBest) {
|
|
|
58
110
|
if (Number.isFinite(personalBest) && Math.abs(value - personalBest) <= 1e-6) return 'personal-best';
|
|
59
111
|
return 'slower';
|
|
60
112
|
}
|
|
113
|
+
|
|
114
|
+
function ensureSectorPerformanceShape(sectorPerformance) {
|
|
115
|
+
if (!sectorPerformance || typeof sectorPerformance !== 'object') {
|
|
116
|
+
return createEmptySectorPerformance();
|
|
117
|
+
}
|
|
118
|
+
sectorPerformance.current = ensureSectorArray(sectorPerformance.current);
|
|
119
|
+
sectorPerformance.last = ensureSectorArray(sectorPerformance.last);
|
|
120
|
+
sectorPerformance.best = ensureSectorArray(sectorPerformance.best);
|
|
121
|
+
return sectorPerformance;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function ensureSectorArray(values) {
|
|
125
|
+
return Array.isArray(values) && values.length === TELEMETRY_SECTOR_COUNT
|
|
126
|
+
? values
|
|
127
|
+
: createEmptySectorTimes();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function writeSectorPerformance(target, sectorValues, bestSectors, overallBestSectors) {
|
|
131
|
+
for (let index = 0; index < TELEMETRY_SECTOR_COUNT; index += 1) {
|
|
132
|
+
target[index] = classifySectorPerformance(
|
|
133
|
+
sectorValues?.[index],
|
|
134
|
+
bestSectors?.[index],
|
|
135
|
+
overallBestSectors[index],
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|