@inventure71/paddockjs 1.0.0 → 2.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 (190) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/INSTALL_AND_UPDATE.md +4 -3
  3. package/README.md +194 -12
  4. package/docs/architecture.md +119 -44
  5. package/docs/concepts.md +4 -2
  6. package/docs/custom_model_controller.md +329 -0
  7. package/docs/data_contract.md +299 -17
  8. package/docs/index.md +1 -0
  9. package/docs/learnings.md +22 -0
  10. package/docs/rules.md +37 -10
  11. package/docs/system_specs.md +23 -10
  12. package/docs/training.md +272 -30
  13. package/examples/python/README.md +62 -0
  14. package/examples/python/base_policy_server.py +191 -0
  15. package/examples/python/requirements.txt +2 -0
  16. package/package.json +12 -4
  17. package/src/api/PaddockSimulatorController.js +4 -0
  18. package/src/app/BrowserExpertAdapter.js +367 -9
  19. package/src/app/F1SimulatorApp.js +305 -1603
  20. package/src/app/banners/bannerFormatters.js +55 -0
  21. package/src/app/banners/raceDataBannerController.js +141 -0
  22. package/src/app/banners/stewardMessageController.js +99 -0
  23. package/src/app/camera/cameraConstants.js +21 -0
  24. package/src/app/camera/cameraController.js +265 -0
  25. package/src/app/camera/cameraGeometry.js +176 -0
  26. package/src/app/domBindings.js +4 -0
  27. package/src/app/readouts/carOverviewRenderer.js +109 -0
  28. package/src/app/readouts/controlStateRenderer.js +37 -0
  29. package/src/app/readouts/raceStatusRenderer.js +133 -0
  30. package/src/app/readouts/readoutFormatters.js +58 -0
  31. package/src/app/readouts/telemetryRenderer.js +113 -0
  32. package/src/app/readouts/timingTowerRenderer.js +177 -0
  33. package/src/app/rendering/appAssets.js +38 -0
  34. package/src/app/rendering/carRenderer.js +286 -0
  35. package/src/app/rendering/displayUtils.js +22 -0
  36. package/src/app/rendering/drsTrailRenderer.js +90 -0
  37. package/src/app/rendering/expertSensorRenderer.js +126 -0
  38. package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
  39. package/src/app/rendering/replayGhostRenderer.js +124 -0
  40. package/src/app/rendering/trackRenderer.js +25 -0
  41. package/src/app/runtime/frameLoop.js +66 -0
  42. package/src/app/runtime/runtimeTiming.js +34 -0
  43. package/src/app/runtime/runtimeVisibility.js +45 -0
  44. package/src/config/defaultOptions.js +20 -0
  45. package/src/environment/actions.js +13 -5
  46. package/src/environment/controllerLoop.js +246 -0
  47. package/src/environment/episode.js +86 -2
  48. package/src/environment/evaluation.js +54 -32
  49. package/src/environment/index.d.ts +361 -9
  50. package/src/environment/index.js +1 -0
  51. package/src/environment/metrics.js +159 -0
  52. package/src/environment/observationOptions.js +22 -1
  53. package/src/environment/observations.js +325 -40
  54. package/src/environment/options.js +37 -5
  55. package/src/environment/runtime.js +222 -21
  56. package/src/environment/scenarios.js +37 -8
  57. package/src/environment/sensors/bodySenses.js +23 -0
  58. package/src/environment/sensors/boundarySenses.js +17 -0
  59. package/src/environment/sensors/carRays.js +75 -0
  60. package/src/environment/sensors/contactSenses.js +32 -0
  61. package/src/environment/sensors/index.js +157 -0
  62. package/src/environment/sensors/indexedRayBands.js +103 -0
  63. package/src/environment/sensors/nearbyCars.js +52 -0
  64. package/src/environment/sensors/opponentRadar.js +65 -0
  65. package/src/environment/sensors/rayConfig.js +110 -0
  66. package/src/environment/sensors/rayDefaults.js +6 -0
  67. package/src/environment/sensors/rayGeometry.js +60 -0
  68. package/src/environment/sensors/rayGuards.js +19 -0
  69. package/src/environment/sensors/sensorTargets.js +60 -0
  70. package/src/environment/sensors/surfaceRays.js +296 -0
  71. package/src/environment/sensors/trackRays.js +261 -0
  72. package/src/environment/sensors.js +1 -367
  73. package/src/environment/specs.js +100 -5
  74. package/src/environment/workerProtocol.js +13 -1
  75. package/src/index.d.ts +169 -6
  76. package/src/index.js +5 -0
  77. package/src/rendering/proceduralTrackAsset.js +20 -823
  78. package/src/rendering/renderSnapshot.js +38 -12
  79. package/src/rendering/track/finishLineRenderer.js +34 -0
  80. package/src/rendering/track/gridRenderer.js +47 -0
  81. package/src/rendering/track/offsetStrokeSafety.js +136 -0
  82. package/src/rendering/track/pitLaneRenderer.js +248 -0
  83. package/src/rendering/track/trackMaterialRenderer.js +326 -0
  84. package/src/rendering/track/trackRenderConstants.js +38 -0
  85. package/src/rendering/track/trackRenderGeometry.js +94 -0
  86. package/src/simulation/collisionGeometry.js +1 -1
  87. package/src/simulation/driver/driverControlConstants.js +28 -0
  88. package/src/simulation/driver/driverController.js +47 -0
  89. package/src/simulation/driver/driverInput.js +28 -0
  90. package/src/simulation/driver/driverMath.js +6 -0
  91. package/src/simulation/driver/driverPersonality.js +25 -0
  92. package/src/simulation/driver/edgeRecovery.js +48 -0
  93. package/src/simulation/driver/racingControls.js +399 -0
  94. package/src/simulation/driver/racingLinePlan.js +166 -0
  95. package/src/simulation/driver/recoveryDynamics.js +38 -0
  96. package/src/simulation/driver/rejoinControls.js +213 -0
  97. package/src/simulation/driver/safetyCarControls.js +35 -0
  98. package/src/simulation/driver/trafficScan.js +51 -0
  99. package/src/simulation/driverController.js +4 -619
  100. package/src/simulation/participants/participantInteractions.js +120 -0
  101. package/src/simulation/pit/pitFlow.js +234 -0
  102. package/src/simulation/pit/pitIntent.js +92 -0
  103. package/src/simulation/pit/pitOccupancy.js +47 -0
  104. package/src/simulation/pit/pitPenaltyService.js +71 -0
  105. package/src/simulation/pit/pitQueue.js +55 -0
  106. package/src/simulation/pit/pitRouteMovement.js +160 -0
  107. package/src/simulation/pit/pitRouting.js +319 -0
  108. package/src/simulation/pit/pitService.js +41 -0
  109. package/src/simulation/pit/pitServiceConstants.js +21 -0
  110. package/src/simulation/pit/pitServiceProfile.js +45 -0
  111. package/src/simulation/pit/pitSnapshots.js +61 -0
  112. package/src/simulation/pit/pitState.js +113 -0
  113. package/src/simulation/pit/pitTireService.js +92 -0
  114. package/src/simulation/race/classification.js +104 -0
  115. package/src/simulation/race/gridPenalties.js +37 -0
  116. package/src/simulation/race/raceControlState.js +38 -0
  117. package/src/simulation/race/raceDistance.js +32 -0
  118. package/src/simulation/race/raceFinish.js +65 -0
  119. package/src/simulation/race/raceLifecycle.js +71 -0
  120. package/src/simulation/race/raceOrder.js +113 -0
  121. package/src/simulation/race/raceProgress.js +124 -0
  122. package/src/simulation/race/raceSetup.js +93 -0
  123. package/src/simulation/race/raceStep.js +80 -0
  124. package/src/simulation/race/redFlag.js +15 -0
  125. package/src/simulation/race/retirements.js +44 -0
  126. package/src/simulation/race/safetyCar.js +86 -0
  127. package/src/simulation/race/stalledDnf.js +86 -0
  128. package/src/simulation/raceSimulation.js +230 -2817
  129. package/src/simulation/replay/replayGhosts.js +128 -0
  130. package/src/simulation/rules/moduleConfig.js +72 -0
  131. package/src/simulation/rules/penaltyConfig.js +99 -0
  132. package/src/simulation/rules/penaltyEvents.js +18 -0
  133. package/src/simulation/rules/penaltyStats.js +26 -0
  134. package/src/simulation/rules/ruleConfigMerge.js +41 -0
  135. package/src/simulation/rules/ruleDefaults.js +85 -0
  136. package/src/simulation/rules/rulesReview.js +141 -0
  137. package/src/simulation/rules/rulesetPresets.js +47 -0
  138. package/src/simulation/rules/trackLimitsSteward.js +1 -1
  139. package/src/simulation/rulesConfig.js +5 -323
  140. package/src/simulation/snapshots/raceSnapshots.js +192 -0
  141. package/src/simulation/timing/drsTiming.js +87 -0
  142. package/src/simulation/timing/gapEstimation.js +38 -0
  143. package/src/simulation/timing/lapTelemetry.js +182 -0
  144. package/src/simulation/timing/raceTiming.js +15 -0
  145. package/src/simulation/timing/sectorPerformance.js +60 -0
  146. package/src/simulation/timing/timingConstants.js +5 -0
  147. package/src/simulation/timing/timingHistory.js +46 -0
  148. package/src/simulation/timing/timingLines.js +79 -0
  149. package/src/simulation/track/centerlineSpline.js +59 -0
  150. package/src/simulation/track/drsZones.js +67 -0
  151. package/src/simulation/track/pitLaneAccess.js +103 -0
  152. package/src/simulation/track/pitLaneLayout.js +395 -0
  153. package/src/simulation/track/pitLaneState.js +160 -0
  154. package/src/simulation/track/proceduralCenterline.js +237 -0
  155. package/src/simulation/track/sampleGeometry.js +49 -0
  156. package/src/simulation/track/spatialQueries.js +154 -0
  157. package/src/simulation/track/startStraight.js +141 -0
  158. package/src/simulation/track/trackConstants.js +109 -0
  159. package/src/simulation/track/trackGenerationOptions.js +283 -0
  160. package/src/simulation/track/trackMath.js +241 -0
  161. package/src/simulation/track/trackModel.js +240 -0
  162. package/src/simulation/track/trackQueryIndex.js +945 -0
  163. package/src/simulation/track/trackSectors.js +19 -0
  164. package/src/simulation/track/trackStatePolicy.js +24 -0
  165. package/src/simulation/track/trackValidation.js +98 -0
  166. package/src/simulation/trackModel.js +1 -1706
  167. package/src/simulation/vehicle/contactResolution.js +116 -0
  168. package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
  169. package/src/simulation/vehicle/pitWheelSurface.js +69 -0
  170. package/src/simulation/vehicle/runoffResponse.js +88 -0
  171. package/src/simulation/vehicle/surfacePriority.js +28 -0
  172. package/src/simulation/vehicle/vehicleGeometry.js +247 -0
  173. package/src/simulation/vehicle/vehiclePhysics.js +363 -0
  174. package/src/simulation/vehicle/vehicleSnapshots.js +316 -0
  175. package/src/simulation/vehicle/vehicleState.js +186 -0
  176. package/src/simulation/vehicle/wheelSurface.js +150 -0
  177. package/src/simulation/vehicleGeometry.js +1 -247
  178. package/src/simulation/vehiclePhysics.js +1 -136
  179. package/src/simulation/warmup/runtimeWarmup.js +224 -0
  180. package/src/simulation/wheelSurface.js +1 -338
  181. package/src/styles.css +42 -0
  182. package/src/ui/cameraControlsTemplate.js +20 -0
  183. package/src/ui/carOverviewTemplate.js +33 -0
  184. package/src/ui/componentTemplates.js +14 -360
  185. package/src/ui/raceCanvasTemplate.js +69 -0
  186. package/src/ui/raceControlsTemplate.js +36 -0
  187. package/src/ui/shellTemplate.js +5 -4
  188. package/src/ui/telemetryDrawerTemplate.js +55 -0
  189. package/src/ui/telemetryTemplates.js +136 -0
  190. package/src/ui/timingTowerTemplate.js +34 -0
