@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/INSTALL_AND_UPDATE.md +165 -0
  3. package/README.md +217 -20
  4. package/docs/architecture.md +84 -15
  5. package/docs/concepts.md +20 -3
  6. package/docs/data_contract.md +412 -20
  7. package/docs/data_model.md +2 -1
  8. package/docs/index.md +1 -0
  9. package/docs/learnings.md +1 -1
  10. package/docs/rules.md +140 -19
  11. package/docs/system_specs.md +74 -20
  12. package/docs/training.md +206 -0
  13. package/examples/train-basic-policy.mjs +276 -0
  14. package/examples/trainingData.mjs +133 -0
  15. package/package.json +18 -8
  16. package/src/api/PaddockSimulatorController.js +39 -4
  17. package/src/app/BrowserExpertAdapter.js +32 -0
  18. package/src/app/F1SimulatorApp.js +1251 -146
  19. package/src/app/domBindings.js +10 -2
  20. package/src/config/defaultOptions.js +11 -3
  21. package/src/data/championship.js +13 -0
  22. package/src/data/normalizeDrivers.js +7 -2
  23. package/src/environment/actions.js +68 -0
  24. package/src/environment/episode.js +17 -0
  25. package/src/environment/evaluation.js +184 -0
  26. package/src/environment/events.js +17 -0
  27. package/src/environment/index.d.ts +521 -0
  28. package/src/environment/index.js +15 -0
  29. package/src/environment/observationOptions.js +12 -0
  30. package/src/environment/observations.js +284 -0
  31. package/src/environment/options.js +157 -0
  32. package/src/environment/recorder.js +28 -0
  33. package/src/environment/rewards.js +69 -0
  34. package/src/environment/runtime.js +175 -0
  35. package/src/environment/scenarios.js +220 -0
  36. package/src/environment/sensors.js +367 -0
  37. package/src/environment/specs.js +163 -0
  38. package/src/environment/workerProtocol.js +52 -0
  39. package/src/index.d.ts +405 -6
  40. package/src/index.js +27 -2
  41. package/src/rendering/proceduralTrackAsset.js +343 -24
  42. package/src/simulation/collisionGeometry.js +214 -0
  43. package/src/simulation/driverController.js +431 -65
  44. package/src/simulation/raceSimulation.js +2140 -171
  45. package/src/simulation/rules/collisionSteward.js +69 -0
  46. package/src/simulation/rules/penaltyLedger.js +153 -0
  47. package/src/simulation/rules/pitLaneSpeedingSteward.js +71 -0
  48. package/src/simulation/rules/tireRequirementSteward.js +29 -0
  49. package/src/simulation/rules/trackLimitsSteward.js +85 -0
  50. package/src/simulation/rulesConfig.js +352 -0
  51. package/src/simulation/trackModel.js +1154 -76
  52. package/src/simulation/units.js +6 -4
  53. package/src/simulation/vehicleGeometry.js +247 -0
  54. package/src/simulation/vehiclePhysics.js +82 -50
  55. package/src/simulation/wheelSurface.js +338 -0
  56. package/src/styles.css +369 -67
  57. package/src/ui/bannerTemplates.js +69 -0
  58. package/src/ui/componentTemplates.js +45 -81
  59. package/src/ui/raceControlStatusBanner.js +39 -0
  60. package/src/ui/shellTemplate.js +1 -1
  61. package/src/ui/templateUtils.js +23 -0
  62. package/AGENTS.md +0 -86
  63. 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 CameraMode = 'overview' | 'leader' | 'selected';
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: Record<string, unknown>;
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: CarSnapshot, snapshot: RaceSnapshot) => void;
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?: Partial<F1SimulatorOptions>): void;
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?: Partial<F1SimulatorOptions>): void;
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
- resolvedOptions = resolveF1SimulatorOptions(mergeRestartOptions(resolvedOptions, nextOptions));
100
- app.restart(resolvedOptions);
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
  },