@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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { nearestTrackState } from '../../simulation/track/trackModel.js';
|
|
2
|
+
import { normalizeAngle } from '../../simulation/simMath.js';
|
|
3
|
+
import { offsetTrackPoint, pointAt, sampleIndexAtDistance } from '../../simulation/track/spatialQueries.js';
|
|
2
4
|
import { pitOverrideAllowedForCar } from '../../simulation/track/trackStatePolicy.js';
|
|
3
5
|
import { metersToSimUnits, simUnitsToMeters } from '../../simulation/units.js';
|
|
4
6
|
import {
|
|
@@ -12,13 +14,18 @@ import { canUseIndexedRecoveryRayApproximation } from './rayGuards.js';
|
|
|
12
14
|
import { degreesToRadians, getCarRayOrigin, getCarRayVector, pointOnRay } from './rayGeometry.js';
|
|
13
15
|
import { findIndexedTrackBandBoundaries } from './indexedRayBands.js';
|
|
14
16
|
|
|
17
|
+
const RAY_TRACK_STATE_REUSE_POSITION_TOLERANCE = metersToSimUnits(1);
|
|
18
|
+
const RAY_TRACK_STATE_REUSE_PROGRESS_TOLERANCE = metersToSimUnits(0.25);
|
|
19
|
+
const RAY_TRACK_STATE_REUSE_HEADING_TOLERANCE = 0.02;
|
|
20
|
+
|
|
15
21
|
export function createTrackRayContext(car, snapshot, origin, precision = 'debug') {
|
|
16
22
|
if (!Array.isArray(snapshot.track?.samples) || snapshot.track.samples.length === 0) {
|
|
17
23
|
return { origin, originState: null, precision };
|
|
18
24
|
}
|
|
25
|
+
const reusedOriginState = reusableRayOriginState(snapshot.track, car, origin);
|
|
19
26
|
return {
|
|
20
27
|
origin,
|
|
21
|
-
originState: nearestRayTrackState(snapshot.track, car, origin, car.progress, precision),
|
|
28
|
+
originState: reusedOriginState ?? nearestRayTrackState(snapshot.track, car, origin, car.progress, precision),
|
|
22
29
|
precision,
|
|
23
30
|
};
|
|
24
31
|
}
|
|
@@ -43,6 +50,18 @@ export function estimateTrackHit(
|
|
|
43
50
|
? context.originState
|
|
44
51
|
: nearestRayTrackState(snapshot.track, car, origin, car.progress, precision);
|
|
45
52
|
const includePitLane = Boolean(car.inPitLane || car.pitLanePart || originState.inPitLane);
|
|
53
|
+
const indexedHit = estimateIndexedTrackHit({
|
|
54
|
+
car,
|
|
55
|
+
track: snapshot.track,
|
|
56
|
+
origin,
|
|
57
|
+
originState,
|
|
58
|
+
ray,
|
|
59
|
+
lengthMeters,
|
|
60
|
+
includePitLane,
|
|
61
|
+
precision,
|
|
62
|
+
sharedRayQuery,
|
|
63
|
+
});
|
|
64
|
+
if (indexedHit) return indexedHit;
|
|
46
65
|
const directionalAnalyticHit = canUseDirectionalAnalyticTrackHit({ car, track: snapshot.track, originState, ray, includePitLane })
|
|
47
66
|
? estimateAnalyticMainTrackHit({
|
|
48
67
|
car,
|
|
@@ -55,20 +74,6 @@ export function estimateTrackHit(
|
|
|
55
74
|
})
|
|
56
75
|
: null;
|
|
57
76
|
if (directionalAnalyticHit) return directionalAnalyticHit;
|
|
58
|
-
if (!usesSampledDriverContract(car, precision)) {
|
|
59
|
-
const indexedHit = estimateIndexedTrackHit({
|
|
60
|
-
car,
|
|
61
|
-
track: snapshot.track,
|
|
62
|
-
origin,
|
|
63
|
-
originState,
|
|
64
|
-
ray,
|
|
65
|
-
lengthMeters,
|
|
66
|
-
includePitLane,
|
|
67
|
-
precision,
|
|
68
|
-
sharedRayQuery,
|
|
69
|
-
});
|
|
70
|
-
if (indexedHit) return indexedHit;
|
|
71
|
-
}
|
|
72
77
|
const analyticHit = estimateAnalyticMainTrackHit({
|
|
73
78
|
car,
|
|
74
79
|
track: snapshot.track,
|
|
@@ -168,7 +173,6 @@ function canUseDirectionalAnalyticTrackHit({ car, track, originState, ray, inclu
|
|
|
168
173
|
|
|
169
174
|
function estimateIndexedTrackHit({ car, track, origin, originState, ray, lengthMeters, includePitLane, precision, sharedRayQuery }) {
|
|
170
175
|
if (includePitLane || (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState))) return null;
|
|
171
|
-
if (!canUseIndexedRecoveryRayApproximation(track, originState)) return null;
|
|
172
176
|
const trackHalfWidth = track.width / 2;
|
|
173
177
|
const inside = Math.abs(originState.signedOffset ?? 0) <= trackHalfWidth;
|
|
174
178
|
const boundaries = findIndexedTrackBandBoundaries(
|
|
@@ -181,21 +185,28 @@ function estimateIndexedTrackHit({ car, track, origin, originState, ray, lengthM
|
|
|
181
185
|
sharedRayQuery,
|
|
182
186
|
);
|
|
183
187
|
if (!boundaries.available) return null;
|
|
184
|
-
if (boundaries.trackEdgeDistance == null)
|
|
188
|
+
if (boundaries.trackEdgeDistance == null) {
|
|
189
|
+
if (usesMainTrackOnlyRays(car)) return createTrackMiss(lengthMeters);
|
|
190
|
+
return canSampleRecoveryTransition(track, originState, ray)
|
|
191
|
+
? null
|
|
192
|
+
: createTrackMiss(lengthMeters);
|
|
193
|
+
}
|
|
185
194
|
const kind = inside ? 'exit' : 'entry';
|
|
186
|
-
const hitDistance =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
const hitDistance = usesMainTrackOnlyRays(car)
|
|
196
|
+
? boundaries.trackEdgeDistance
|
|
197
|
+
: validatedIndexedTransitionDistance({
|
|
198
|
+
track,
|
|
199
|
+
car,
|
|
200
|
+
origin,
|
|
201
|
+
ray,
|
|
202
|
+
progressHint: car.progress,
|
|
203
|
+
distance: boundaries.trackEdgeDistance,
|
|
204
|
+
maxDistance: metersToSimUnits(lengthMeters),
|
|
205
|
+
kind,
|
|
206
|
+
includePitLane,
|
|
207
|
+
precision,
|
|
208
|
+
originInside: inside,
|
|
209
|
+
});
|
|
199
210
|
if (hitDistance == null) return null;
|
|
200
211
|
return {
|
|
201
212
|
hit: true,
|
|
@@ -287,16 +298,116 @@ function refineTrackTransitionDistance(
|
|
|
287
298
|
}
|
|
288
299
|
|
|
289
300
|
function nearestRayTrackState(track, car, point, progressHint, precision = 'debug') {
|
|
290
|
-
const useBatchTrainingIndexMode = usesMainTrackOnlyRays(car);
|
|
291
301
|
return nearestTrackState(track, point, progressHint, {
|
|
292
302
|
allowPitOverride: pitOverrideAllowedForCar(car),
|
|
293
|
-
indexMode: usesSampledDriverContract(car, precision) ? 'legacy' : useBatchTrainingIndexMode ? 'sample' : undefined,
|
|
294
|
-
hintMaxDistance: useBatchTrainingIndexMode ? Infinity : undefined,
|
|
295
303
|
});
|
|
296
304
|
}
|
|
297
305
|
|
|
298
|
-
function
|
|
299
|
-
|
|
306
|
+
function reusableRayOriginState(track, car, origin) {
|
|
307
|
+
const synthesizedState = synthesizedMainRoadRayOriginState(track, car, origin);
|
|
308
|
+
if (synthesizedState) return synthesizedState;
|
|
309
|
+
const trackState = car?.trackState;
|
|
310
|
+
if (!trackState || trackState.inPitLane) return null;
|
|
311
|
+
const mainRoadEdge = track.width / 2 + (track.kerbWidth ?? 0);
|
|
312
|
+
if (!Number.isFinite(trackState.crossTrackError) || trackState.crossTrackError > mainRoadEdge) return null;
|
|
313
|
+
if (
|
|
314
|
+
typeof car?.trackStatePoseX === 'number' &&
|
|
315
|
+
typeof car?.trackStatePoseY === 'number' &&
|
|
316
|
+
typeof car?.trackStatePoseHeading === 'number'
|
|
317
|
+
) {
|
|
318
|
+
if (
|
|
319
|
+
car.trackStatePoseX !== car.x ||
|
|
320
|
+
car.trackStatePoseY !== car.y ||
|
|
321
|
+
car.trackStatePoseHeading !== car.heading
|
|
322
|
+
) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
if (!Number.isInteger(trackState.segmentId) && Number.isFinite(trackState.distance)) {
|
|
326
|
+
trackState.segmentId = sampleIndexAtDistance(track, trackState.distance);
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
if (!Number.isFinite(trackState.distance) || !Number.isFinite(trackState.signedOffset)) return null;
|
|
330
|
+
const base = pointAt(track, trackState.distance);
|
|
331
|
+
const projected = offsetTrackPoint(base, trackState.signedOffset);
|
|
332
|
+
const dx = (origin?.x ?? car?.x ?? 0) - projected.x;
|
|
333
|
+
const dy = (origin?.y ?? car?.y ?? 0) - projected.y;
|
|
334
|
+
if ((dx * dx) + (dy * dy) > RAY_TRACK_STATE_REUSE_POSITION_TOLERANCE * RAY_TRACK_STATE_REUSE_POSITION_TOLERANCE) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
if (wrappedDistanceDelta(trackState.distance, car?.progress, track.length) > RAY_TRACK_STATE_REUSE_PROGRESS_TOLERANCE) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
if (Math.abs(normalizeAngle((trackState.heading ?? base.heading) - base.heading)) > RAY_TRACK_STATE_REUSE_HEADING_TOLERANCE) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
const segmentId = trackState.segmentId ?? sampleIndexAtDistance(track, trackState.distance);
|
|
344
|
+
return {
|
|
345
|
+
segmentId,
|
|
346
|
+
x: base.x,
|
|
347
|
+
y: base.y,
|
|
348
|
+
distance: base.distance,
|
|
349
|
+
heading: base.heading,
|
|
350
|
+
normalX: base.normalX,
|
|
351
|
+
normalY: base.normalY,
|
|
352
|
+
curvature: base.curvature,
|
|
353
|
+
signedOffset: trackState.signedOffset,
|
|
354
|
+
crossTrackError: trackState.crossTrackError,
|
|
355
|
+
surface: trackState.surface ?? 'track',
|
|
356
|
+
onTrack: trackState.surface === 'track' || trackState.surface === 'kerb',
|
|
357
|
+
distanceSquared: trackState.distanceSquared,
|
|
358
|
+
inPitLane: false,
|
|
359
|
+
pitLanePart: null,
|
|
360
|
+
pitBoxId: null,
|
|
361
|
+
mainTrackSignedOffset: trackState.mainTrackSignedOffset,
|
|
362
|
+
mainTrackCrossTrackError: trackState.mainTrackCrossTrackError,
|
|
363
|
+
pitLaneCrossTrackError: trackState.pitLaneCrossTrackError,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return trackState;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function synthesizedMainRoadRayOriginState(track, car, origin) {
|
|
370
|
+
if (!Number.isFinite(car?.progress) || !Number.isFinite(car?.signedOffset)) return null;
|
|
371
|
+
if (car?.inPitLane || car?.pitLanePart) return null;
|
|
372
|
+
const crossTrackError = Math.abs(car.signedOffset);
|
|
373
|
+
const mainRoadEdge = track.width / 2 + (track.kerbWidth ?? 0);
|
|
374
|
+
if (crossTrackError > mainRoadEdge) return null;
|
|
375
|
+
const base = pointAt(track, car.progress);
|
|
376
|
+
const projected = offsetTrackPoint(base, car.signedOffset);
|
|
377
|
+
const dx = (origin?.x ?? car?.x ?? 0) - projected.x;
|
|
378
|
+
const dy = (origin?.y ?? car?.y ?? 0) - projected.y;
|
|
379
|
+
if ((dx * dx) + (dy * dy) > RAY_TRACK_STATE_REUSE_POSITION_TOLERANCE * RAY_TRACK_STATE_REUSE_POSITION_TOLERANCE) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
const segmentId = car?.trackState?.segmentId ?? sampleIndexAtDistance(track, car.progress);
|
|
383
|
+
return {
|
|
384
|
+
segmentId,
|
|
385
|
+
x: base.x,
|
|
386
|
+
y: base.y,
|
|
387
|
+
distance: base.distance,
|
|
388
|
+
heading: base.heading,
|
|
389
|
+
normalX: base.normalX,
|
|
390
|
+
normalY: base.normalY,
|
|
391
|
+
curvature: base.curvature,
|
|
392
|
+
signedOffset: car.signedOffset,
|
|
393
|
+
crossTrackError,
|
|
394
|
+
surface: crossTrackError <= track.width / 2 ? 'track' : 'kerb',
|
|
395
|
+
onTrack: true,
|
|
396
|
+
distanceSquared: car?.trackState?.distanceSquared,
|
|
397
|
+
inPitLane: false,
|
|
398
|
+
pitLanePart: null,
|
|
399
|
+
pitBoxId: null,
|
|
400
|
+
mainTrackSignedOffset: car?.trackState?.mainTrackSignedOffset,
|
|
401
|
+
mainTrackCrossTrackError: car?.trackState?.mainTrackCrossTrackError,
|
|
402
|
+
pitLaneCrossTrackError: car?.trackState?.pitLaneCrossTrackError,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function wrappedDistanceDelta(first, second, totalLength) {
|
|
407
|
+
if (!Number.isFinite(first) || !Number.isFinite(second)) return Infinity;
|
|
408
|
+
if (!Number.isFinite(totalLength) || totalLength <= 0) return Math.abs(first - second);
|
|
409
|
+
const delta = Math.abs(first - second);
|
|
410
|
+
return Math.min(delta, totalLength - delta);
|
|
300
411
|
}
|
|
301
412
|
|
|
302
413
|
function refineStepsForPrecision(precision) {
|
|
@@ -308,6 +419,16 @@ function isInsideTrackBorder(state, track, includePitLane = true) {
|
|
|
308
419
|
return state.crossTrackError <= track.width / 2;
|
|
309
420
|
}
|
|
310
421
|
|
|
422
|
+
function canSampleRecoveryTransition(track, originState, ray) {
|
|
423
|
+
if (!canUseIndexedRecoveryRayApproximation(track, originState)) return false;
|
|
424
|
+
const trackHalfWidth = track.width / 2;
|
|
425
|
+
const offset = originState.signedOffset ?? 0;
|
|
426
|
+
if (Math.abs(offset) <= trackHalfWidth) return false;
|
|
427
|
+
const lateral = ray.x * originState.normalX + ray.y * originState.normalY;
|
|
428
|
+
if (Math.abs(lateral) < 0.05) return false;
|
|
429
|
+
return offset > 0 ? lateral < 0 : lateral > 0;
|
|
430
|
+
}
|
|
431
|
+
|
|
311
432
|
function validatedIndexedTransitionDistance({
|
|
312
433
|
track,
|
|
313
434
|
car,
|
|
@@ -323,19 +444,8 @@ function validatedIndexedTransitionDistance({
|
|
|
323
444
|
}) {
|
|
324
445
|
const step = metersToSimUnits(TRACK_RAY_STEP_METERS);
|
|
325
446
|
const refineSteps = refineStepsForPrecision(precision);
|
|
326
|
-
if (refineSteps <= 0 && usesMainTrackOnlyRays(car)) return distance;
|
|
327
447
|
if (refineSteps <= 0) {
|
|
328
|
-
return
|
|
329
|
-
track,
|
|
330
|
-
car,
|
|
331
|
-
origin,
|
|
332
|
-
ray,
|
|
333
|
-
progressHint,
|
|
334
|
-
distance,
|
|
335
|
-
maxDistance,
|
|
336
|
-
kind,
|
|
337
|
-
includePitLane,
|
|
338
|
-
});
|
|
448
|
+
return distance;
|
|
339
449
|
}
|
|
340
450
|
|
|
341
451
|
const windows = [step * 1.5, step * 8];
|
|
@@ -418,7 +528,6 @@ function refineValidatedTransitionDistance(track, car, origin, ray, progressHint
|
|
|
418
528
|
function validatedInsideTrackBorder(track, car, origin, ray, distance, progressHint, includePitLane) {
|
|
419
529
|
const state = nearestTrackState(track, pointOnRay(origin, ray, distance), progressHint, {
|
|
420
530
|
allowPitOverride: pitOverrideAllowedForCar(car),
|
|
421
|
-
indexMode: 'legacy',
|
|
422
531
|
});
|
|
423
532
|
return isInsideTrackBorder(state, track, includePitLane);
|
|
424
533
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
PaddockActionSpec,
|
|
3
3
|
PaddockDriverControllerLoop,
|
|
4
4
|
PaddockDriverControllerLoopOptions,
|
|
5
|
+
PaddockEnvironmentOptions,
|
|
5
6
|
PaddockObservationSpec,
|
|
6
7
|
PaddockParticipantInteraction as EnvPaddockParticipantInteraction,
|
|
7
8
|
PaddockParticipantInteractionsOptions as EnvPaddockParticipantInteractionsOptions,
|
|
@@ -23,7 +24,7 @@ export type {
|
|
|
23
24
|
} from './environment/index.js';
|
|
24
25
|
|
|
25
26
|
export type TireCompound = 'S' | 'M' | 'H';
|
|
26
|
-
export type PaddockPhysicsMode = 'arcade' | '
|
|
27
|
+
export type PaddockPhysicsMode = 'arcade' | 'advanced';
|
|
27
28
|
export type PaddockWarmupPolicy = 'config-change' | 'always' | 'never';
|
|
28
29
|
export type PaddockProceduralTrackProfile = 'race' | 'training-short' | 'training-medium' | 'training-technical';
|
|
29
30
|
|
|
@@ -81,11 +82,12 @@ export type PaddockPitIntentRequest = PaddockPitIntent | {
|
|
|
81
82
|
pitTargetCompound?: TireCompound | string;
|
|
82
83
|
targetTire?: TireCompound | string;
|
|
83
84
|
};
|
|
84
|
-
export type CameraMode = 'overview' | 'leader' | 'selected' | 'show-all' | 'pit';
|
|
85
|
+
export type CameraMode = 'overview' | 'leader' | 'selected' | 'driver' | 'show-all' | 'pit';
|
|
85
86
|
export type RaceBannerMode = 'project' | 'radio' | 'hidden';
|
|
86
87
|
export type RaceBannerEnabledMode = 'project' | 'radio';
|
|
87
88
|
export type RaceDataBannerSize = 'auto' | 'custom';
|
|
88
89
|
export type TimingTowerVerticalFit = 'expand-race-view' | 'scroll';
|
|
90
|
+
export type TimingGapMode = 'interval' | 'leader';
|
|
89
91
|
export type LayoutPreset = 'standard' | 'left-tower-overlay';
|
|
90
92
|
export type CameraControlsMode = 'embedded' | 'external' | false;
|
|
91
93
|
export type PaddockPresetName = 'dashboard' | 'timing-overlay' | 'compact-race' | 'full-dashboard';
|
|
@@ -111,6 +113,7 @@ export interface TeamData {
|
|
|
111
113
|
name?: string;
|
|
112
114
|
color?: string;
|
|
113
115
|
icon?: string;
|
|
116
|
+
theme?: PaddockThemeSelector;
|
|
114
117
|
pitCrew?: PaddockPitCrewStats;
|
|
115
118
|
pitCrewStats?: PaddockPitCrewStats;
|
|
116
119
|
}
|
|
@@ -254,12 +257,110 @@ export interface NormalizedSimulatorDriver extends SimulatorDriver {
|
|
|
254
257
|
};
|
|
255
258
|
}
|
|
256
259
|
|
|
257
|
-
export
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
export type PaddockThemeMode = 'dark' | 'light' | 'system';
|
|
261
|
+
export type PaddockThemeSelector = 'default' | 'active' | 'selectedTeam' | 'team' | `team:${string}` | string;
|
|
262
|
+
export type PaddockThemeTokenValue = string | { light?: string; dark?: string };
|
|
263
|
+
export type PaddockThemeTokenName =
|
|
264
|
+
| 'primary'
|
|
265
|
+
| 'primaryText'
|
|
266
|
+
| 'secondary'
|
|
267
|
+
| 'secondaryText'
|
|
268
|
+
| 'surface'
|
|
269
|
+
| 'surfaceRaised'
|
|
270
|
+
| 'surfacePanel'
|
|
271
|
+
| 'text'
|
|
272
|
+
| 'mutedText'
|
|
273
|
+
| 'border'
|
|
274
|
+
| 'success'
|
|
275
|
+
| 'warning'
|
|
276
|
+
| 'danger'
|
|
277
|
+
| 'info'
|
|
278
|
+
| 'yellowFlag'
|
|
279
|
+
| 'greenFlag'
|
|
280
|
+
| 'redFlag'
|
|
281
|
+
| 'safetyCar'
|
|
282
|
+
| 'drsActive'
|
|
283
|
+
| 'pitLane'
|
|
284
|
+
| 'track'
|
|
285
|
+
| 'trackEdge'
|
|
286
|
+
| 'timingTowerMaxWidth'
|
|
287
|
+
| 'raceViewMinHeight';
|
|
288
|
+
|
|
289
|
+
export interface F1SimulatorThemeTokens extends Partial<Record<PaddockThemeTokenName, PaddockThemeTokenValue>> {
|
|
290
|
+
/** Compatibility alias for `primary`. Prefer `tokens.primary` in new 3.0.0 code. */
|
|
291
|
+
accentColor?: PaddockThemeTokenValue;
|
|
292
|
+
/** Compatibility alias for `greenFlag`. Prefer `tokens.greenFlag` or `tokens.success`. */
|
|
293
|
+
greenColor?: PaddockThemeTokenValue;
|
|
294
|
+
/** Compatibility alias for `yellowFlag`. Prefer `tokens.yellowFlag` or `tokens.warning`. */
|
|
295
|
+
yellowColor?: PaddockThemeTokenValue;
|
|
296
|
+
/** Compatibility alias for `info`. */
|
|
297
|
+
blueColor?: PaddockThemeTokenValue;
|
|
298
|
+
/** Compatibility alias for `redFlag`. Prefer `tokens.redFlag` or `tokens.danger`. */
|
|
299
|
+
raceControlRedColor?: PaddockThemeTokenValue;
|
|
300
|
+
/** Compatibility alias for `surface`. */
|
|
301
|
+
surfaceColor?: PaddockThemeTokenValue;
|
|
302
|
+
/** Compatibility alias for `surfaceRaised`. */
|
|
303
|
+
surfaceRaisedColor?: PaddockThemeTokenValue;
|
|
304
|
+
/** Compatibility alias for `surfacePanel`. */
|
|
305
|
+
surfacePanelColor?: PaddockThemeTokenValue;
|
|
306
|
+
/** Compatibility alias for `border`. */
|
|
307
|
+
lineColor?: PaddockThemeTokenValue;
|
|
308
|
+
/** Compatibility alias for `border`. */
|
|
309
|
+
lineStrongColor?: PaddockThemeTokenValue;
|
|
310
|
+
/** Compatibility alias for `text`. */
|
|
311
|
+
textColor?: PaddockThemeTokenValue;
|
|
312
|
+
/** Compatibility alias for `mutedText`. */
|
|
313
|
+
mutedTextColor?: PaddockThemeTokenValue;
|
|
314
|
+
/** Compatibility alias for `track`. */
|
|
315
|
+
trackColor?: PaddockThemeTokenValue;
|
|
316
|
+
/** Compatibility alias for `trackEdge`. */
|
|
317
|
+
trackEdgeColor?: PaddockThemeTokenValue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface F1SimulatorThemePackage extends F1SimulatorThemeTokens {
|
|
321
|
+
extends?: string;
|
|
322
|
+
tokens?: F1SimulatorThemeTokens;
|
|
323
|
+
components?: Record<string, Partial<Record<string, PaddockThemeTokenName>>>;
|
|
324
|
+
light?: F1SimulatorThemeTokens;
|
|
325
|
+
dark?: F1SimulatorThemeTokens;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export interface F1SimulatorTheme extends Omit<F1SimulatorThemePackage, 'components'> {
|
|
329
|
+
mode?: PaddockThemeMode;
|
|
330
|
+
use?: string;
|
|
331
|
+
themes?: Record<string, F1SimulatorThemePackage>;
|
|
332
|
+
componentThemes?: Record<string, PaddockThemeSelector | { theme: PaddockThemeSelector }>;
|
|
333
|
+
teamThemes?: Record<string, PaddockThemeSelector | { theme: PaddockThemeSelector }>;
|
|
334
|
+
components?: Record<
|
|
335
|
+
string,
|
|
336
|
+
PaddockThemeSelector | { theme: PaddockThemeSelector } | Partial<Record<string, PaddockThemeTokenName>>
|
|
337
|
+
>;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export type ResolvedPaddockThemeMode = Exclude<PaddockThemeMode, 'system'>;
|
|
341
|
+
|
|
342
|
+
export interface ResolvedPaddockThemePackage {
|
|
343
|
+
tokens: Record<ResolvedPaddockThemeMode, F1SimulatorThemeTokens>;
|
|
344
|
+
components: Record<string, Partial<Record<string, PaddockThemeTokenName>>>;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export interface ResolvedPaddockTheme {
|
|
348
|
+
mode: PaddockThemeMode;
|
|
349
|
+
activeMode: ResolvedPaddockThemeMode;
|
|
350
|
+
use: string;
|
|
351
|
+
tokens: Record<ResolvedPaddockThemeMode, F1SimulatorThemeTokens>;
|
|
352
|
+
activeTokens: F1SimulatorThemeTokens;
|
|
353
|
+
components: Record<string, Partial<Record<string, PaddockThemeTokenName>>>;
|
|
354
|
+
themes: Record<string, ResolvedPaddockThemePackage>;
|
|
355
|
+
componentThemes: Record<string, PaddockThemeSelector>;
|
|
356
|
+
teamThemes: Record<string, PaddockThemeSelector>;
|
|
357
|
+
light: F1SimulatorThemeTokens;
|
|
358
|
+
dark: F1SimulatorThemeTokens;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface PaddockThemeSyncOptions {
|
|
362
|
+
attribute?: string;
|
|
363
|
+
map?: Record<string, PaddockThemeMode | F1SimulatorTheme>;
|
|
263
364
|
}
|
|
264
365
|
|
|
265
366
|
export interface F1SimulatorUiOptions {
|
|
@@ -277,10 +378,15 @@ export interface F1SimulatorUiOptions {
|
|
|
277
378
|
};
|
|
278
379
|
raceDataBannerSize?: RaceDataBannerSize;
|
|
279
380
|
raceDataTelemetryDetail?: boolean;
|
|
381
|
+
driverCamera?: boolean;
|
|
280
382
|
penaltyBanners?: boolean;
|
|
281
383
|
timingPenaltyBadges?: boolean;
|
|
282
384
|
simulationSpeedControl?: boolean;
|
|
283
385
|
timingTowerVerticalFit?: TimingTowerVerticalFit;
|
|
386
|
+
timingGapMode?: TimingGapMode;
|
|
387
|
+
timingGapModeToggle?: boolean;
|
|
388
|
+
timingEntryVerticalPadding?: number;
|
|
389
|
+
responsiveNarrowLayout?: boolean;
|
|
284
390
|
}
|
|
285
391
|
|
|
286
392
|
export interface F1SimulatorDebugOptions {
|
|
@@ -638,6 +744,18 @@ export interface WheelSurfaceSnapshot {
|
|
|
638
744
|
fullyOutsideWhiteLine: boolean;
|
|
639
745
|
}
|
|
640
746
|
|
|
747
|
+
export interface CarTrackStateSnapshot {
|
|
748
|
+
distance: number;
|
|
749
|
+
signedOffset: number;
|
|
750
|
+
crossTrackError: number;
|
|
751
|
+
surface: string;
|
|
752
|
+
inPitLane: boolean;
|
|
753
|
+
pitLanePart: 'entry' | 'fast-lane' | 'working-lane' | 'exit' | 'service-box' | 'garage-box' | null;
|
|
754
|
+
pitBoxId: string | null;
|
|
755
|
+
curvature: number;
|
|
756
|
+
heading: number;
|
|
757
|
+
}
|
|
758
|
+
|
|
641
759
|
export interface CarSnapshot {
|
|
642
760
|
id: string;
|
|
643
761
|
rank: number | null;
|
|
@@ -683,6 +801,7 @@ export interface CarSnapshot {
|
|
|
683
801
|
stabilityState?: PaddockStabilityState;
|
|
684
802
|
signedOffset?: number;
|
|
685
803
|
crossTrackError?: number;
|
|
804
|
+
trackState?: CarTrackStateSnapshot | null;
|
|
686
805
|
inPitLane?: boolean;
|
|
687
806
|
wheels?: WheelSurfaceSnapshot[];
|
|
688
807
|
pitLanePart?: 'entry' | 'fast-lane' | 'working-lane' | 'exit' | 'service-box' | 'garage-box' | null;
|
|
@@ -791,6 +910,7 @@ export interface F1SimulatorExpertOptions {
|
|
|
791
910
|
enabled: boolean;
|
|
792
911
|
controlledDrivers: string[];
|
|
793
912
|
frameSkip?: number;
|
|
913
|
+
episode?: PaddockEnvironmentOptions['episode'];
|
|
794
914
|
visualizeSensors?: boolean | {
|
|
795
915
|
rays?: boolean;
|
|
796
916
|
drivers?: 'selected' | 'all' | string[];
|
|
@@ -862,7 +982,6 @@ export interface F1SimulatorOptions extends F1SimulatorCallbacks {
|
|
|
862
982
|
seed?: number;
|
|
863
983
|
trackSeed?: number;
|
|
864
984
|
trackGeneration?: PaddockProceduralTrackOptions;
|
|
865
|
-
trackQueryIndex?: boolean;
|
|
866
985
|
warmup?: PaddockWarmupOptions | boolean;
|
|
867
986
|
totalLaps?: number;
|
|
868
987
|
physicsMode?: PaddockPhysicsMode;
|
|
@@ -889,6 +1008,7 @@ export interface MountRaceCanvasOptions {
|
|
|
889
1008
|
includeTimingTower?: boolean;
|
|
890
1009
|
includeTelemetrySectorBanner?: boolean;
|
|
891
1010
|
timingTowerVerticalFit?: TimingTowerVerticalFit;
|
|
1011
|
+
responsiveNarrowLayout?: boolean;
|
|
892
1012
|
}
|
|
893
1013
|
|
|
894
1014
|
export interface MountTelemetryPanelOptions {
|
|
@@ -899,12 +1019,17 @@ export interface MountRaceTelemetryDrawerOptions {
|
|
|
899
1019
|
timingTowerVerticalFit?: TimingTowerVerticalFit;
|
|
900
1020
|
drawerInitiallyOpen?: boolean;
|
|
901
1021
|
raceDataTelemetryDetail?: boolean;
|
|
1022
|
+
responsiveNarrowLayout?: boolean;
|
|
902
1023
|
}
|
|
903
1024
|
|
|
904
1025
|
export interface F1MountedSimulator {
|
|
905
1026
|
readonly expert: F1SimulatorExpertApi | null;
|
|
906
1027
|
destroy(): void;
|
|
907
1028
|
restart(nextOptions?: F1SimulatorRestartOptions): void;
|
|
1029
|
+
setTheme(theme: F1SimulatorTheme): ResolvedPaddockTheme;
|
|
1030
|
+
setThemeMode(mode: PaddockThemeMode): ResolvedPaddockTheme;
|
|
1031
|
+
getTheme(): ResolvedPaddockTheme;
|
|
1032
|
+
syncThemeFrom(source: Element, options?: PaddockThemeSyncOptions): () => void;
|
|
908
1033
|
selectDriver(driverId: string): void;
|
|
909
1034
|
setSafetyCarDeployed(deployed: boolean): void;
|
|
910
1035
|
setRedFlagDeployed(deployed: boolean): void;
|
|
@@ -916,6 +1041,9 @@ export interface F1MountedSimulator {
|
|
|
916
1041
|
getPitIntent(driverId: string): PaddockPitIntent;
|
|
917
1042
|
getPitTargetCompound(driverId: string): TireCompound | string | null;
|
|
918
1043
|
getSimulationSpeed(): number;
|
|
1044
|
+
setTimingGapMode(mode: TimingGapMode): TimingGapMode;
|
|
1045
|
+
getTimingGapMode(): TimingGapMode;
|
|
1046
|
+
toggleTimingGapMode(): TimingGapMode;
|
|
919
1047
|
servePenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
920
1048
|
cancelPenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
921
1049
|
getSnapshot(): RaceSnapshot | null;
|
|
@@ -942,6 +1070,10 @@ export interface PaddockSimulatorController {
|
|
|
942
1070
|
start(): Promise<PaddockSimulatorController>;
|
|
943
1071
|
destroy(): void;
|
|
944
1072
|
restart(nextOptions?: F1SimulatorRestartOptions): void;
|
|
1073
|
+
setTheme(theme: F1SimulatorTheme): ResolvedPaddockTheme;
|
|
1074
|
+
setThemeMode(mode: PaddockThemeMode): ResolvedPaddockTheme;
|
|
1075
|
+
getTheme(): ResolvedPaddockTheme;
|
|
1076
|
+
syncThemeFrom(source: Element, options?: PaddockThemeSyncOptions): () => void;
|
|
945
1077
|
selectDriver(driverId: string): void;
|
|
946
1078
|
setSafetyCarDeployed(deployed: boolean): void;
|
|
947
1079
|
setRedFlagDeployed(deployed: boolean): void;
|
|
@@ -953,6 +1085,9 @@ export interface PaddockSimulatorController {
|
|
|
953
1085
|
getPitIntent(driverId: string): PaddockPitIntent;
|
|
954
1086
|
getPitTargetCompound(driverId: string): TireCompound | string | null;
|
|
955
1087
|
getSimulationSpeed(): number;
|
|
1088
|
+
setTimingGapMode(mode: TimingGapMode): TimingGapMode;
|
|
1089
|
+
getTimingGapMode(): TimingGapMode;
|
|
1090
|
+
toggleTimingGapMode(): TimingGapMode;
|
|
956
1091
|
servePenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
957
1092
|
cancelPenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
958
1093
|
getSnapshot(): RaceSnapshot | null;
|
|
@@ -961,10 +1096,13 @@ export interface PaddockSimulatorController {
|
|
|
961
1096
|
export const CHAMPIONSHIP_ENTRY_BLUEPRINTS: ChampionshipEntryBlueprint[];
|
|
962
1097
|
export const DEMO_PROJECT_DRIVERS: SimulatorDriver[];
|
|
963
1098
|
export const DEFAULT_F1_SIMULATOR_ASSETS: Required<F1SimulatorAssets>;
|
|
1099
|
+
export const DEFAULT_PADDOCK_THEME: F1SimulatorTheme;
|
|
964
1100
|
export const PADDOCK_SIMULATOR_PRESETS: Record<PaddockPresetName, {
|
|
965
1101
|
ui?: F1SimulatorUiOptions;
|
|
966
1102
|
theme?: F1SimulatorTheme;
|
|
967
1103
|
}>;
|
|
1104
|
+
export const PADDOCK_THEME_CSS_VARIABLES: Record<PaddockThemeTokenName, string>;
|
|
1105
|
+
export const PADDOCK_THEME_TOKEN_KEYS: readonly PaddockThemeTokenName[];
|
|
968
1106
|
|
|
969
1107
|
export const REAL_F1_CAR_LENGTH_METERS: number;
|
|
970
1108
|
export const SIM_UNITS_PER_METER: number;
|
|
@@ -980,6 +1118,12 @@ export function normalizeSimulatorDrivers(
|
|
|
980
1118
|
drivers: SimulatorDriver[],
|
|
981
1119
|
options?: { entries?: ChampionshipEntryBlueprint[]; caller?: string },
|
|
982
1120
|
): NormalizedSimulatorDriver[];
|
|
1121
|
+
export function resolvePaddockTheme(theme?: F1SimulatorTheme): ResolvedPaddockTheme;
|
|
1122
|
+
export function applyPaddockTheme(
|
|
1123
|
+
root: Element,
|
|
1124
|
+
theme?: F1SimulatorTheme | ResolvedPaddockTheme,
|
|
1125
|
+
context?: { selectedTeamId?: string | null },
|
|
1126
|
+
): void;
|
|
983
1127
|
|
|
984
1128
|
export function metersToSimUnits(meters: number): number;
|
|
985
1129
|
export function simUnitsToMeters(simUnits: number): number;
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import './styles.css';
|
|
|
2
2
|
import { F1SimulatorApp } from './app/F1SimulatorApp.js';
|
|
3
3
|
import { resolveF1SimulatorOptions } from './config/defaultOptions.js';
|
|
4
4
|
import { mergeRestartOptions } from './config/restartOptions.js';
|
|
5
|
+
import { createThemeSync, resolveRuntimeThemeModeOptions, resolveRuntimeThemeOptions } from './api/runtimeTheme.js';
|
|
5
6
|
import { createF1SimulatorShell } from './ui/shellTemplate.js';
|
|
6
7
|
export {
|
|
7
8
|
createPaddockSimulator,
|
|
@@ -30,7 +31,14 @@ export {
|
|
|
30
31
|
} from './data/championship.js';
|
|
31
32
|
export { DEMO_PROJECT_DRIVERS } from './data/demoDrivers.js';
|
|
32
33
|
export { DEFAULT_F1_SIMULATOR_ASSETS } from './config/defaultAssets.js';
|
|
33
|
-
export {
|
|
34
|
+
export {
|
|
35
|
+
DEFAULT_PADDOCK_THEME,
|
|
36
|
+
PADDOCK_SIMULATOR_PRESETS,
|
|
37
|
+
PADDOCK_THEME_CSS_VARIABLES,
|
|
38
|
+
PADDOCK_THEME_TOKEN_KEYS,
|
|
39
|
+
applyPaddockTheme,
|
|
40
|
+
resolvePaddockTheme,
|
|
41
|
+
} from './config/defaultOptions.js';
|
|
34
42
|
export { createPaddockDriverControllerLoop } from './environment/controllerLoop.js';
|
|
35
43
|
export { normalizeSimulatorDrivers } from './data/normalizeDrivers.js';
|
|
36
44
|
export { createProceduralTrack } from './simulation/track/trackModel.js';
|
|
@@ -61,7 +69,22 @@ export async function mountF1Simulator(root, options = {}) {
|
|
|
61
69
|
const app = new F1SimulatorApp(shell, resolvedOptions);
|
|
62
70
|
await app.init();
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
const syncResolvedTimingGapMode = (timingGapMode) => {
|
|
73
|
+
resolvedOptions = {
|
|
74
|
+
...resolvedOptions,
|
|
75
|
+
ui: {
|
|
76
|
+
...resolvedOptions.ui,
|
|
77
|
+
timingGapMode,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
return timingGapMode;
|
|
81
|
+
};
|
|
82
|
+
const syncResolvedRuntimeOptions = () => {
|
|
83
|
+
syncResolvedTimingGapMode(app.getTimingGapMode());
|
|
84
|
+
return resolvedOptions;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const mountedSimulator = {
|
|
65
88
|
get expert() {
|
|
66
89
|
return app.expert ?? null;
|
|
67
90
|
},
|
|
@@ -70,10 +93,26 @@ export async function mountF1Simulator(root, options = {}) {
|
|
|
70
93
|
root.innerHTML = '';
|
|
71
94
|
},
|
|
72
95
|
restart(nextOptions = {}) {
|
|
73
|
-
const nextResolvedOptions = resolveF1SimulatorOptions(mergeRestartOptions(
|
|
96
|
+
const nextResolvedOptions = resolveF1SimulatorOptions(mergeRestartOptions(syncResolvedRuntimeOptions(), nextOptions));
|
|
74
97
|
app.restart(nextResolvedOptions);
|
|
75
98
|
resolvedOptions = nextResolvedOptions;
|
|
76
99
|
},
|
|
100
|
+
setTheme(themeInput = {}) {
|
|
101
|
+
const nextResolvedOptions = resolveRuntimeThemeOptions(syncResolvedRuntimeOptions(), themeInput);
|
|
102
|
+
resolvedOptions = nextResolvedOptions;
|
|
103
|
+
return app.setTheme(nextResolvedOptions.theme);
|
|
104
|
+
},
|
|
105
|
+
setThemeMode(mode) {
|
|
106
|
+
const nextResolvedOptions = resolveRuntimeThemeModeOptions(syncResolvedRuntimeOptions(), mode);
|
|
107
|
+
resolvedOptions = nextResolvedOptions;
|
|
108
|
+
return app.setTheme(nextResolvedOptions.theme);
|
|
109
|
+
},
|
|
110
|
+
getTheme() {
|
|
111
|
+
return resolvedOptions.theme;
|
|
112
|
+
},
|
|
113
|
+
syncThemeFrom(source, options = {}) {
|
|
114
|
+
return createThemeSync(mountedSimulator, source, options);
|
|
115
|
+
},
|
|
77
116
|
selectDriver(driverId) {
|
|
78
117
|
app.selectCar(driverId, { focus: true });
|
|
79
118
|
},
|
|
@@ -108,6 +147,15 @@ export async function mountF1Simulator(root, options = {}) {
|
|
|
108
147
|
getSimulationSpeed() {
|
|
109
148
|
return app.simulationSpeed;
|
|
110
149
|
},
|
|
150
|
+
setTimingGapMode(mode) {
|
|
151
|
+
return syncResolvedTimingGapMode(app.setTimingGapMode(mode));
|
|
152
|
+
},
|
|
153
|
+
getTimingGapMode() {
|
|
154
|
+
return app.getTimingGapMode();
|
|
155
|
+
},
|
|
156
|
+
toggleTimingGapMode() {
|
|
157
|
+
return syncResolvedTimingGapMode(app.toggleTimingGapMode());
|
|
158
|
+
},
|
|
111
159
|
servePenalty(penaltyId) {
|
|
112
160
|
return app.servePenalty(penaltyId);
|
|
113
161
|
},
|
|
@@ -118,4 +166,5 @@ export async function mountF1Simulator(root, options = {}) {
|
|
|
118
166
|
return app.getSnapshot();
|
|
119
167
|
},
|
|
120
168
|
};
|
|
169
|
+
return mountedSimulator;
|
|
121
170
|
}
|