@@ -1,6 +1,113 @@
1
1
  export type TireCompound = 'S' | 'M' | 'H';
2
+ export type PaddockPhysicsMode = 'arcade' | 'simulator';
3
+ export type PaddockProceduralTrackProfile = 'race' | 'training-short' | 'training-medium' | 'training-technical';
4
+
5
+ export interface PaddockProceduralTrackOptions {
6
+ profile?: PaddockProceduralTrackProfile;
7
+ minLengthMeters?: number;
8
+ maxLengthMeters?: number;
9
+ startStraightMeters?: number;
10
+ includePitLane?: boolean;
11
+ length?: {
12
+ minMeters?: number;
13
+ maxMeters?: number;
14
+ };
15
+ startStraight?: {
16
+ gridMeters?: number;
17
+ exitMeters?: number;
18
+ blendMeters?: number;
19
+ lockExtraMeters?: number;
20
+ };
21
+ pitLane?: {
22
+ enabled?: boolean;
23
+ };
24
+ shape?: {
25
+ scale?: number;
26
+ cornerDensity?: number;
27
+ variation?: number;
28
+ };
29
+ validation?: {
30
+ minClearanceMultiplier?: number;
31
+ minShapeVariation?: number;
32
+ minNonAdjacentArcMeters?: number;
33
+ maxLocalTurnRadians?: number;
34
+ maxSampleHeadingDeltaRadians?: number;
35
+ };
36
+ attempts?: {
37
+ primary?: number;
38
+ fallback?: number;
39
+ };
40
+ }
41
+
42
+ export type PaddockStabilityState = 'stable' | 'understeer' | 'oversteer' | 'spin-risk' | 'destroyed';
2
43
  export type PaddockPitIntent = 0 | 1 | 2;
