@inventure71/paddockjs 4.1.0 → 4.3.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 (257) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/INSTALL_AND_UPDATE.md +31 -5
  3. package/README.md +34 -3
  4. package/docs/advanced_ai_pace.md +131 -0
  5. package/docs/advanced_physics_rebuild.md +166 -0
  6. package/docs/architecture.md +111 -30
  7. package/docs/cleanup_coverage.md +284 -0
  8. package/docs/component_inventory.md +32 -16
  9. package/docs/composable-layouts.md +2 -0
  10. package/docs/concepts.md +12 -1
  11. package/docs/custom_model_controller.md +1 -1
  12. package/docs/data_contract.md +33 -10
  13. package/docs/data_model.md +4 -2
  14. package/docs/efficiency_cleanup_2026-10-13.md +80 -0
  15. package/docs/getting-started.md +3 -0
  16. package/docs/grid_walk.md +80 -0
  17. package/docs/index.md +5 -2
  18. package/docs/keyboard_driving_fix.md +118 -0
  19. package/docs/limit_driving_2026-10-14.md +88 -0
  20. package/docs/loading.md +141 -0
  21. package/docs/maintainability_audit_2026-09-08.md +146 -0
  22. package/docs/racing_ai_improvements.md +145 -0
  23. package/docs/rules.md +43 -13
  24. package/docs/sense_contract.md +4 -1
  25. package/docs/system_specs.md +40 -12
  26. package/docs/testing-reliability-guide.md +67 -0
  27. package/docs/v11_improvements.md +108 -0
  28. package/package.json +23 -15
  29. package/src/api/MountedSimulatorComponents.js +101 -0
  30. package/src/api/PaddockSimulatorController.js +56 -81
  31. package/src/app/BrowserExpertAdapter.js +0 -6
  32. package/src/app/BrowserExpertExternalRenderer.js +72 -50
  33. package/src/app/ExternalTrackSnapshot.js +50 -0
  34. package/src/app/F1SimulatorApp.js +242 -297
  35. package/src/app/HostLifecycleEmitter.js +93 -0
  36. package/src/app/banners/raceDataBannerController.js +9 -4
  37. package/src/app/camera/cameraConstants.js +1 -1
  38. package/src/app/camera/cameraController.js +10 -13
  39. package/src/app/camera/cameraGeometry.js +1 -1
  40. package/src/app/controls/SimulatorViewControls.js +45 -0
  41. package/src/app/controls/workbenchBindings.js +64 -0
  42. package/src/app/controls/workbenchDisclosure.js +41 -0
  43. package/src/app/domBindings.js +21 -13
  44. package/src/app/gridWalk/GridWalkController.js +180 -0
  45. package/src/app/gridWalk/gridWalkCamera.js +88 -0
  46. package/src/app/gridWalk/gridWalkControls.js +24 -0
  47. package/src/app/gridWalk/gridWalkCrew.js +246 -0
  48. package/src/app/gridWalk/gridWalkLabels.js +104 -0
  49. package/src/app/gridWalk/gridWalkLayout.js +46 -0
  50. package/src/app/gridWalk/gridWalkNavigation.js +99 -0
  51. package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
  52. package/src/app/gridWalk/gridWalkSession.js +50 -0
  53. package/src/app/layoutSupport.js +2 -0
  54. package/src/app/raceOverlayClearanceSupport.js +21 -7
  55. package/src/app/readouts/carOverviewRenderer.js +9 -4
  56. package/src/app/readouts/readoutFormatters.js +1 -8
  57. package/src/app/readouts/telemetryRenderer.js +1 -3
  58. package/src/app/readouts/timingTowerRenderer.js +48 -50
  59. package/src/app/rendering/appAssets.js +8 -10
  60. package/src/app/rendering/carRenderer.js +27 -79
  61. package/src/app/rendering/displayUtils.js +0 -6
  62. package/src/app/rendering/expertSensorRenderer.js +1 -1
  63. package/src/app/rendering/replayGhostRenderer.js +4 -1
  64. package/src/app/rendering/trackRenderer.js +2 -2
  65. package/src/app/runtime/frameLoop.js +18 -17
  66. package/src/app/runtime/pixiStartupLifecycle.js +94 -0
  67. package/src/app/runtime/runtimeTiming.js +4 -1
  68. package/src/app/runtime/runtimeVisibility.js +7 -2
  69. package/src/config/cssValues.js +1 -1
  70. package/src/config/defaultOptions.js +12 -173
  71. package/src/config/restartOptions.js +1 -0
  72. package/src/config/runtimeSeed.js +10 -0
  73. package/src/config/simulatorDefaults.js +106 -0
  74. package/src/config/telemetryModules.js +20 -0
  75. package/src/config/theme/cssTokens.js +85 -0
  76. package/src/config/theme/themeCss.js +72 -0
  77. package/src/config/theme/themeInputs.js +155 -0
  78. package/src/config/theme/themeResolver.js +172 -0
  79. package/src/config/theme/themeSelectors.js +69 -0
  80. package/src/config/theme/themeTokens.js +257 -0
  81. package/src/config/theme/themeUtils.js +15 -0
  82. package/src/config/themeOptions.js +14 -830
  83. package/src/config/timingGapMode.js +0 -2
  84. package/src/config/uiOptions.js +53 -0
  85. package/src/data/championship.js +16 -158
  86. package/src/data/championshipDefaults.js +87 -0
  87. package/src/data/driverData.js +10 -20
  88. package/src/data/entryIdentity.js +67 -0
  89. package/src/data/entryPresentation.js +23 -0
  90. package/src/data/entryTypes.d.ts +160 -0
  91. package/src/data/index.d.ts +12 -195
  92. package/src/data/ratingScale.js +14 -0
  93. package/src/data/teamTypes.d.ts +23 -0
  94. package/src/data/trackTypes.d.ts +101 -0
  95. package/src/data/vehicleData.js +12 -23
  96. package/src/environment/actions.js +3 -2
  97. package/src/environment/controllerLoop.js +57 -44
  98. package/src/environment/controllerScheduler.js +30 -0
  99. package/src/environment/driverObservations.js +260 -0
  100. package/src/environment/driverReset.js +51 -0
  101. package/src/environment/episode.js +0 -1
  102. package/src/environment/evaluation.js +6 -90
  103. package/src/environment/evaluationTracker.js +100 -0
  104. package/src/environment/events.js +23 -0
  105. package/src/environment/index.d.ts +35 -91
  106. package/src/environment/metrics.js +3 -17
  107. package/src/environment/observationSchema.js +104 -0
  108. package/src/environment/observationVector.js +16 -115
  109. package/src/environment/observations.js +16 -316
  110. package/src/environment/options.js +5 -24
  111. package/src/environment/renderFrames.js +21 -0
  112. package/src/environment/resetOptions.js +41 -0
  113. package/src/environment/results.js +125 -0
  114. package/src/environment/rewards.js +10 -16
  115. package/src/environment/runtime.js +17 -300
  116. package/src/environment/scenarioOptions.js +90 -0
  117. package/src/environment/scenarioPlacements.js +70 -0
  118. package/src/environment/scenarioPresets.js +78 -0
  119. package/src/environment/scenarios.js +5 -236
  120. package/src/environment/scratch.js +18 -0
  121. package/src/environment/sensorOptions.js +20 -0
  122. package/src/environment/sensors/index.js +2 -556
  123. package/src/environment/sensors/indexedRayBands.js +0 -18
  124. package/src/environment/sensors/nearbyCars.js +18 -15
  125. package/src/environment/sensors/opponentRadar.js +3 -2
  126. package/src/environment/sensors/pitConnectorProximity.js +20 -0
  127. package/src/environment/sensors/rayBandTrace.js +1 -37
  128. package/src/environment/sensors/rayBatch.js +81 -0
  129. package/src/environment/sensors/rayConfig.js +26 -0
  130. package/src/environment/sensors/rayGeometry.js +0 -8
  131. package/src/environment/sensors/rayGuards.js +0 -6
  132. package/src/environment/sensors/rayOutputs.js +75 -0
  133. package/src/environment/sensors/rayQuery.js +110 -0
  134. package/src/environment/sensors/rayScratch.js +146 -0
  135. package/src/environment/sensors/sensorTargets.js +12 -4
  136. package/src/environment/sensors/surfaceRays.js +6 -61
  137. package/src/environment/sensors/trackRays.js +7 -80
  138. package/src/environment/simulationSetup.js +30 -0
  139. package/src/environment/specs.js +15 -109
  140. package/src/index.d.ts +43 -202
  141. package/src/index.js +28 -6
  142. package/src/placeholder/index.js +1 -8
  143. package/src/rendering/displayLifecycle.js +6 -0
  144. package/src/rendering/proceduralTrackAsset.js +1 -1
  145. package/src/rendering/renderSnapshot.js +3 -0
  146. package/src/rendering/track/finishLineRenderer.js +1 -1
  147. package/src/rendering/track/gridRenderer.js +1 -1
  148. package/src/rendering/track/offsetStrokeSafety.js +2 -2
  149. package/src/rendering/track/pitLaneRenderer.js +1 -1
  150. package/src/rendering/track/trackMaterialRenderer.js +4 -4
  151. package/src/rendering/track/trackRenderConstants.js +0 -1
  152. package/src/rendering/track/trackRenderGeometry.js +1 -7
  153. package/src/simulation/collisionGeometry.js +12 -20
  154. package/src/simulation/driver/advancedPathControls.js +53 -0
  155. package/src/simulation/driver/advancedRacePace.js +112 -0
  156. package/src/simulation/driver/advancedRacingControls.js +61 -0
  157. package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
  158. package/src/simulation/driver/advancedRacingSteering.js +76 -0
  159. package/src/simulation/driver/advancedRacingTiming.js +16 -0
  160. package/src/simulation/driver/arcadeRacePace.js +83 -0
  161. package/src/simulation/driver/arcadeRacingControls.js +111 -0
  162. package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
  163. package/src/simulation/driver/driverControlConstants.js +0 -2
  164. package/src/simulation/driver/driverController.js +16 -5
  165. package/src/simulation/driver/edgeRecovery.js +2 -3
  166. package/src/simulation/driver/racingControls.js +10 -396
  167. package/src/simulation/driver/racingDriverProfile.js +48 -0
  168. package/src/simulation/driver/racingLinePlan.js +54 -20
  169. package/src/simulation/driver/rejoinControls.js +59 -112
  170. package/src/simulation/driver/safetyCarControls.js +12 -0
  171. package/src/simulation/driver/trafficScan.js +136 -27
  172. package/src/simulation/participants/participantInteractions.js +24 -15
  173. package/src/simulation/pit/pitCarPlacement.js +29 -0
  174. package/src/simulation/pit/pitFlow.js +8 -30
  175. package/src/simulation/pit/pitIntent.js +4 -3
  176. package/src/simulation/pit/pitOccupancy.js +2 -2
  177. package/src/simulation/pit/pitQueue.js +2 -18
  178. package/src/simulation/pit/pitRouteMovement.js +1 -36
  179. package/src/simulation/pit/pitRouting.js +5 -81
  180. package/src/simulation/pit/pitService.js +3 -32
  181. package/src/simulation/pit/pitServiceConstants.js +1 -0
  182. package/src/simulation/pit/pitServiceProgress.js +34 -0
  183. package/src/simulation/pit/pitSnapshots.js +8 -4
  184. package/src/simulation/pit/pitState.js +1 -5
  185. package/src/simulation/race/classification.js +1 -8
  186. package/src/simulation/race/raceFinish.js +3 -8
  187. package/src/simulation/race/raceOrder.js +2 -13
  188. package/src/simulation/race/raceProgress.js +6 -20
  189. package/src/simulation/race/raceSetup.js +1 -1
  190. package/src/simulation/race/raceStep.js +20 -11
  191. package/src/simulation/race/redFlag.js +2 -4
  192. package/src/simulation/race/safetyCar.js +5 -9
  193. package/src/simulation/race/stalledDnf.js +2 -4
  194. package/src/simulation/raceSimulation.js +6 -38
  195. package/src/simulation/replay/replayGhosts.js +44 -32
  196. package/src/simulation/rules/collisionSteward.js +0 -14
  197. package/src/simulation/rules/penaltyActions.js +37 -0
  198. package/src/simulation/rules/penaltyLedger.js +8 -1
  199. package/src/simulation/rules/pitLaneSpeedingSteward.js +1 -1
  200. package/src/simulation/rules/rulesReview.js +20 -16
  201. package/src/simulation/rules/trackLimitsSteward.js +2 -2
  202. package/src/simulation/simMath.js +4 -0
  203. package/src/simulation/snapshots/raceSnapshots.js +30 -11
  204. package/src/simulation/timing/drsState.js +8 -0
  205. package/src/simulation/timing/drsTiming.js +2 -4
  206. package/src/simulation/timing/lapTelemetry.js +7 -4
  207. package/src/simulation/timing/sectorPerformance.js +14 -6
  208. package/src/simulation/timing/timingLines.js +12 -6
  209. package/src/simulation/track/pitLaneAccess.js +2 -2
  210. package/src/simulation/track/pitLaneLayout.js +4 -135
  211. package/src/simulation/track/pitLaneModel.js +70 -0
  212. package/src/simulation/track/pitLaneState.js +0 -1
  213. package/src/simulation/track/pitServiceLayout.js +121 -0
  214. package/src/simulation/track/proceduralTrack.js +77 -0
  215. package/src/simulation/track/spatialQueries.js +76 -31
  216. package/src/simulation/track/trackBuilder.js +54 -0
  217. package/src/simulation/track/trackConstants.js +0 -22
  218. package/src/simulation/track/trackMath.js +4 -29
  219. package/src/simulation/track/trackModel.js +12 -240
  220. package/src/simulation/track/trackModelCache.js +105 -0
  221. package/src/simulation/track/trackNearestQueries.js +1218 -0
  222. package/src/simulation/track/trackQueryConstants.js +3 -0
  223. package/src/simulation/track/trackQueryConstruction.js +368 -0
  224. package/src/simulation/track/trackQueryIndex.js +12 -1933
  225. package/src/simulation/track/trackQuerySegments.js +174 -0
  226. package/src/simulation/track/trackQueryStats.js +12 -0
  227. package/src/simulation/track/trackRayQueries.js +165 -0
  228. package/src/simulation/vehicle/advancedContactResponse.js +89 -0
  229. package/src/simulation/vehicle/advancedTireForces.js +92 -0
  230. package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
  231. package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
  232. package/src/simulation/vehicle/contactResolution.js +23 -9
  233. package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
  234. package/src/simulation/vehicle/pitWheelSurface.js +0 -12
  235. package/src/simulation/vehicle/runoffResponse.js +2 -4
  236. package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
  237. package/src/simulation/vehicle/vehicleGeometry.js +85 -103
  238. package/src/simulation/vehicle/vehicleKinematics.js +10 -0
  239. package/src/simulation/vehicle/vehiclePhysics.js +42 -196
  240. package/src/simulation/vehicle/vehicleSnapshots.js +18 -6
  241. package/src/simulation/vehicle/vehicleState.js +5 -3
  242. package/src/simulation/vehicle/wheelSurface.js +13 -432
  243. package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
  244. package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
  245. package/src/styles.css +173 -19
  246. package/src/ui/bannerTemplates.js +4 -2
  247. package/src/ui/cameraControlsTemplate.js +4 -2
  248. package/src/ui/gridWalkTemplate.js +29 -0
  249. package/src/ui/htmlEscaping.js +8 -0
  250. package/src/ui/raceCanvasTemplate.js +3 -2
  251. package/src/ui/shellTemplate.js +3 -0
  252. package/src/ui/telemetryDrawerTemplate.js +4 -2
  253. package/src/ui/telemetryTemplates.js +3 -22
  254. package/src/ui/templateUtils.js +2 -8
  255. package/src/ui/viewControlsTemplate.js +21 -0
  256. package/examples/train-basic-policy.mjs +0 -276
  257. package/examples/trainingData.mjs +0 -133
