@inventure71/paddockjs 0.2.0 → 1.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 +45 -0
- package/INSTALL_AND_UPDATE.md +165 -0
- package/README.md +217 -20
- package/docs/architecture.md +84 -15
- package/docs/concepts.md +20 -3
- package/docs/data_contract.md +412 -20
- package/docs/data_model.md +2 -1
- package/docs/index.md +1 -0
- package/docs/learnings.md +1 -1
- package/docs/rules.md +140 -19
- package/docs/system_specs.md +74 -20
- package/docs/training.md +206 -0
- package/examples/train-basic-policy.mjs +276 -0
- package/examples/trainingData.mjs +133 -0
- package/package.json +18 -8
- package/src/api/PaddockSimulatorController.js +39 -4
- package/src/app/BrowserExpertAdapter.js +32 -0
- package/src/app/F1SimulatorApp.js +1251 -146
- package/src/app/domBindings.js +10 -2
- package/src/config/defaultOptions.js +11 -3
- package/src/data/championship.js +13 -0
- package/src/data/normalizeDrivers.js +7 -2
- package/src/environment/actions.js +68 -0
- package/src/environment/episode.js +17 -0
- package/src/environment/evaluation.js +184 -0
- package/src/environment/events.js +17 -0
- package/src/environment/index.d.ts +521 -0
- package/src/environment/index.js +15 -0
- package/src/environment/observationOptions.js +12 -0
- package/src/environment/observations.js +284 -0
- package/src/environment/options.js +157 -0
- package/src/environment/recorder.js +28 -0
- package/src/environment/rewards.js +69 -0
- package/src/environment/runtime.js +175 -0
- package/src/environment/scenarios.js +220 -0
- package/src/environment/sensors.js +367 -0
- package/src/environment/specs.js +163 -0
- package/src/environment/workerProtocol.js +52 -0
- package/src/index.d.ts +405 -6
- package/src/index.js +27 -2
- package/src/rendering/proceduralTrackAsset.js +343 -24
- package/src/simulation/collisionGeometry.js +214 -0
- package/src/simulation/driverController.js +431 -65
- package/src/simulation/raceSimulation.js +2140 -171
- package/src/simulation/rules/collisionSteward.js +69 -0
- package/src/simulation/rules/penaltyLedger.js +153 -0
- package/src/simulation/rules/pitLaneSpeedingSteward.js +71 -0
- package/src/simulation/rules/tireRequirementSteward.js +29 -0
- package/src/simulation/rules/trackLimitsSteward.js +85 -0
- package/src/simulation/rulesConfig.js +352 -0
- package/src/simulation/trackModel.js +1154 -76
- package/src/simulation/units.js +6 -4
- package/src/simulation/vehicleGeometry.js +247 -0
- package/src/simulation/vehiclePhysics.js +82 -50
- package/src/simulation/wheelSurface.js +338 -0
- package/src/styles.css +369 -67
- package/src/ui/bannerTemplates.js +69 -0
- package/src/ui/componentTemplates.js +45 -81
- package/src/ui/raceControlStatusBanner.js +39 -0
- package/src/ui/shellTemplate.js +1 -1
- package/src/ui/templateUtils.js +23 -0
- package/AGENTS.md +0 -86
- package/src/standalone.js +0 -15
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
import type { PaddockActionSpec, PaddockObservationSpec } from './environment/index.js';
|
|
2
|
+
|
|
3
|
+
export type { PaddockActionSpec, PaddockObservationSpec } from './environment/index.js';
|
|
4
|
+
|
|
1
5
|
export type TireCompound = 'S' | 'M' | 'H';
|
|
2
|
-
export type
|
|
6
|
+
export type PaddockPitIntent = 0 | 1 | 2;
|
|
7
|
+
export type PaddockPitIntentRequest = PaddockPitIntent | {
|
|
8
|
+
intent?: PaddockPitIntent;
|
|
9
|
+
pitIntent?: PaddockPitIntent;
|
|
10
|
+
targetCompound?: TireCompound | string;
|
|
11
|
+
compound?: TireCompound | string;
|
|
12
|
+
pitCompound?: TireCompound | string;
|
|
13
|
+
pitTargetCompound?: TireCompound | string;
|
|
14
|
+
targetTire?: TireCompound | string;
|
|
15
|
+
};
|
|
16
|
+
export type CameraMode = 'overview' | 'leader' | 'selected' | 'show-all' | 'pit';
|
|
3
17
|
export type RaceBannerMode = 'project' | 'radio' | 'hidden';
|
|
4
18
|
export type RaceBannerEnabledMode = 'project' | 'radio';
|
|
5
19
|
export type RaceDataBannerSize = 'auto' | 'custom';
|
|
@@ -22,6 +36,14 @@ export interface TeamData {
|
|
|
22
36
|
name?: string;
|
|
23
37
|
color?: string;
|
|
24
38
|
icon?: string;
|
|
39
|
+
pitCrew?: PaddockPitCrewStats;
|
|
40
|
+
pitCrewStats?: PaddockPitCrewStats;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface PaddockPitCrewStats {
|
|
44
|
+
speed?: number;
|
|
45
|
+
consistency?: number;
|
|
46
|
+
reliability?: number;
|
|
25
47
|
}
|
|
26
48
|
|
|
27
49
|
export interface SimulatorDriver {
|
|
@@ -179,6 +201,10 @@ export interface F1SimulatorUiOptions {
|
|
|
179
201
|
enabled?: true | false | RaceBannerEnabledMode[];
|
|
180
202
|
};
|
|
181
203
|
raceDataBannerSize?: RaceDataBannerSize;
|
|
204
|
+
raceDataTelemetryDetail?: boolean;
|
|
205
|
+
penaltyBanners?: boolean;
|
|
206
|
+
timingPenaltyBadges?: boolean;
|
|
207
|
+
simulationSpeedControl?: boolean;
|
|
182
208
|
timingTowerVerticalFit?: TimingTowerVerticalFit;
|
|
183
209
|
}
|
|
184
210
|
|
|
@@ -201,9 +227,113 @@ export interface TrackSnapshot {
|
|
|
201
227
|
width?: number;
|
|
202
228
|
length?: number;
|
|
203
229
|
sectors?: TrackSectorSnapshot[];
|
|
230
|
+
pitLane?: PitLaneSnapshot;
|
|
204
231
|
[key: string]: unknown;
|
|
205
232
|
}
|
|
206
233
|
|
|
234
|
+
export interface TrackPointSnapshot {
|
|
235
|
+
x: number;
|
|
236
|
+
y: number;
|
|
237
|
+
heading?: number;
|
|
238
|
+
distance?: number;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface PitLaneSnapshot {
|
|
242
|
+
enabled: boolean;
|
|
243
|
+
side: -1 | 1;
|
|
244
|
+
width: number;
|
|
245
|
+
offset?: number;
|
|
246
|
+
boxCount: number;
|
|
247
|
+
teamCount: number;
|
|
248
|
+
boxesPerTeam: number;
|
|
249
|
+
entry: {
|
|
250
|
+
trackDistance: number;
|
|
251
|
+
distanceFromStart: number;
|
|
252
|
+
trackPoint: TrackPointSnapshot;
|
|
253
|
+
edgePoint: TrackPointSnapshot;
|
|
254
|
+
trackConnectPoint: TrackPointSnapshot;
|
|
255
|
+
lanePoint: TrackPointSnapshot;
|
|
256
|
+
roadCenterline: TrackPointSnapshot[];
|
|
257
|
+
connector: TrackPointSnapshot[];
|
|
258
|
+
};
|
|
259
|
+
exit: {
|
|
260
|
+
trackDistance: number;
|
|
261
|
+
distanceFromStart: number;
|
|
262
|
+
trackPoint: TrackPointSnapshot;
|
|
263
|
+
edgePoint: TrackPointSnapshot;
|
|
264
|
+
trackConnectPoint: TrackPointSnapshot;
|
|
265
|
+
lanePoint: TrackPointSnapshot;
|
|
266
|
+
roadCenterline: TrackPointSnapshot[];
|
|
267
|
+
connector: TrackPointSnapshot[];
|
|
268
|
+
};
|
|
269
|
+
mainLane: {
|
|
270
|
+
start: TrackPointSnapshot;
|
|
271
|
+
end: TrackPointSnapshot;
|
|
272
|
+
points: TrackPointSnapshot[];
|
|
273
|
+
length: number;
|
|
274
|
+
heading: number;
|
|
275
|
+
};
|
|
276
|
+
fastLane?: {
|
|
277
|
+
offset: number;
|
|
278
|
+
width: number;
|
|
279
|
+
};
|
|
280
|
+
workingLane?: {
|
|
281
|
+
start: TrackPointSnapshot;
|
|
282
|
+
end: TrackPointSnapshot;
|
|
283
|
+
points: TrackPointSnapshot[];
|
|
284
|
+
offset: number;
|
|
285
|
+
width: number;
|
|
286
|
+
};
|
|
287
|
+
serviceNormal: { x: number; y: number };
|
|
288
|
+
teams?: PitTeamSnapshot[];
|
|
289
|
+
boxes: PitBoxSnapshot[];
|
|
290
|
+
serviceAreas?: PitServiceAreaSnapshot[];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export interface PitTeamSnapshot {
|
|
294
|
+
id: string;
|
|
295
|
+
name: string;
|
|
296
|
+
color: string;
|
|
297
|
+
index: number;
|
|
298
|
+
boxIds: string[];
|
|
299
|
+
serviceAreaId?: string | null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface PitBoxSnapshot {
|
|
303
|
+
id: string;
|
|
304
|
+
index: number;
|
|
305
|
+
teamIndex: number;
|
|
306
|
+
teamBoxIndex: number;
|
|
307
|
+
teamId?: string;
|
|
308
|
+
teamName?: string;
|
|
309
|
+
teamColor?: string;
|
|
310
|
+
distanceAlongLane: number;
|
|
311
|
+
laneTarget: TrackPointSnapshot;
|
|
312
|
+
center: TrackPointSnapshot;
|
|
313
|
+
length: number;
|
|
314
|
+
depth: number;
|
|
315
|
+
corners: TrackPointSnapshot[];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export interface PitServiceAreaSnapshot {
|
|
319
|
+
id: string;
|
|
320
|
+
index: number;
|
|
321
|
+
teamIndex: number;
|
|
322
|
+
teamId?: string;
|
|
323
|
+
teamName?: string;
|
|
324
|
+
teamColor?: string;
|
|
325
|
+
distanceAlongLane: number;
|
|
326
|
+
queueDistanceAlongLane: number;
|
|
327
|
+
laneTarget: TrackPointSnapshot;
|
|
328
|
+
center: TrackPointSnapshot;
|
|
329
|
+
queuePoint: TrackPointSnapshot;
|
|
330
|
+
length: number;
|
|
331
|
+
depth: number;
|
|
332
|
+
corners: TrackPointSnapshot[];
|
|
333
|
+
queueCorners: TrackPointSnapshot[];
|
|
334
|
+
garageBoxIds: string[];
|
|
335
|
+
}
|
|
336
|
+
|
|
207
337
|
export interface TrackSectorSnapshot {
|
|
208
338
|
index: number;
|
|
209
339
|
id: string;
|
|
@@ -222,6 +352,8 @@ export interface LapTelemetrySnapshot {
|
|
|
222
352
|
currentSectorElapsed: number | null;
|
|
223
353
|
currentSectorProgress: number | null;
|
|
224
354
|
currentSectors: Array<number | null>;
|
|
355
|
+
sectorProgress: Array<number | null>;
|
|
356
|
+
liveSectors: Array<number | null>;
|
|
225
357
|
sectorPerformance: {
|
|
226
358
|
current: Array<SectorPerformanceStatus | null>;
|
|
227
359
|
last: Array<SectorPerformanceStatus | null>;
|
|
@@ -234,12 +366,187 @@ export interface LapTelemetrySnapshot {
|
|
|
234
366
|
completedLaps: number;
|
|
235
367
|
}
|
|
236
368
|
|
|
369
|
+
export interface PitStopSnapshot {
|
|
370
|
+
status: 'pending' | 'entering' | 'queued' | 'servicing' | 'exiting' | 'completed';
|
|
371
|
+
intent: PaddockPitIntent;
|
|
372
|
+
phase: 'entry' | 'queue' | 'queue-release' | 'penalty' | 'service' | 'exit' | null;
|
|
373
|
+
boxIndex: number;
|
|
374
|
+
boxId: string;
|
|
375
|
+
garageBoxIndex?: number | null;
|
|
376
|
+
garageBoxId?: string | null;
|
|
377
|
+
teamId?: string | null;
|
|
378
|
+
teamColor?: string | null;
|
|
379
|
+
stopsCompleted: number;
|
|
380
|
+
queueingForService?: boolean;
|
|
381
|
+
plannedRaceDistance: number | null;
|
|
382
|
+
entryRaceDistance: number | null;
|
|
383
|
+
serviceRemainingSeconds: number | null;
|
|
384
|
+
penaltyServiceRemainingSeconds: number | null;
|
|
385
|
+
penaltyServiceTotalSeconds: number | null;
|
|
386
|
+
servingPenaltyIds: string[];
|
|
387
|
+
targetTire?: TireCompound | string | null;
|
|
388
|
+
serviceProfile?: {
|
|
389
|
+
baseSeconds: number;
|
|
390
|
+
seconds: number;
|
|
391
|
+
perfect: boolean;
|
|
392
|
+
variabilityEnabled: boolean;
|
|
393
|
+
teamId?: string | null;
|
|
394
|
+
pitCrew: Required<PaddockPitCrewStats>;
|
|
395
|
+
speedDeltaSeconds: number;
|
|
396
|
+
consistencyDeltaSeconds: number;
|
|
397
|
+
issueDelaySeconds: number;
|
|
398
|
+
issue?: string | null;
|
|
399
|
+
} | null;
|
|
400
|
+
}
|
|
401
|
+
|
|
237
402
|
export interface RaceEvent {
|
|
238
403
|
type: string;
|
|
239
404
|
at?: number;
|
|
405
|
+
firstShapeId?: string;
|
|
406
|
+
secondShapeId?: string;
|
|
407
|
+
contactType?: 'body-body' | 'body-wheel' | 'wheel-body' | 'wheel-wheel' | string;
|
|
408
|
+
depth?: number;
|
|
409
|
+
timeOfImpact?: number;
|
|
410
|
+
[key: string]: unknown;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export type PaddockRulesetName = 'paddock' | 'custom' | 'grandPrix2025' | 'fia2025';
|
|
414
|
+
|
|
415
|
+
export interface PaddockPitStopRules {
|
|
416
|
+
enabled?: boolean;
|
|
417
|
+
pitLaneSpeedLimitKph?: number;
|
|
418
|
+
defaultStopSeconds?: number;
|
|
419
|
+
variability?: {
|
|
420
|
+
enabled?: boolean;
|
|
421
|
+
perfect?: boolean;
|
|
422
|
+
speedImpactSeconds?: number;
|
|
423
|
+
consistencyJitterSeconds?: number;
|
|
424
|
+
issueChance?: number;
|
|
425
|
+
issueMaxDelaySeconds?: number;
|
|
426
|
+
};
|
|
427
|
+
maxConcurrentPitLaneCars?: number;
|
|
428
|
+
minimumPitLaneGapMeters?: number;
|
|
429
|
+
doubleStacking?: boolean;
|
|
430
|
+
tirePitRequestThresholdPercent?: number;
|
|
431
|
+
tirePitCommitThresholdPercent?: number;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export interface PaddockTireStrategyRules {
|
|
435
|
+
enabled?: boolean;
|
|
436
|
+
compounds?: TireCompound[];
|
|
437
|
+
mandatoryDistinctDryCompounds?: number | null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export interface PaddockPenaltySubsectionRules {
|
|
441
|
+
strictness?: number;
|
|
442
|
+
timePenaltySeconds?: number;
|
|
443
|
+
consequences?: PaddockPenaltyConsequence[];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export type PaddockPenaltyConsequence =
|
|
447
|
+
| { type: 'warning' }
|
|
448
|
+
| { type: 'time'; seconds: number }
|
|
449
|
+
| { type: 'driveThrough'; conversionSeconds?: number }
|
|
450
|
+
| { type: 'stopGo'; seconds?: number; conversionSeconds?: number }
|
|
451
|
+
| { type: 'positionDrop'; positions: number }
|
|
452
|
+
| { type: 'gridDrop'; positions: number }
|
|
453
|
+
| { type: 'disqualification' };
|
|
454
|
+
|
|
455
|
+
export type PaddockPenaltyStatus = 'issued' | 'served' | 'applied' | 'cancelled';
|
|
456
|
+
|
|
457
|
+
export interface PaddockTrackLimitPenaltyRules extends PaddockPenaltySubsectionRules {
|
|
458
|
+
warningsBeforePenalty?: number;
|
|
459
|
+
relaxedMarginMeters?: number;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export interface PaddockCollisionPenaltyRules extends PaddockPenaltySubsectionRules {
|
|
463
|
+
minimumSeverity?: number;
|
|
464
|
+
relaxedSeverityMargin?: number;
|
|
465
|
+
minimumImpactSpeedKph?: number;
|
|
466
|
+
relaxedImpactSpeedKph?: number;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export interface PaddockPitLaneSpeedingPenaltyRules extends PaddockPenaltySubsectionRules {
|
|
470
|
+
speedLimitKph?: number;
|
|
471
|
+
marginKph?: number;
|
|
472
|
+
relaxedMarginKph?: number;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export interface PaddockPenaltyRules {
|
|
476
|
+
enabled?: boolean;
|
|
477
|
+
stewardStrictness?: number;
|
|
478
|
+
trackLimits?: PaddockTrackLimitPenaltyRules;
|
|
479
|
+
collision?: PaddockCollisionPenaltyRules;
|
|
480
|
+
tireRequirement?: PaddockPenaltySubsectionRules;
|
|
481
|
+
pitLaneSpeeding?: PaddockPitLaneSpeedingPenaltyRules;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export interface PaddockRaceModules {
|
|
485
|
+
pitStops?: PaddockPitStopRules;
|
|
486
|
+
tireStrategy?: PaddockTireStrategyRules;
|
|
487
|
+
penalties?: PaddockPenaltyRules;
|
|
488
|
+
weather?: { enabled?: boolean };
|
|
489
|
+
reliability?: { enabled?: boolean };
|
|
490
|
+
fuelLoad?: { enabled?: boolean };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export interface PaddockRaceRules {
|
|
494
|
+
ruleset?: PaddockRulesetName;
|
|
495
|
+
profile?: PaddockRulesetName;
|
|
496
|
+
modules?: PaddockRaceModules;
|
|
497
|
+
drsDetectionSeconds?: number;
|
|
498
|
+
safetyCarSpeed?: number;
|
|
499
|
+
safetyCarLeadDistance?: number;
|
|
500
|
+
safetyCarGap?: number;
|
|
501
|
+
collisionRestitution?: number;
|
|
502
|
+
standingStart?: boolean;
|
|
503
|
+
startLightCount?: number;
|
|
504
|
+
startLightInterval?: number;
|
|
505
|
+
startLightsOutHold?: number;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export interface PaddockPenaltyEntry {
|
|
509
|
+
id: string;
|
|
510
|
+
type: string;
|
|
511
|
+
at: number;
|
|
512
|
+
lap: number;
|
|
513
|
+
driverId: string;
|
|
514
|
+
strictness: number;
|
|
515
|
+
status: PaddockPenaltyStatus;
|
|
516
|
+
penaltySeconds: number;
|
|
517
|
+
pendingPenaltySeconds?: number;
|
|
518
|
+
consequences: PaddockPenaltyConsequence[];
|
|
519
|
+
serviceType?: 'driveThrough' | 'stopGo' | null;
|
|
520
|
+
serviceRequired?: boolean;
|
|
521
|
+
serviceServedAt?: number | null;
|
|
522
|
+
appliedAt?: number;
|
|
523
|
+
cancelledAt?: number;
|
|
524
|
+
unserved?: boolean;
|
|
525
|
+
positionDrop?: number;
|
|
526
|
+
gridDrop?: number;
|
|
527
|
+
disqualified?: boolean;
|
|
528
|
+
reason?: string;
|
|
529
|
+
otherCarId?: string;
|
|
530
|
+
aheadDriverId?: string;
|
|
531
|
+
atFaultDriverId?: string;
|
|
532
|
+
sharedFault?: boolean;
|
|
533
|
+
impactSpeedKph?: number;
|
|
534
|
+
impactSpeedThresholdKph?: number;
|
|
240
535
|
[key: string]: unknown;
|
|
241
536
|
}
|
|
242
537
|
|
|
538
|
+
export interface WheelSurfaceSnapshot {
|
|
539
|
+
id: string;
|
|
540
|
+
x: number;
|
|
541
|
+
y: number;
|
|
542
|
+
signedOffset: number;
|
|
543
|
+
crossTrackError: number;
|
|
544
|
+
surface: string;
|
|
545
|
+
onTrack: boolean;
|
|
546
|
+
inPitLane: boolean;
|
|
547
|
+
fullyOutsideWhiteLine: boolean;
|
|
548
|
+
}
|
|
549
|
+
|
|
243
550
|
export interface CarSnapshot {
|
|
244
551
|
id: string;
|
|
245
552
|
rank: number;
|
|
@@ -250,12 +557,32 @@ export interface CarSnapshot {
|
|
|
250
557
|
tire: TireCompound;
|
|
251
558
|
lap: number;
|
|
252
559
|
speedKph: number;
|
|
560
|
+
finishRank?: number | null;
|
|
561
|
+
status?: 'racing' | 'waved-flag' | string;
|
|
562
|
+
raceStatus?: 'racing' | 'waved-flag' | string;
|
|
563
|
+
wavedFlag?: boolean;
|
|
253
564
|
finished?: boolean;
|
|
254
565
|
finishTime?: number | null;
|
|
566
|
+
penaltySeconds?: number;
|
|
567
|
+
adjustedFinishTime?: number | null;
|
|
255
568
|
classifiedRank?: number | null;
|
|
256
569
|
intervalAheadSeconds?: number | null;
|
|
257
570
|
leaderGapSeconds?: number | null;
|
|
571
|
+
gapAheadLaps?: number;
|
|
572
|
+
intervalAheadLaps?: number;
|
|
573
|
+
leaderGapLaps?: number;
|
|
258
574
|
lapTelemetry?: LapTelemetrySnapshot;
|
|
575
|
+
surface?: string;
|
|
576
|
+
signedOffset?: number;
|
|
577
|
+
crossTrackError?: number;
|
|
578
|
+
inPitLane?: boolean;
|
|
579
|
+
wheels?: WheelSurfaceSnapshot[];
|
|
580
|
+
pitLanePart?: 'entry' | 'fast-lane' | 'working-lane' | 'exit' | 'service-box' | 'garage-box' | null;
|
|
581
|
+
pitBoxId?: string | null;
|
|
582
|
+
pitLaneCrossTrackError?: number | null;
|
|
583
|
+
usedTireCompounds?: Array<TireCompound | string>;
|
|
584
|
+
pitIntent?: PaddockPitIntent;
|
|
585
|
+
pitStop?: PitStopSnapshot | null;
|
|
259
586
|
[key: string]: unknown;
|
|
260
587
|
}
|
|
261
588
|
|
|
@@ -266,6 +593,12 @@ export interface RaceClassificationEntry {
|
|
|
266
593
|
lap: number;
|
|
267
594
|
finished: boolean;
|
|
268
595
|
finishTime?: number | null;
|
|
596
|
+
penaltySeconds?: number;
|
|
597
|
+
adjustedFinishTime?: number | null;
|
|
598
|
+
gapLaps?: number;
|
|
599
|
+
intervalLaps?: number;
|
|
600
|
+
positionDrop?: number;
|
|
601
|
+
disqualified?: boolean;
|
|
269
602
|
[key: string]: unknown;
|
|
270
603
|
}
|
|
271
604
|
|
|
@@ -280,6 +613,15 @@ export interface RaceSnapshot {
|
|
|
280
613
|
totalLaps: number;
|
|
281
614
|
raceControl: {
|
|
282
615
|
mode: string;
|
|
616
|
+
redFlag: boolean;
|
|
617
|
+
pitLaneOpen: boolean;
|
|
618
|
+
pitLaneStatus?: {
|
|
619
|
+
enabled: boolean;
|
|
620
|
+
open: boolean;
|
|
621
|
+
reason: string;
|
|
622
|
+
color: string;
|
|
623
|
+
light: string;
|
|
624
|
+
};
|
|
283
625
|
finished: boolean;
|
|
284
626
|
finishedAt: number | null;
|
|
285
627
|
winner: CarSnapshot | null;
|
|
@@ -289,12 +631,20 @@ export interface RaceSnapshot {
|
|
|
289
631
|
[key: string]: unknown;
|
|
290
632
|
};
|
|
291
633
|
};
|
|
634
|
+
pitLaneStatus?: {
|
|
635
|
+
enabled: boolean;
|
|
636
|
+
open: boolean;
|
|
637
|
+
reason: string;
|
|
638
|
+
color: string;
|
|
639
|
+
light: string;
|
|
640
|
+
};
|
|
292
641
|
safetyCar: {
|
|
293
642
|
deployed: boolean;
|
|
294
643
|
[key: string]: unknown;
|
|
295
644
|
};
|
|
296
|
-
rules:
|
|
645
|
+
rules: PaddockRaceRules;
|
|
297
646
|
events: RaceEvent[];
|
|
647
|
+
penalties: PaddockPenaltyEntry[];
|
|
298
648
|
cars: CarSnapshot[];
|
|
299
649
|
}
|
|
300
650
|
|
|
@@ -308,7 +658,7 @@ export interface F1SimulatorCallbacks {
|
|
|
308
658
|
onLoadingChange?: (state: { loading: boolean; phase: string }) => void;
|
|
309
659
|
onReady?: (payload: { snapshot: RaceSnapshot }) => void;
|
|
310
660
|
onError?: (error: unknown, context: LifecycleErrorContext) => void;
|
|
311
|
-
onDriverSelect?: (driver:
|
|
661
|
+
onDriverSelect?: (driver: NormalizedSimulatorDriver, snapshot: RaceSnapshot) => void;
|
|
312
662
|
onRaceEvent?: (event: RaceEvent, snapshot: RaceSnapshot) => void;
|
|
313
663
|
onLapChange?: (payload: {
|
|
314
664
|
previousLeaderLap: number | null;
|
|
@@ -323,6 +673,34 @@ export interface F1SimulatorCallbacks {
|
|
|
323
673
|
}) => void;
|
|
324
674
|
}
|
|
325
675
|
|
|
676
|
+
export interface F1SimulatorExpertOptions {
|
|
677
|
+
enabled: boolean;
|
|
678
|
+
controlledDrivers: string[];
|
|
679
|
+
frameSkip?: number;
|
|
680
|
+
visualizeSensors?: boolean | {
|
|
681
|
+
rays?: boolean;
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export interface F1SimulatorExpertAction {
|
|
686
|
+
steering: number;
|
|
687
|
+
throttle: number;
|
|
688
|
+
brake: number;
|
|
689
|
+
pitIntent?: PaddockPitIntent;
|
|
690
|
+
pitCompound?: TireCompound | string;
|
|
691
|
+
pitTargetCompound?: TireCompound | string;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export interface F1SimulatorExpertApi {
|
|
695
|
+
reset(options?: unknown): unknown;
|
|
696
|
+
step(actions: Record<string, F1SimulatorExpertAction>): unknown;
|
|
697
|
+
getObservation(): unknown;
|
|
698
|
+
getState(): unknown;
|
|
699
|
+
getActionSpec(): PaddockActionSpec;
|
|
700
|
+
getObservationSpec(): PaddockObservationSpec;
|
|
701
|
+
destroy(): void;
|
|
702
|
+
}
|
|
703
|
+
|
|
326
704
|
export interface F1SimulatorOptions extends F1SimulatorCallbacks {
|
|
327
705
|
preset?: PaddockPresetName;
|
|
328
706
|
drivers: SimulatorDriver[];
|
|
@@ -330,6 +708,7 @@ export interface F1SimulatorOptions extends F1SimulatorCallbacks {
|
|
|
330
708
|
seed?: number;
|
|
331
709
|
trackSeed?: number;
|
|
332
710
|
totalLaps?: number;
|
|
711
|
+
rules?: PaddockRaceRules;
|
|
333
712
|
initialCameraMode?: CameraMode;
|
|
334
713
|
theme?: F1SimulatorTheme;
|
|
335
714
|
title?: string;
|
|
@@ -339,8 +718,11 @@ export interface F1SimulatorOptions extends F1SimulatorCallbacks {
|
|
|
339
718
|
showBackLink?: boolean;
|
|
340
719
|
ui?: F1SimulatorUiOptions;
|
|
341
720
|
assets?: F1SimulatorAssets;
|
|
721
|
+
expert?: F1SimulatorExpertOptions;
|
|
342
722
|
}
|
|
343
723
|
|
|
724
|
+
export type F1SimulatorRestartOptions = Partial<Omit<F1SimulatorOptions, 'assets' | 'expert'>>;
|
|
725
|
+
|
|
344
726
|
export interface MountRaceCanvasOptions {
|
|
345
727
|
includeRaceDataPanel?: boolean;
|
|
346
728
|
includeTimingTower?: boolean;
|
|
@@ -355,20 +737,30 @@ export interface MountTelemetryPanelOptions {
|
|
|
355
737
|
export interface MountRaceTelemetryDrawerOptions {
|
|
356
738
|
timingTowerVerticalFit?: TimingTowerVerticalFit;
|
|
357
739
|
drawerInitiallyOpen?: boolean;
|
|
740
|
+
raceDataTelemetryDetail?: boolean;
|
|
358
741
|
}
|
|
359
742
|
|
|
360
743
|
export interface F1MountedSimulator {
|
|
744
|
+
readonly expert: F1SimulatorExpertApi | null;
|
|
361
745
|
destroy(): void;
|
|
362
|
-
restart(nextOptions?:
|
|
746
|
+
restart(nextOptions?: F1SimulatorRestartOptions): void;
|
|
363
747
|
selectDriver(driverId: string): void;
|
|
364
748
|
setSafetyCarDeployed(deployed: boolean): void;
|
|
749
|
+
setRedFlagDeployed(deployed: boolean): void;
|
|
750
|
+
setPitLaneOpen(open: boolean): void;
|
|
365
751
|
callSafetyCar(): void;
|
|
366
752
|
clearSafetyCar(): void;
|
|
367
753
|
toggleSafetyCar(): void;
|
|
754
|
+
setPitIntent(driverId: string, intent: PaddockPitIntentRequest, targetCompound?: TireCompound | string): boolean;
|
|
755
|
+
getPitIntent(driverId: string): PaddockPitIntent;
|
|
756
|
+
getPitTargetCompound(driverId: string): TireCompound | string | null;
|
|
757
|
+
servePenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
758
|
+
cancelPenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
368
759
|
getSnapshot(): RaceSnapshot | null;
|
|
369
760
|
}
|
|
370
761
|
|
|
371
762
|
export interface PaddockSimulatorController {
|
|
763
|
+
readonly expert: F1SimulatorExpertApi | null;
|
|
372
764
|
mountRaceControls<T extends Element>(root: T): T;
|
|
373
765
|
mountCameraControls<T extends Element>(root: T): T;
|
|
374
766
|
mountSafetyCarControl<T extends Element>(root: T): T;
|
|
@@ -387,12 +779,19 @@ export interface PaddockSimulatorController {
|
|
|
387
779
|
querySelectorAll(selector: string): Element[];
|
|
388
780
|
start(): Promise<PaddockSimulatorController>;
|
|
389
781
|
destroy(): void;
|
|
390
|
-
restart(nextOptions?:
|
|
782
|
+
restart(nextOptions?: F1SimulatorRestartOptions): void;
|
|
391
783
|
selectDriver(driverId: string): void;
|
|
392
784
|
setSafetyCarDeployed(deployed: boolean): void;
|
|
785
|
+
setRedFlagDeployed(deployed: boolean): void;
|
|
786
|
+
setPitLaneOpen(open: boolean): void;
|
|
393
787
|
callSafetyCar(): void;
|
|
394
788
|
clearSafetyCar(): void;
|
|
395
789
|
toggleSafetyCar(): void;
|
|
790
|
+
setPitIntent(driverId: string, intent: PaddockPitIntentRequest, targetCompound?: TireCompound | string): boolean;
|
|
791
|
+
getPitIntent(driverId: string): PaddockPitIntent;
|
|
792
|
+
getPitTargetCompound(driverId: string): TireCompound | string | null;
|
|
793
|
+
servePenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
794
|
+
cancelPenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
396
795
|
getSnapshot(): RaceSnapshot | null;
|
|
397
796
|
}
|
|
398
797
|
|
|
@@ -416,7 +815,7 @@ export function buildChampionshipDriverGrid(
|
|
|
416
815
|
export function formatDriverNumber(driverNumber: number | string | null | undefined): string;
|
|
417
816
|
export function normalizeSimulatorDrivers(
|
|
418
817
|
drivers: SimulatorDriver[],
|
|
419
|
-
options?: { entries?: ChampionshipEntryBlueprint[] },
|
|
818
|
+
options?: { entries?: ChampionshipEntryBlueprint[]; caller?: string },
|
|
420
819
|
): NormalizedSimulatorDriver[];
|
|
421
820
|
|
|
422
821
|
export function metersToSimUnits(meters: number): number;
|
package/src/index.js
CHANGED
|
@@ -91,13 +91,17 @@ export async function mountF1Simulator(root, options = {}) {
|
|
|
91
91
|
await app.init();
|
|
92
92
|
|
|
93
93
|
return {
|
|
94
|
+
get expert() {
|
|
95
|
+
return app.expert ?? null;
|
|
96
|
+
},
|
|
94
97
|
destroy() {
|
|
95
98
|
app.destroy();
|
|
96
99
|
root.innerHTML = '';
|
|
97
100
|
},
|
|
98
101
|
restart(nextOptions = {}) {
|
|
99
|
-
|
|
100
|
-
app.restart(
|
|
102
|
+
const nextResolvedOptions = resolveF1SimulatorOptions(mergeRestartOptions(resolvedOptions, nextOptions));
|
|
103
|
+
app.restart(nextResolvedOptions);
|
|
104
|
+
resolvedOptions = nextResolvedOptions;
|
|
101
105
|
},
|
|
102
106
|
selectDriver(driverId) {
|
|
103
107
|
app.selectCar(driverId, { focus: true });
|
|
@@ -105,6 +109,12 @@ export async function mountF1Simulator(root, options = {}) {
|
|
|
105
109
|
setSafetyCarDeployed(deployed) {
|
|
106
110
|
app.setSafetyCarDeployed(deployed);
|
|
107
111
|
},
|
|
112
|
+
setRedFlagDeployed(deployed) {
|
|
113
|
+
app.setRedFlagDeployed(deployed);
|
|
114
|
+
},
|
|
115
|
+
setPitLaneOpen(open) {
|
|
116
|
+
app.setPitLaneOpen(open);
|
|
117
|
+
},
|
|
108
118
|
callSafetyCar() {
|
|
109
119
|
app.setSafetyCarDeployed(true);
|
|
110
120
|
},
|
|
@@ -115,6 +125,21 @@ export async function mountF1Simulator(root, options = {}) {
|
|
|
115
125
|
const active = app.getSnapshot()?.raceControl.mode === 'safety-car';
|
|
116
126
|
app.setSafetyCarDeployed(!active);
|
|
117
127
|
},
|
|
128
|
+
setPitIntent(driverId, intent, targetCompound) {
|
|
129
|
+
return app.setPitIntent(driverId, intent, targetCompound);
|
|
130
|
+
},
|
|
131
|
+
getPitIntent(driverId) {
|
|
132
|
+
return app.getPitIntent(driverId);
|
|
133
|
+
},
|
|
134
|
+
getPitTargetCompound(driverId) {
|
|
135
|
+
return app.getPitTargetCompound(driverId);
|
|
136
|
+
},
|
|
137
|
+
servePenalty(penaltyId) {
|
|
138
|
+
return app.servePenalty(penaltyId);
|
|
139
|
+
},
|
|
140
|
+
cancelPenalty(penaltyId) {
|
|
141
|
+
return app.cancelPenalty(penaltyId);
|
|
142
|
+
},
|
|
118
143
|
getSnapshot() {
|
|
119
144
|
return app.getSnapshot();
|
|
120
145
|
},
|