3
44
  export type PaddockScenarioPreset = 'cornering' | 'off-track-recovery' | 'overtaking-pack' | 'pit-entry';
45
+ export type PaddockParticipantInteractionProfile =
46
+ | 'normal'
47
+ | 'isolated-training'
48
+ | 'batch-training'
49
+ | 'phantom-race'
50
+ | 'time-trial-overlay';
51
+
52
+ export interface PaddockParticipantInteraction {
53
+ profile: PaddockParticipantInteractionProfile;
54
+ collidable: boolean;
55
+ detectableByRays: boolean;
56
+ detectableAsNearby: boolean;
57
+ blocksPitLane: boolean;
58
+ affectsRaceOrder: boolean;
59
+ }
60
+
61
+ export type PaddockParticipantInteractionOverride =
62
+ Partial<PaddockParticipantInteraction> & { profile?: PaddockParticipantInteractionProfile };
63
+
64
+ export interface PaddockParticipantInteractionsOptions {
65
+ defaultProfile?: PaddockParticipantInteractionProfile;
66
+ drivers?: Record<string, PaddockParticipantInteractionOverride>;
67
+ }
68
+
69
+ export interface PaddockReplayGhostTrajectorySample {
70
+ timeSeconds: number;
71
+ x: number;
72
+ y: number;
73
+ headingRadians: number;
74
+ speedKph?: number;
75
+ progressMeters?: number;
76
+ }
77
+
78
+ export interface PaddockReplayGhostOptions {
79
+ id: string;
80
+ label?: string;
81
+ color?: string;
82
+ opacity?: number;
83
+ visible?: boolean;
84
+ trajectory: PaddockReplayGhostTrajectorySample[];
85
+ sensors?: {
86
+ detectableByRays?: boolean;
87
+ detectableAsNearby?: boolean;
88
+ };
89
+ }
90
+
91
+ export interface PaddockReplayGhostSnapshot {
92
+ id: string;
93
+ label: string;
94
+ color: string;
95
+ opacity: number;
96
+ visible: boolean;
97
+ previousX: number;
98
+ previousY: number;
99
+ x: number;
100
+ y: number;
101
+ previousHeading: number;
102
+ heading: number;
103
+ speedKph: number;
104
+ progressMeters: number;
105
+ timeSeconds: number;
106
+ sensors: {
107
+ detectableByRays: boolean;
108
+ detectableAsNearby: boolean;
109
+ };
110
+ }
4
111
 