@@ -4,13 +4,13 @@ import { metersToSimUnits, simUnitsToMeters } from '../../simulation/units.js';
4
4
  import {
5
5
  ANALYTIC_TRACK_RAY_MAX_CURVATURE,
6
6
  DRIVER_RAY_REFINE_STEPS,
7
- PIT_CONNECTOR_RAY_FALLBACK_METERS,
8
7
  TRACK_RAY_REFINE_STEPS,
9
8
  TRACK_RAY_STEP_METERS,
10
9
  } from './rayDefaults.js';
11
10
  import { canUseIndexedRecoveryRayApproximation } from './rayGuards.js';
12
11
  import { pointOnRay } from './rayGeometry.js';
13
12
  import { findIndexedTrackBandBoundaries } from './indexedRayBands.js';
13
+ import { isNearPitConnector } from './pitConnectorProximity.js';
14
14
 
15
15
  export function requestedSurfaceChannels(channels = []) {
16
16
  const requested = [];
@@ -55,7 +55,7 @@ export function estimateSurfaceHits(
55
55
 
56
56
  const originState = context?.precision === precision
57
57
  ? context.originState
58
- : nearestRayTrackState(snapshot.track, car, origin, car.progress, precision);
58
+ : nearestRayTrackState(snapshot.track, car, origin, car.progress);
59
59
  writeSurfaceHitsAtOrigin(hits, requested, originState);
60
60
  const indexedHits = estimateIndexedSurfaceHits({
61
61
  car,
@@ -65,7 +65,6 @@ export function estimateSurfaceHits(
65
65
  vector,
66
66
  requested,
67
67
  originState,
68
- precision,
69
68
  sharedRayQuery,
70
69
  hits,
71
70
  });
@@ -90,7 +89,7 @@ export function estimateSurfaceHits(
90
89
  let previousState = null;
91
90
 
92
91
  for (let distance = 0; distance <= maxDistance; distance += step) {
93
- const state = nearestRayTrackState(snapshot.track, car, pointOnRay(origin, vector, distance), car.progress, precision);
92
+ const state = nearestRayTrackState(snapshot.track, car, pointOnRay(origin, vector, distance), car.progress);
94
93
  if (pendingKerb && matchesSurfaceChannel('kerb', state)) {
95
94
  const hitDistance = previousState
96
95
  ? refineSurfaceTransition(snapshot.track, car, origin, vector, car.progress, previousDistance, distance, 'kerb', refineStepsForPrecision(precision))
@@ -187,7 +186,7 @@ function estimateAnalyticSurfaceHits({ car, track, ray, vector, requested, origi
187
186
  return hits;
188
187
  }
189
188
 
190
- function estimateIndexedSurfaceHits({ car, track, ray, origin, vector, requested, originState, precision, sharedRayQuery, hits }) {
189
+ function estimateIndexedSurfaceHits({ car, track, ray, origin, vector, requested, originState, sharedRayQuery, hits }) {
191
190
  if (!originState || car.inPitLane || originState.inPitLane) return null;
192
191
  if (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState)) return null;
193
192
 
@@ -224,23 +223,9 @@ function estimateIndexedSurfaceHits({ car, track, ray, origin, vector, requested
224
223
  writeSurfaceMiss(hits[channel], ray.lengthMeters);
225
224
  continue;
226
225
  }
227
- const hitDistance = usesMainTrackOnlyRays(car) || refineStepsForPrecision(precision) <= 0 || precision === 'debug'
228
- ? boundaryDistance
229
- : sampledIndexedSurfaceDistance({
230
- track,
231
- car,
232
- origin,
233
- vector,
234
- progressHint: car.progress,
235
- distance: boundaryDistance,
236
- maxDistance: metersToSimUnits(ray.lengthMeters),
237
- channel,
238
- precision,
239
- });
240
- if (hitDistance == null) return null;
241
226
  writeSurfaceHit(
242
227
  hits[channel],
243
- simUnitsToMeters(hitDistance),
228
+ simUnitsToMeters(boundaryDistance),
244
229
  channel === 'kerb'
245
230
  ? 'kerb'
246
231
  : surfaceBeyondKerb(track, originState.signedOffset ?? 0, lateral, metersToSimUnits(ray.lengthMeters)),
@@ -250,28 +235,6 @@ function estimateIndexedSurfaceHits({ car, track, ray, origin, vector, requested
250
235
  return hits;
251
236
  }
252
237
 
253
- function sampledIndexedSurfaceDistance({
254
- track,
255
- car,
256
- origin,
257
- vector,
258
- progressHint,
259
- distance,
260
- maxDistance,
261
- channel,
262
- precision,
263
- }) {
264
- const step = metersToSimUnits(TRACK_RAY_STEP_METERS);
265
- const firstIndex = Math.max(0, Math.floor(Math.max(0, distance - step * 1.5) / step));
266
- const lastIndex = Math.ceil(Math.min(maxDistance, distance + step * 8) / step);
267
- for (let index = firstIndex; index <= lastIndex; index += 1) {
268
- const sampleDistance = Math.min(maxDistance, index * step);
269
- const state = nearestRayTrackState(track, car, pointOnRay(origin, vector, sampleDistance), progressHint, precision);
270
- if (matchesSurfaceChannel(channel, state)) return sampleDistance;
271
- }
272
- return null;
273
- }
274
-
275
238
  function usesMainTrackOnlyRays(car) {
276
239
  return car?.interaction?.profile === 'batch-training';
277
240
  }
@@ -320,24 +283,6 @@ function surfaceBeyondKerb(track, offset, lateral, maxDistance) {
320
283
  return hitAbs <= gravelOuter ? 'gravel' : 'grass';
321
284
  }
322
285
 
323
- function isNearPitConnector(track, state) {
324
- const pitLane = track.pitLane;
325
- if (!pitLane?.enabled || !Number.isFinite(state?.distance)) return false;
326
- const window = metersToSimUnits(PIT_CONNECTOR_RAY_FALLBACK_METERS);
327
- const entryDistance = pitLane.entry?.trackDistance ?? pitLane.entry?.distanceFromStart;
328
- const exitDistance = pitLane.exit?.trackDistance ?? pitLane.exit?.distanceFromStart;
329
- return wrappedTrackDistance(state.distance, entryDistance, track.length) <= window ||
330
- wrappedTrackDistance(state.distance, exitDistance, track.length) <= window;
331
- }
332
-
333
- function wrappedTrackDistance(first, second, totalLength) {
334
- if (!Number.isFinite(first) || !Number.isFinite(second) || !Number.isFinite(totalLength) || totalLength <= 0) {
335
- return Infinity;
336
- }
337
- const delta = Math.abs(first - second);
338
- return Math.min(delta, totalLength - delta);
339
- }
340
-
341
286
  function refineSurfaceTransition(
342
287
  track,
343
288
  car,
@@ -396,7 +341,7 @@ function canSampleRecoverySurface(track, originState, vector) {
396
341
  return offset > 0 ? lateral < 0 : lateral > 0;
397
342
  }
398
343
 
399
- function nearestRayTrackState(track, car, point, progressHint, precision = 'debug') {
344
+ function nearestRayTrackState(track, car, point, progressHint) {
400
345
  return nearestTrackState(track, point, progressHint, {
401
346
  allowPitOverride: pitOverrideAllowedForCar(car),
402
347
  });
@@ -6,13 +6,13 @@ import { metersToSimUnits, simUnitsToMeters } from '../../simulation/units.js';
6
6
  import {
7
7
  ANALYTIC_TRACK_RAY_MAX_CURVATURE,
8
8
  DRIVER_RAY_REFINE_STEPS,
9
- PIT_CONNECTOR_RAY_FALLBACK_METERS,
10
9
  TRACK_RAY_REFINE_STEPS,
11
10
  TRACK_RAY_STEP_METERS,
12
11
  } from './rayDefaults.js';
13
12
  import { canUseIndexedRecoveryRayApproximation } from './rayGuards.js';
14
13
  import { degreesToRadians, getCarRayOrigin, getCarRayVector, pointOnRay } from './rayGeometry.js';
15
14
  import { findIndexedTrackBandBoundaries } from './indexedRayBands.js';
15
+ import { isNearPitConnector } from './pitConnectorProximity.js';
16
16
 
17
17
  const RAY_TRACK_STATE_REUSE_POSITION_TOLERANCE = metersToSimUnits(1);
18
18
  const RAY_TRACK_STATE_REUSE_PROGRESS_TOLERANCE = metersToSimUnits(0.25);
@@ -25,7 +25,7 @@ export function createTrackRayContext(car, snapshot, origin, precision = 'debug'
25
25
  const reusedOriginState = reusableRayOriginState(snapshot.track, car, origin);
26
26
  return {
27
27
  origin,
28
- originState: reusedOriginState ?? nearestRayTrackState(snapshot.track, car, origin, car.progress, precision),
28
+ originState: reusedOriginState ?? nearestRayTrackState(snapshot.track, car, origin, car.progress),
29
29
  precision,
30
30
  };
31
31
  }
@@ -48,7 +48,7 @@ export function estimateTrackHit(
48
48
  const step = metersToSimUnits(TRACK_RAY_STEP_METERS);
49
49
  const originState = context?.precision === precision
50
50
  ? context.originState
51
- : nearestRayTrackState(snapshot.track, car, origin, car.progress, precision);
51
+ : nearestRayTrackState(snapshot.track, car, origin, car.progress);
52
52
  const includePitLane = Boolean(car.inPitLane || car.pitLanePart || originState.inPitLane);
53
53
  const indexedHit = estimateIndexedTrackHit({
54
54
  car,
@@ -62,22 +62,9 @@ export function estimateTrackHit(
62
62
  sharedRayQuery,
63
63
  });
64
64
  if (indexedHit) return indexedHit;
65
- const directionalAnalyticHit = canUseDirectionalAnalyticTrackHit({ car, track: snapshot.track, originState, ray, includePitLane })
66
- ? estimateAnalyticMainTrackHit({
67
- car,
68
- track: snapshot.track,
69
- origin,
70
- originState,
71
- ray,
72
- lengthMeters,
73
- includePitLane,
74
- })
75
- : null;
76
- if (directionalAnalyticHit) return directionalAnalyticHit;
77
65
  const analyticHit = estimateAnalyticMainTrackHit({
78
66
  car,
79
67
  track: snapshot.track,
80
- origin,
81
68
  originState,
82
69
  ray,
83
70
  lengthMeters,
@@ -88,7 +75,7 @@ export function estimateTrackHit(
88
75
  let previousInside = null;
89
76
 
90
77
  for (let distance = 0; distance <= maxDistance; distance += step) {
91
- const state = nearestRayTrackState(snapshot.track, car, pointOnRay(origin, ray, distance), car.progress, precision);
78
+ const state = nearestRayTrackState(snapshot.track, car, pointOnRay(origin, ray, distance), car.progress);
92
79
  const inside = isInsideTrackBorder(state, snapshot.track, includePitLane);
93
80
  if (previousInside == null) {
94
81
  previousInside = inside;
@@ -109,7 +96,6 @@ export function estimateTrackHit(
109
96
  kind,
110
97
  includePitLane,
111
98
  refineStepsForPrecision(precision),
112
- precision,
113
99
  );
114
100
  return {
115
101
  hit: true,
@@ -132,7 +118,7 @@ export function createTrackMiss(lengthMeters) {
132
118
  };
133
119
  }
134
120
 
135
- function estimateAnalyticMainTrackHit({ car, track, origin, originState, ray, lengthMeters, includePitLane }) {
121
+ function estimateAnalyticMainTrackHit({ car, track, originState, ray, lengthMeters, includePitLane }) {
136
122
  if (includePitLane || (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState))) return null;
137
123
  if (!canUseIndexedRecoveryRayApproximation(track, originState)) return null;
138
124
  if (Math.abs(originState.curvature ?? 0) > ANALYTIC_TRACK_RAY_MAX_CURVATURE) return null;
@@ -162,15 +148,6 @@ function estimateAnalyticMainTrackHit({ car, track, origin, originState, ray, le
162
148
  };
163
149
  }
164
150
 
165
- function canUseDirectionalAnalyticTrackHit({ car, track, originState, ray, includePitLane }) {
166
- if (includePitLane || (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState))) return false;
167
- if (!canUseIndexedRecoveryRayApproximation(track, originState)) return false;
168
- if (Math.abs(originState.curvature ?? 0) > ANALYTIC_TRACK_RAY_MAX_CURVATURE) return false;
169
-
170
- const lateral = ray.x * originState.normalX + ray.y * originState.normalY;
171
- return Math.abs(lateral) >= 0.08;
172
- }
173
-
174
151
  function estimateIndexedTrackHit({ car, track, origin, originState, ray, lengthMeters, includePitLane, precision, sharedRayQuery }) {
175
152
  if (includePitLane || (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState))) return null;
176
153
  const trackHalfWidth = track.width / 2;
@@ -205,7 +182,6 @@ function estimateIndexedTrackHit({ car, track, origin, originState, ray, lengthM
205
182
  kind,
206
183
  includePitLane,
207
184
  precision,
208
- originInside: inside,
209
185
  });
210
186
  if (hitDistance == null) return null;
211
187
  return {
@@ -219,24 +195,6 @@ function usesMainTrackOnlyRays(car) {
219
195
  return car?.interaction?.profile === 'batch-training';
220
196
  }
221
197
 
222
- function isNearPitConnector(track, state) {
223
- const pitLane = track.pitLane;
224
- if (!pitLane?.enabled || !Number.isFinite(state?.distance)) return false;
225
- const window = metersToSimUnits(PIT_CONNECTOR_RAY_FALLBACK_METERS);
226
- const entryDistance = pitLane.entry?.trackDistance ?? pitLane.entry?.distanceFromStart;
227
- const exitDistance = pitLane.exit?.trackDistance ?? pitLane.exit?.distanceFromStart;
228
- return wrappedTrackDistance(state.distance, entryDistance, track.length) <= window ||
229
- wrappedTrackDistance(state.distance, exitDistance, track.length) <= window;
230
- }
231
-
232
- function wrappedTrackDistance(first, second, totalLength) {
233
- if (!Number.isFinite(first) || !Number.isFinite(second) || !Number.isFinite(totalLength) || totalLength <= 0) {
234
- return Infinity;
235
- }
236
- const delta = Math.abs(first - second);
237
- return Math.min(delta, totalLength - delta);
238
- }
239
-
240
198
  function estimateLocalTrackHit(car, snapshot, angleDegrees, lengthMeters) {
241
199
  const trackHalfWidthMeters = simUnitsToMeters(snapshot.track.width / 2);
242
200
  const offsetMeters = simUnitsToMeters(car.signedOffset ?? 0);
@@ -277,13 +235,12 @@ function refineTrackTransitionDistance(
277
235
  kind,
278
236
  includePitLane = true,
279
237
  refineSteps = TRACK_RAY_REFINE_STEPS,
280
- precision = 'debug',
281
238
  ) {
282
239
  let low = lowDistance;
283
240
  let high = highDistance;
284
241
  for (let index = 0; index < refineSteps; index += 1) {
285
242
  const middle = (low + high) / 2;
286
- const state = nearestRayTrackState(track, car, pointOnRay(origin, ray, middle), progressHint, precision);
243
+ const state = nearestRayTrackState(track, car, pointOnRay(origin, ray, middle), progressHint);
287
244
  const inside = isInsideTrackBorder(state, track, includePitLane);
288
245
  if (kind === 'entry') {
289
246
  if (inside) high = middle;
@@ -297,7 +254,7 @@ function refineTrackTransitionDistance(
297
254
  return high;
298
255
  }
299
256
 
300
- function nearestRayTrackState(track, car, point, progressHint, precision = 'debug') {
257
+ function nearestRayTrackState(track, car, point, progressHint) {
301
258
  return nearestTrackState(track, point, progressHint, {
302
259
  allowPitOverride: pitOverrideAllowedForCar(car),
303
260
  });
@@ -440,7 +397,6 @@ function validatedIndexedTransitionDistance({
440
397
  kind,
441
398
  includePitLane,
442
399
  precision = 'driver',
443
- originInside = kind === 'exit',
444
400
  }) {
445
401
  const step = metersToSimUnits(TRACK_RAY_STEP_METERS);
446
402
  const refineSteps = refineStepsForPrecision(precision);
@@ -460,7 +416,6 @@ function validatedIndexedTransitionDistance({
460
416
  ? !previousInside && inside
461
417
  : previousInside && !inside;
462
418
  if (matched) {
463
- if (refineSteps <= 0) return distance;
464
419
  return refineValidatedTransitionDistance(
465
420
  track,
466
421
  car,
@@ -479,34 +434,6 @@ function validatedIndexedTransitionDistance({
479
434
  return null;
480
435
  }
481
436
 
482
- function sampledIndexedTransitionDistance({
483
- track,
484
- car,
485
- origin,
486
- ray,
487
- progressHint,
488
- distance,
489
- maxDistance,
490
- kind,
491
- includePitLane,
492
- }) {
493
- const step = metersToSimUnits(TRACK_RAY_STEP_METERS);
494
- const firstIndex = Math.max(1, Math.floor(Math.max(0, distance - step * 1.5) / step));
495
- const lastIndex = Math.ceil(Math.min(maxDistance, distance + step * 8) / step);
496
- for (let index = firstIndex; index <= lastIndex; index += 1) {
497
- const high = Math.min(maxDistance, index * step);
498
- const low = Math.max(0, high - step);
499
- if (high <= low) continue;
500
- const previousInside = validatedInsideTrackBorder(track, car, origin, ray, low, progressHint, includePitLane);
501
- const inside = validatedInsideTrackBorder(track, car, origin, ray, high, progressHint, includePitLane);
502
- const matched = kind === 'entry'
503
- ? !previousInside && inside
504
- : previousInside && !inside;
505
- if (matched) return high;
506
- }
507
- return null;
508
- }
509
-
510
437
  function refineValidatedTransitionDistance(track, car, origin, ray, progressHint, lowDistance, highDistance, kind, includePitLane, refineSteps) {
511
438
  let low = lowDistance;
512
439
  let high = highDistance;
@@ -0,0 +1,30 @@
1
+ import { createRaceSimulation, FIXED_STEP } from '../simulation/raceSimulation.js';
2
+ import { disableWarmupOptions, runWarmupWithGuard } from '../simulation/warmup/runtimeWarmup.js';
3
+ import { applyControlledRunoffResponse, initializeControlledPitIntentForSimulation } from './driverReset.js';
4
+ import { buildEnvironmentObservation } from './observations.js';
5
+ import { applyEnvironmentScenario } from './scenarios.js';
6
+
7
+ export function createSimulationWithEnvironmentScenario(options) {
8
+ runWarmupWithGuard({
9
+ options,
10
+ surface: 'environment',
11
+ execute: ({ warmup }) => {
12
+ const warmupSimulation = createRaceSimulation(disableWarmupOptions(options, warmup.surface));
13
+ initializeControlledPitIntentForSimulation(warmupSimulation, options);
14
+ applyEnvironmentScenario(warmupSimulation, options);
15
+ applyControlledRunoffResponse(warmupSimulation, options);
16
+ for (let index = 0; index < warmup.steps; index += 1) warmupSimulation.step(FIXED_STEP);
17
+ const warmupSnapshot = warmupSimulation.snapshotObservation?.() ?? warmupSimulation.snapshot();
18
+ buildEnvironmentObservation({
19
+ snapshot: warmupSnapshot,
20
+ options,
21
+ events: [],
22
+ });
23
+ },
24
+ });
25
+ const sim = createRaceSimulation(disableWarmupOptions(options, 'environment'));
26
+ initializeControlledPitIntentForSimulation(sim, options);
27
+ applyEnvironmentScenario(sim, options);
28
+ applyControlledRunoffResponse(sim, options);
29
+ return sim;
30
+ }
@@ -1,8 +1,7 @@
1
1
  import { normalizeLookaheadMeters } from './observationOptions.js';
2
- import { effectiveSensorOptions } from './observations.js';
3
- import { normalizeRayOptions } from './sensors.js';
4
-
5
- const MODEL_RAY_SURFACE_CHANNELS = Object.freeze(['kerb', 'illegalSurface']);
2
+ import { effectiveSensorOptions } from './sensorOptions.js';
3
+ import { normalizeRayOptions } from './sensors/rayConfig.js';
4
+ import { buildObservationVectorSchema } from './observationSchema.js';
6
5
 
7
6
  export function buildActionSpec(options) {
8
7
  const compounds = Array.isArray(options.rules?.modules?.tireStrategy?.compounds) &&
@@ -119,7 +118,13 @@ export function buildObservationSpec(options) {
119
118
  events: { type: 'array' },
120
119
  },
121
120
  vector: {
122
- schema: buildVectorSchema({ rayOptions, nearbyOptions, lookaheadMeters, profile }),
121
+ schema: buildObservationVectorSchema({
122
+ profile,
123
+ lookaheadCount: lookaheadMeters.length,
124
+ rayCount: rayOptions.enabled ? rayOptions.anglesDegrees.length : 0,
125
+ rayChannels: rayOptions.channels,
126
+ nearbyCount: nearbyOptions.enabled ? nearbyOptions.maxCars : 0,
127
+ }),
123
128
  },
124
129
  perDriver: buildPerDriverObservationSpecs(options, { rayOptions, nearbyOptions, lookaheadMeters, profile }),
125
130
  };
@@ -136,11 +141,12 @@ function buildPerDriverObservationSpecs(options, { rayOptions, nearbyOptions, lo
136
141
  nearbyCars: buildNearbyObjectSpec(sensors.nearbyCars),
137
142
  },
138
143
  vector: {
139
- schema: buildVectorSchema({
140
- rayOptions: sensors.rays,
141
- nearbyOptions: sensors.nearbyCars,
142
- lookaheadMeters,
144
+ schema: buildObservationVectorSchema({
143
145
  profile,
146
+ lookaheadCount: lookaheadMeters.length,
147
+ rayCount: sensors.rays.enabled ? sensors.rays.anglesDegrees.length : 0,
148
+ rayChannels: sensors.rays.channels,
149
+ nearbyCount: sensors.nearbyCars.enabled ? sensors.nearbyCars.maxCars : 0,
144
150
  }),
145
151
  },
146
152
  }];
@@ -187,106 +193,6 @@ function buildNearbyObjectSpec(nearbyOptions) {
187
193
  };
188
194
  }
189
195
 
190
- function buildVectorSchema({ rayOptions, nearbyOptions, lookaheadMeters, profile = 'default' }) {
191
- const includePhysicalDriverSenses = profile === 'physical-driver';
192
- const schema = [
193
- { name: 'self.speedKph', unit: 'kph', scale: 'fixed:400' },
194
- { name: 'self.speedMetersPerSecond', unit: 'm/s', scale: 'fixed:120' },
195
- { name: 'self.steeringAngleRadians', unit: 'rad', scale: 'fixed:pi' },
196
- { name: 'self.throttle', scale: '0..1' },
197
- { name: 'self.brake', scale: '0..1' },
198
- { name: 'self.lateralG', scale: 'fixed:8g' },
199
- { name: 'self.longitudinalG', scale: 'fixed:6g' },
200
- { name: 'self.gripUsage', scale: '0..2' },
201
- { name: 'self.slipAngleRadians', unit: 'rad', scale: 'fixed:pi' },
202
- { name: 'self.tractionLimited', scale: 'boolean' },
203
- { name: 'self.lapProgressRatio', scale: '0..1' },
204
- { name: 'self.trackOffsetMeters', unit: 'm', scale: 'fixed:meters' },
205
- { name: 'self.trackHeadingErrorRadians', unit: 'rad', scale: 'fixed:pi' },
206
- { name: 'self.onTrack', scale: 'boolean' },
207
- { name: 'self.inPitLane', scale: 'boolean' },
208
- { name: 'self.tireEnergy', scale: '0..100' },
209
- { name: 'self.pitIntent', scale: '0..2' },
210
- { name: 'self.pitStopActive', scale: 'boolean' },
211
- { name: 'race.positionNormalized', scale: '0..1' },
212
- { name: 'race.raceModeGreen', scale: 'boolean' },
213
- { name: 'race.raceModeSafetyCar', scale: 'boolean' },
214
- { name: 'race.redFlag', scale: 'boolean' },
215
- { name: 'race.pitLaneOpen', scale: 'boolean' },
216
- { name: 'track.curvature', scale: 'track-curvature' },
217
- ];
218
- if (includePhysicalDriverSenses) {
219
- schema.push(
220
- { name: 'self.yawRateRadiansPerSecond', unit: 'rad/s', scale: 'fixed:pi' },
221
- { name: 'trackRelation.leftBoundaryMeters', unit: 'm', scale: 'fixed:meters' },
222
- { name: 'trackRelation.rightBoundaryMeters', unit: 'm', scale: 'fixed:meters' },
223
- { name: 'trackRelation.legalWidthMeters', unit: 'm', scale: 'fixed:meters' },
224
- );
225
- for (let index = 0; index < 4; index += 1) {
226
- schema.push(
227
- { name: `contactPatches[${index}].present`, scale: 'boolean' },
228
- { name: `contactPatches[${index}].surfaceCode`, scale: 'surface-code' },
229
- { name: `contactPatches[${index}].onLegalSurface`, scale: 'boolean' },
230
- { name: `contactPatches[${index}].signedOffsetMeters`, unit: 'm', scale: 'fixed:meters' },
231
- { name: `contactPatches[${index}].crossTrackErrorMeters`, unit: 'm', scale: 'fixed:meters' },
232
- );
233
- }
234
- }
235
- lookaheadMeters.forEach((_, index) => {
236
- schema.push(
237
- { name: `track.lookahead[${index}].curvature`, scale: 'track-curvature' },
238
- { name: `track.lookahead[${index}].headingDeltaRadians`, unit: 'rad', scale: 'fixed:pi' },
239
- );
240
- });
241
- if (rayOptions.enabled) {
242
- rayOptions.anglesDegrees.forEach((_, index) => {
243
- schema.push(
244
- { name: `rays[${index}].track.distanceRatio`, scale: '0..1' },
245
- { name: `rays[${index}].track.hit`, scale: 'boolean' },
246
- { name: `rays[${index}].track.kindExit`, scale: 'boolean' },
247
- { name: `rays[${index}].track.kindEntry`, scale: 'boolean' },
248
- { name: `rays[${index}].car.distanceRatio`, scale: '0..1' },
249
- { name: `rays[${index}].car.hit`, scale: 'boolean' },
250
- { name: `rays[${index}].car.relativeSpeedKph`, unit: 'kph', scale: 'fixed:200' },
251
- { name: `rays[${index}].car.targetTypeReplayGhost`, scale: 'boolean' },
252
- );
253
- MODEL_RAY_SURFACE_CHANNELS.forEach((channel) => {
254
- if (rayOptions.channels?.includes?.(channel)) {
255
- schema.push(
256
- { name: `rays[${index}].${channel}.distanceRatio`, scale: '0..1' },
257
- { name: `rays[${index}].${channel}.hit`, scale: 'boolean' },
258
- );
259
- }
260
- });
261
- });
262
- }
263
- if (nearbyOptions.enabled) {
264
- for (let index = 0; index < nearbyOptions.maxCars; index += 1) {
265
- schema.push(
266
- { name: `nearbyCars[${index}].present`, scale: 'boolean' },
267
- { name: `nearbyCars[${index}].relativeForwardRatio`, scale: '-1..1' },
268
- { name: `nearbyCars[${index}].relativeRightRatio`, scale: '-1..1' },
269
- { name: `nearbyCars[${index}].relativeDistanceRatio`, scale: '0..1' },
270
- { name: `nearbyCars[${index}].relativeSpeedKph`, unit: 'kph', scale: 'fixed:200' },
271
- { name: `nearbyCars[${index}].relativeHeadingRadians`, unit: 'rad', scale: 'fixed:pi' },
272
- { name: `nearbyCars[${index}].ahead`, scale: 'boolean' },
273
- { name: `nearbyCars[${index}].sameLap`, scale: 'boolean' },
274
- { name: `nearbyCars[${index}].entityTypeReplayGhost`, scale: 'boolean' },
275
- );
276
- if (includePhysicalDriverSenses) {
277
- schema.push(
278
- { name: `nearbyCars[${index}].behind`, scale: 'boolean' },
279
- { name: `nearbyCars[${index}].closingRateMetersPerSecond`, unit: 'm/s', scale: 'fixed:100' },
280
- { name: `nearbyCars[${index}].timeToContactSeconds`, unit: 's', scale: 'fixed:10' },
281
- { name: `nearbyCars[${index}].leftOverlap`, scale: 'boolean' },
282
- { name: `nearbyCars[${index}].rightOverlap`, scale: 'boolean' },
283
- );
284
- }
285
- }
286
- }
287
- return schema;
288
- }
289
-
290
196
  function surfaceRaySpec(lengthMeters) {
291
197
  return {
292
198
  distanceMeters: { unit: 'm', noHitValue: lengthMeters },