5
112
  export interface TeamData {
6
113
  id?: string;
@@ -92,6 +199,7 @@ export type PaddockPenaltyStatus = 'issued' | 'served' | 'applied' | 'cancelled'
92
199
 
93
200
  export interface RaceSnapshot {
94
201
  time: number;
202
+ physicsMode: PaddockPhysicsMode;
95
203
  totalLaps: number;
96
204
  raceControl: {
97
205
  mode: string;
@@ -100,6 +208,7 @@ export interface RaceSnapshot {
100
208
  };
101
209
  track: Record<string, unknown>;
102
210
  cars: Array<Record<string, unknown>>;
211
+ replayGhosts: PaddockReplayGhostSnapshot[];
103
212
  events: RaceEvent[];
104
213
  penalties: PaddockPenaltyEntry[];
105
214
  [key: string]: unknown;
@@ -143,6 +252,14 @@ export interface PaddockRaceRules {
143
252
  compounds?: TireCompound[];
144
253
  mandatoryDistinctDryCompounds?: number | null;
145
254
  };
255
+ tireDegradation?: {
256
+ enabled?: boolean;
257
+ };
258
+ stalledDnf?: {
259
+ enabled?: boolean;
260
+ maxStoppedSeconds?: number;
261
+ speedThresholdKph?: number;
262
+ };
146
263
  penalties?: {
147
264
  enabled?: boolean;
148
265
  stewardStrictness?: number;
@@ -192,22 +309,38 @@ export interface PaddockRaceRules {
192
309
  }
193
310
 
194
311
  export interface PaddockSensorRayResult {
312
+ id?: string;
195
313
  angleDegrees: number;
196
314
  angleRadians: number;
197
315
  lengthMeters: number;
316
+ roadEdge: {
317
+ hit: boolean;
318
+ distanceMeters: number;
319
+ kind: 'exit' | 'entry' | null;
320
+ };
198
321
  track: {
199
322
  hit: boolean;
200
323
  distanceMeters: number;
201
324
  kind: 'exit' | 'entry' | null;
202
325
  };
326
+ kerb: PaddockSurfaceRayHit;
327
+ illegalSurface: PaddockSurfaceRayHit;
203
328
  car: {
204
329
  hit: boolean;
205
330
  distanceMeters: number;
206
331
  driverId: string | null;
332
+ targetId?: string | null;
333
+ targetType?: 'car' | 'replayGhost' | null;
207
334
  relativeSpeedKph: number;
208
335
  };
209
336
  }
210
337
 
338
+ export interface PaddockSurfaceRayHit {
339
+ hit: boolean;
340
+ distanceMeters: number;
341
+ surface: string | null;
342
+ }
343
+
211
344
  export interface PaddockNearbyCarObservation {
212
345
  id: string;
213
346
  relativeForwardMeters: number;
@@ -216,7 +349,13 @@ export interface PaddockNearbyCarObservation {
216
349
  relativeSpeedKph: number;
217
350
  relativeHeadingRadians: number;
218
351
  ahead: boolean;
352
+ behind: boolean;
219
353
  sameLap: boolean;
354
+ closingRateMetersPerSecond: number;
355
+ timeToContactSeconds: number | null;
356
+ leftOverlap: boolean;
357
+ rightOverlap: boolean;
358
+ entityType?: 'car' | 'replayGhost';
220
359
  }
221
360
 
222
361
  export interface PaddockTrackLookaheadObservation {
@@ -226,11 +365,13 @@ export interface PaddockTrackLookaheadObservation {
226
365
  }
227
366
 
228
367
  export interface PaddockDriverObservationObject {
368
+ profile: string;
229
369
  self: {
230
370
  id: string;
231
371
  speedKph: number;
232
372
  speedMetersPerSecond: number;
233
373
  headingRadians: number;
374
+ yawRateRadiansPerSecond: number;
234
375
  steeringAngleRadians: number;
235
376
  throttle: number;
236
377
  brake: number;
@@ -252,7 +393,25 @@ export interface PaddockDriverObservationObject {
252
393
  pitStopServiceRemainingSeconds: number | null;
253
394
  pitStopPenaltyServiceRemainingSeconds: number | null;
254
395
  pitStopsCompleted: number;
396
+ lateralG: number;
397
+ longitudinalG: number;
398
+ gripUsage: number;
399
+ slipAngleRadians: number;
400
+ tractionLimited: boolean;
401
+ stabilityState: PaddockStabilityState;
402
+ destroyed: boolean;
403
+ destroyReason: string | null;
404
+ };
405
+ trackRelation: {
406
+ lateralOffsetMeters: number;
407
+ headingErrorRadians: number;
408
+ legalWidthMeters: number;
409
+ leftBoundaryMeters: number;
410
+ rightBoundaryMeters: number;
411
+ onLegalSurface: boolean;
412
+ surface: string;
255
413
  };
414
+ contactPatches: PaddockContactPatchObservation[];
256
415
  race: {
257
416
  position: number;
258
417
  totalCars: number;
@@ -272,6 +431,17 @@ export interface PaddockDriverObservationObject {
272
431
  events: RaceEvent[];
273
432
  }
274
433
 
434
+ export interface PaddockContactPatchObservation {
435
+ id: 'front-left' | 'front-right' | 'rear-left' | 'rear-right' | string;
436
+ present: boolean;
437
+ signedOffsetMeters: number;
438
+ crossTrackErrorMeters: number;
439
+ surface: string;
440
+ surfaceCode: number;
441
+ onLegalSurface: boolean;
442
+ inPitLane: boolean;
443
+ }
444
+
275
445
  export interface PaddockObservationSchemaEntry {
276
446
  name: string;
277
447
  unit?: string;
@@ -279,21 +449,34 @@ export interface PaddockObservationSchemaEntry {
279
449
  }
280
450
 
281
451
  export interface PaddockDriverObservation {
282
- object: PaddockDriverObservationObject;
283
- vector: number[];
284
- schema: PaddockObservationSchemaEntry[];
452
+ object?: PaddockDriverObservationObject;
453
+ vector?: number[] | Float32Array;
454
+ schema?: PaddockObservationSchemaEntry[];
285
455
  events: RaceEvent[];
286
456
  }
287
457
 
458
+ export type PaddockWarmupPolicy = 'config-change' | 'always' | 'never';
459
+
460
+ export interface PaddockWarmupOptions {
461
+ enabled?: boolean;
462
+ policy?: PaddockWarmupPolicy;
463
+ steps?: number;
464
+ }
465
+
288
466
  export interface PaddockEnvironmentOptions {
289
467
  drivers: SimulatorDriver[];
290
468
  entries?: ChampionshipEntryBlueprint[];
291
469
  controlledDrivers: string[];
292
470
  seed?: number;
293
471
  trackSeed?: number;
472
+ trackGeneration?: PaddockProceduralTrackOptions;
473
+ trackQueryIndex?: boolean;
294
474
  totalLaps?: number;
295
475
  frameSkip?: number;
476
+ physicsMode?: PaddockPhysicsMode;
296
477
  rules?: PaddockRaceRules;
478
+ participantInteractions?: PaddockParticipantInteractionsOptions;
479
+ replayGhosts?: PaddockReplayGhostOptions[];
297
480
  actionPolicy?: 'strict' | 'report';
298
481
  scenario?: {
299
482
  participants?: 'all' | 'controlled-only' | string[];
@@ -307,6 +490,15 @@ export interface PaddockEnvironmentOptions {
307
490
  enabled?: boolean;
308
491
  anglesDegrees?: number[];
309
492
  lengthMeters?: number;
493
+ defaultLengthMeters?: number;
494
+ layout?: 'compact' | 'driver-front-heavy' | 'lidar-lite' | string;
495
+ rays?: Array<number | {
496
+ id?: string;
497
+ angleDegrees: number;
498
+ lengthMeters?: number;
499
+ }>;
500
+ channels?: Array<'roadEdge' | 'kerb' | 'illegalSurface' | 'car'>;
501
+ precision?: 'driver' | 'debug';
310
502
  detectTrack?: boolean;
311
503
  detectCars?: boolean;
312
504
  };
@@ -318,13 +510,27 @@ export interface PaddockEnvironmentOptions {
318
510
  };
319
511
  sensorsByDriver?: Record<string, PaddockEnvironmentOptions['sensors']>;
320
512
  observation?: {
513
+ profile?: 'default' | 'physical-driver' | 'debug-map' | string;
514
+ output?: 'full' | 'vector' | 'object';
515
+ includeSchema?: boolean;
516
+ vectorType?: 'array' | 'float32';
321
517
  lookaheadMeters?: number[];
322
518
  };
519
+ result?: {
520
+ stateOutput?: 'full' | 'minimal' | 'none';
521
+ resetDriversObservationScope?: 'all' | 'reset';
522
+ };
523
+ warmup?: PaddockWarmupOptions | boolean;
323
524
  episode?: {
324
525
  maxSteps?: number;
325
526
  endOnRaceFinish?: boolean;
326
527
  };
327
528
  reward?: (payload: PaddockRewardContext) => number;
529
+ externalRenderer?: PaddockExternalRendererHook | ((frame: PaddockExternalRenderFrame) => void);
530
+ }
531
+
532
+ export interface PaddockExternalRendererHook {
533
+ onFrame(frame: PaddockExternalRenderFrame): void;
328
534
  }
329
535
 
330
536
  export interface PaddockScenarioPlacement {
@@ -351,6 +557,8 @@ export interface PaddockRewardContext {
351
557
  action: PaddockAction | undefined;
352
558
  events: RaceEvent[];
353
559
  state: { snapshot: RaceSnapshot };
560
+ metrics: PaddockEnvironmentDriverMetrics;
561
+ episode: PaddockDriverRuntimeState;
354
562
  }
355
563
 
356
564
  export interface PaddockProgressRewardWeights {
@@ -382,7 +590,7 @@ export interface PaddockActionSpec {
382
590
  }
383
591
 
384
592
  export interface PaddockObservationSpec {
385
- version: 2;
593
+ version: 2 | 4;
386
594
  controlledDrivers: string[];
387
595
  object: Record<string, unknown>;
388
596
  vector: {
@@ -446,11 +654,114 @@ export type PaddockPolicyLike =
446
654
  | ((observation: PaddockDriverObservation, context: Record<string, unknown>) => PaddockAction)
447
655
  | { predict(observation: PaddockDriverObservation, context: Record<string, unknown>): PaddockAction };
448
656
 
657
+ export interface PaddockDriverControllerContext {
658
+ runtime: PaddockControllerRuntime;
659
+ mode: string;
660
+ actionRepeat: number;
661
+ controlledDrivers: string[];
662
+ actionSpec: PaddockActionSpec;
663
+ observationSpec: PaddockObservationSpec;
664
+ result: PaddockEnvironmentResult | unknown;
665
+ previousResult: PaddockEnvironmentResult | unknown | null;
666
+ observation: Record<string, PaddockDriverObservation>;
667
+ orderedObservations: Array<{
668
+ driverId: string;
669
+ index: number;
670
+ observation: PaddockDriverObservation | null;
671
+ vector: number[] | Float32Array | null;
672
+ }>;
673
+ metrics: Record<string, PaddockEnvironmentDriverMetrics>;
674
+ events: RaceEvent[];
675
+ info: PaddockEnvironmentResult['info'] | unknown | null;
676
+ previousActions: PaddockActionMap;
677
+ orderedPreviousActions: Array<PaddockAction | null>;
678
+ actions: PaddockActionMap | null;
679
+ policyStep: number;
680
+ runtimeStep: number;
681
+ heldFramesRemaining: number;
682
+ actionIndex: number;
683
+ resetDriverIds: string[];
684
+ }
685
+
686
+ export interface PaddockExternalRenderFrame {
687
+ snapshot: RaceSnapshot;
688
+ observation: Record<string, unknown>;
689
+ meta?: Record<string, unknown> | null;
690
+ }
691
+
692
+ export interface PaddockExternalRendererSource {
693
+ subscribe(onFrame: (frame: PaddockExternalRenderFrame) => void): () => void;
694
+ }
695
+
696
+ export interface PaddockExternalRendererState {
697
+ attached: boolean;
698
+ lastMeta: Record<string, unknown> | null;
699
+ lastFrameAt: number | null;
700
+ lastError: string | null;
701
+ }
702
+
703
+ export interface PaddockDriverController {
704
+ init?(context: PaddockDriverControllerContext): void | Promise<void>;
705
+ reset?(context: PaddockDriverControllerContext): void | Promise<void>;
706
+ decideBatch(context: PaddockDriverControllerContext): PaddockActionMap | Promise<PaddockActionMap>;
707
+ onStep?(context: PaddockDriverControllerContext): void | Promise<void>;
708
+ }
709
+
710
+ export interface PaddockControllerRuntime {
711
+ reset(options?: unknown): PaddockEnvironmentResult | unknown;
712
+ step(actions: PaddockActionMap): PaddockEnvironmentResult | unknown;
713
+ resetDrivers?(
714
+ placements: Record<string, PaddockScenarioPlacement>,
715
+ resultOptions?: PaddockEnvironmentResultOptions
716
+ ): PaddockEnvironmentResult | unknown;
717
+ getObservation(): Record<string, PaddockDriverObservation>;
718
+ getState?(options?: { output?: 'full' | 'minimal' | 'none' }): PaddockEnvironmentResult['state'] | unknown;
719
+ getActionSpec(): PaddockActionSpec;
720
+ getObservationSpec(): PaddockObservationSpec;
721
+ destroy?(): void;
722
+ }
723
+
724
+ export interface PaddockDriverControllerLoopOptions {
725
+ runtime: PaddockControllerRuntime;
726
+ controller: PaddockDriverController;
727
+ actionRepeat?: number;
728
+ mode?: string;
729
+ scheduler?: null | ((callback: () => void | Promise<void>) => unknown);
730
+ }
731
+
732
+ export interface PaddockDriverControllerLoop {
733
+ readonly result: PaddockEnvironmentResult | unknown | null;
734
+ readonly actionSpec: PaddockActionSpec | null;
735
+ readonly observationSpec: PaddockObservationSpec | null;
736
+ readonly stats: {
737
+ policyStep: number;
738
+ runtimeStep: number;
739
+ heldFramesRemaining: number;
740
+ actions: PaddockActionMap | null;
741
+ actionRepeat: number;
742
+ lastDecisionMs: number;
743
+ running: boolean;
744
+ lastError: unknown | null;
745
+ };
746
+ reset(options?: unknown): Promise<PaddockEnvironmentResult | unknown>;
747
+ resetDrivers(
748
+ placements: Record<string, PaddockScenarioPlacement>,
749
+ resultOptions?: PaddockEnvironmentResultOptions
750
+ ): Promise<PaddockEnvironmentResult | unknown>;
751
+ step(): Promise<PaddockEnvironmentResult | unknown>;
752
+ stepFrame(): Promise<PaddockEnvironmentResult | unknown>;
753
+ start(): void;
754
+ stop(): void;
755
+ }
756
+
449
757
  export interface PaddockEnvironmentWorkerMessage {
450
758
  id?: string | number | null;
451
- type: 'reset' | 'step' | 'getActionSpec' | 'getObservationSpec' | 'getObservation' | 'getState' | 'destroy' | string;
759
+ type: 'reset' | 'resetDrivers' | 'step' | 'getActionSpec' | 'getObservationSpec' | 'getObservation' | 'getState' | 'destroy' | string;
452
760
  options?: Partial<PaddockEnvironmentOptions>;
453
761
  actions?: PaddockActionMap;
762
+ placements?: Record<string, PaddockScenarioPlacement>;
763
+ resultOptions?: PaddockEnvironmentResultOptions;
764
+ stateOptions?: { output?: 'full' | 'minimal' | 'none' };
454
765
  }
455
766
 
456
767
  export interface PaddockEnvironmentWorkerResponse {
@@ -468,11 +779,12 @@ export interface PaddockEnvironmentWorkerProtocol {
468
779
  export interface PaddockEnvironmentResult {
469
780
  observation: Record<string, PaddockDriverObservation>;
470
781
  reward: null | Record<string, number>;
782
+ metrics: Record<string, PaddockEnvironmentDriverMetrics>;
471
783
  terminated: boolean;
472
784
  truncated: boolean;
473
785
  done: boolean;
474
786
  events: RaceEvent[];
475
- state: { snapshot: RaceSnapshot };
787
+ state: { snapshot: RaceSnapshot } | null;
476
788
  info: {
477
789
  step: number;
478
790
  elapsedSeconds: number;
@@ -481,20 +793,60 @@ export interface PaddockEnvironmentResult {
481
793
  controlledDrivers: string[];
482
794
  actionErrors: string[];
483
795
  endReason: string | null;
796
+ drivers: Record<string, PaddockDriverRuntimeState>;
484
797
  };
485
798
  }
486
799
 
800
+ export type PaddockEnvironmentSnapshotResult = PaddockEnvironmentResult & {
801
+ state: { snapshot: RaceSnapshot };
802
+ };
803
+
487
804
  export interface PaddockEnvironment {
488
805
  reset(options?: Partial<PaddockEnvironmentOptions>): PaddockEnvironmentResult;
806
+ resetDrivers(
807
+ placements: Record<string, PaddockScenarioPlacement>,
808
+ resultOptions?: PaddockEnvironmentResultOptions
809
+ ): PaddockEnvironmentResult;
489
810
  step(actions: PaddockActionMap): PaddockEnvironmentResult;
490
811
  getObservation(): PaddockEnvironmentResult['observation'];
491
- getState(): PaddockEnvironmentResult['state'];
812
+ getState(options?: { output?: 'full' | 'minimal' | 'none' }): PaddockEnvironmentResult['state'];
492
813
  getActionSpec(): PaddockActionSpec;
493
814
  getObservationSpec(): PaddockObservationSpec;
494
815
  destroy(): void;
495
816
  }
496
817
 
818
+ export interface PaddockEnvironmentResultOptions {
819
+ stateOutput?: 'full' | 'minimal' | 'none';
820
+ observationScope?: 'all' | 'reset';
821
+ resetDriversObservationScope?: 'all' | 'reset';
822
+ }
823
+
824
+ export interface PaddockDriverRuntimeState {
825
+ terminated: boolean;
826
+ truncated: boolean;
827
+ endReason: string | null;
828
+ episodeStep: number;
829
+ episodeId: number;
830
+ }
831
+
832
+ export interface PaddockEnvironmentDriverMetrics {
833
+ progressDeltaMeters: number;
834
+ legalProgressDeltaMeters: number;
835
+ offTrack: boolean;
836
+ kerb: boolean;
837
+ fullyOutsideWhiteLine: boolean;
838
+ severeCut: boolean;
839
+ destroyed: boolean;
840
+ destroyReason: string | null;
841
+ under30kph: boolean;
842
+ spinOrBackwards: boolean;
843
+ completedLap: boolean;
844
+ lapTimeSeconds: number | null;
845
+ contactCount: number;
846
+ }
847
+
497
848
  export function createPaddockEnvironment(options: PaddockEnvironmentOptions): PaddockEnvironment;
849
+ export function createPaddockDriverControllerLoop(options: PaddockDriverControllerLoopOptions): PaddockDriverControllerLoop;
498
850
  export function createProgressReward(options?: PaddockProgressRewardOptions): (context: PaddockRewardContext) => number;
499
851
  export function createRolloutRecorder(): PaddockRolloutRecorder;
500
852
  export function createRolloutTransition(
@@ -504,8 +856,8 @@ export function createRolloutTransition(
504
856
  ): PaddockRolloutTransition;
505
857
  export const ENVIRONMENT_SCENARIO_PRESETS: readonly PaddockScenarioPreset[];
506
858
  export const DEFAULT_EVALUATION_CASES: readonly PaddockEvaluationCase[];
507
- export function createEvaluationTracker(initialResult: PaddockEnvironmentResult): {
508
- update(result: PaddockEnvironmentResult): void;
859
+ export function createEvaluationTracker(initialResult: PaddockEnvironmentSnapshotResult): {
860
+ update(result: PaddockEnvironmentSnapshotResult): void;
509
861
  finish(): Record<string, PaddockEvaluationDriverMetrics>;
510
862
  };
511
863
  export function runEnvironmentEvaluation(options: {
@@ -1,4 +1,5 @@
1
1
  export { createPaddockEnvironment } from './runtime.js';
2
+ export { createPaddockDriverControllerLoop } from './controllerLoop.js';
2
3
  export { createProgressReward } from './rewards.js';
3
4
  export { createRolloutRecorder, createRolloutTransition } from './recorder.js';
4
5
  export {