@inventure71/paddockjs 1.0.0 → 2.0.1

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 (203) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/INSTALL_AND_UPDATE.md +4 -3
  3. package/README.md +219 -12
  4. package/docs/architecture.md +126 -45
  5. package/docs/concepts.md +9 -5
  6. package/docs/custom_model_controller.md +329 -0
  7. package/docs/data_contract.md +317 -22
  8. package/docs/data_model.md +1 -1
  9. package/docs/index.md +10 -0
  10. package/docs/learnings.md +22 -0
  11. package/docs/rules.md +71 -14
  12. package/docs/sense_contract.md +59 -0
  13. package/docs/system_specs.md +75 -13
  14. package/docs/training.md +280 -32
  15. package/examples/python/README.md +62 -0
  16. package/examples/python/base_policy_server.py +191 -0
  17. package/examples/python/requirements.txt +2 -0
  18. package/package.json +12 -4
  19. package/src/api/PaddockSimulatorController.js +6 -33
  20. package/src/app/BrowserExpertAdapter.js +76 -9
  21. package/src/app/BrowserExpertExternalRenderer.js +303 -0
  22. package/src/app/F1SimulatorApp.js +291 -1630
  23. package/src/app/banners/bannerFormatters.js +55 -0
  24. package/src/app/banners/raceDataBannerController.js +141 -0
  25. package/src/app/banners/stewardMessageController.js +99 -0
  26. package/src/app/camera/cameraConstants.js +21 -0
  27. package/src/app/camera/cameraController.js +265 -0
  28. package/src/app/camera/cameraGeometry.js +176 -0
  29. package/src/app/domBindings.js +4 -0
  30. package/src/app/readouts/carOverviewRenderer.js +109 -0
  31. package/src/app/readouts/controlStateRenderer.js +37 -0
  32. package/src/app/readouts/raceStatusRenderer.js +133 -0
  33. package/src/app/readouts/readoutFormatters.js +58 -0
  34. package/src/app/readouts/telemetryRenderer.js +113 -0
  35. package/src/app/readouts/timingTowerRenderer.js +177 -0
  36. package/src/app/rendering/appAssets.js +38 -0
  37. package/src/app/rendering/carRenderer.js +286 -0
  38. package/src/app/rendering/displayUtils.js +22 -0
  39. package/src/app/rendering/drsTrailRenderer.js +90 -0
  40. package/src/app/rendering/expertSensorRenderer.js +126 -0
  41. package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
  42. package/src/app/rendering/replayGhostRenderer.js +124 -0
  43. package/src/app/rendering/trackRenderer.js +25 -0
  44. package/src/app/runtime/frameLoop.js +66 -0
  45. package/src/app/runtime/runtimeTiming.js +34 -0
  46. package/src/app/runtime/runtimeVisibility.js +45 -0
  47. package/src/config/defaultOptions.js +20 -0
  48. package/src/config/restartOptions.js +34 -0
  49. package/src/environment/actions.js +13 -5
  50. package/src/environment/controllerLoop.js +261 -0
  51. package/src/environment/episode.js +98 -2
  52. package/src/environment/evaluation.js +54 -32
  53. package/src/environment/index.d.ts +376 -10
  54. package/src/environment/index.js +1 -0
  55. package/src/environment/metrics.js +159 -0
  56. package/src/environment/observationOptions.js +22 -1
  57. package/src/environment/observationVector.js +200 -0
  58. package/src/environment/observations.js +199 -141
  59. package/src/environment/options.js +37 -5
  60. package/src/environment/recorder.js +13 -6
  61. package/src/environment/rewards.js +13 -2
  62. package/src/environment/runtime.js +272 -23
  63. package/src/environment/scenarios.js +57 -9
  64. package/src/environment/sensors/bodySenses.js +23 -0
  65. package/src/environment/sensors/boundarySenses.js +17 -0
  66. package/src/environment/sensors/carRays.js +75 -0
  67. package/src/environment/sensors/contactSenses.js +32 -0
  68. package/src/environment/sensors/index.js +157 -0
  69. package/src/environment/sensors/indexedRayBands.js +103 -0
  70. package/src/environment/sensors/nearbyCars.js +52 -0
  71. package/src/environment/sensors/opponentRadar.js +75 -0
  72. package/src/environment/sensors/rayConfig.js +110 -0
  73. package/src/environment/sensors/rayDefaults.js +6 -0
  74. package/src/environment/sensors/rayGeometry.js +60 -0
  75. package/src/environment/sensors/rayGuards.js +19 -0
  76. package/src/environment/sensors/sensorTargets.js +66 -0
  77. package/src/environment/sensors/surfaceRays.js +360 -0
  78. package/src/environment/sensors/trackRays.js +424 -0
  79. package/src/environment/sensors.js +1 -367
  80. package/src/environment/specs.js +159 -26
  81. package/src/environment/workerProtocol.js +13 -1
  82. package/src/index.d.ts +171 -6
  83. package/src/index.js +6 -32
  84. package/src/rendering/proceduralTrackAsset.js +20 -823
  85. package/src/rendering/renderSnapshot.js +38 -12
  86. package/src/rendering/track/finishLineRenderer.js +34 -0
  87. package/src/rendering/track/gridRenderer.js +47 -0
  88. package/src/rendering/track/offsetStrokeSafety.js +136 -0
  89. package/src/rendering/track/pitLaneRenderer.js +248 -0
  90. package/src/rendering/track/trackMaterialRenderer.js +326 -0
  91. package/src/rendering/track/trackRenderConstants.js +38 -0
  92. package/src/rendering/track/trackRenderGeometry.js +94 -0
  93. package/src/simulation/collisionGeometry.js +1 -1
  94. package/src/simulation/driver/driverControlConstants.js +28 -0
  95. package/src/simulation/driver/driverController.js +47 -0
  96. package/src/simulation/driver/driverInput.js +28 -0
  97. package/src/simulation/driver/driverMath.js +6 -0
  98. package/src/simulation/driver/driverPersonality.js +25 -0
  99. package/src/simulation/driver/edgeRecovery.js +48 -0
  100. package/src/simulation/driver/racingControls.js +399 -0
  101. package/src/simulation/driver/racingLinePlan.js +166 -0
  102. package/src/simulation/driver/recoveryDynamics.js +38 -0
  103. package/src/simulation/driver/rejoinControls.js +213 -0
  104. package/src/simulation/driver/safetyCarControls.js +35 -0
  105. package/src/simulation/driver/trafficScan.js +51 -0
  106. package/src/simulation/driverController.js +4 -619
  107. package/src/simulation/participants/participantInteractions.js +120 -0
  108. package/src/simulation/pit/pitFlow.js +239 -0
  109. package/src/simulation/pit/pitIntent.js +92 -0
  110. package/src/simulation/pit/pitKinematics.js +24 -0
  111. package/src/simulation/pit/pitOccupancy.js +47 -0
  112. package/src/simulation/pit/pitPenaltyService.js +71 -0
  113. package/src/simulation/pit/pitQueue.js +60 -0
  114. package/src/simulation/pit/pitRouteMovement.js +167 -0
  115. package/src/simulation/pit/pitRouting.js +319 -0
  116. package/src/simulation/pit/pitService.js +43 -0
  117. package/src/simulation/pit/pitServiceConstants.js +21 -0
  118. package/src/simulation/pit/pitServiceProfile.js +45 -0
  119. package/src/simulation/pit/pitSnapshots.js +61 -0
  120. package/src/simulation/pit/pitState.js +113 -0
  121. package/src/simulation/pit/pitTireService.js +94 -0
  122. package/src/simulation/race/classification.js +104 -0
  123. package/src/simulation/race/gridPenalties.js +37 -0
  124. package/src/simulation/race/raceControlState.js +38 -0
  125. package/src/simulation/race/raceDistance.js +32 -0
  126. package/src/simulation/race/raceFinish.js +65 -0
  127. package/src/simulation/race/raceLifecycle.js +67 -0
  128. package/src/simulation/race/raceOrder.js +113 -0
  129. package/src/simulation/race/raceProgress.js +129 -0
  130. package/src/simulation/race/raceSetup.js +93 -0
  131. package/src/simulation/race/raceStep.js +80 -0
  132. package/src/simulation/race/redFlag.js +15 -0
  133. package/src/simulation/race/retirements.js +44 -0
  134. package/src/simulation/race/safetyCar.js +92 -0
  135. package/src/simulation/race/stalledDnf.js +77 -0
  136. package/src/simulation/raceSimulation.js +231 -2817
  137. package/src/simulation/replay/replayGhosts.js +128 -0
  138. package/src/simulation/rules/moduleConfig.js +72 -0
  139. package/src/simulation/rules/penaltyConfig.js +99 -0
  140. package/src/simulation/rules/penaltyEvents.js +18 -0
  141. package/src/simulation/rules/penaltyStats.js +26 -0
  142. package/src/simulation/rules/ruleConfigMerge.js +41 -0
  143. package/src/simulation/rules/ruleDefaults.js +85 -0
  144. package/src/simulation/rules/rulesReview.js +144 -0
  145. package/src/simulation/rules/rulesetPresets.js +47 -0
  146. package/src/simulation/rules/trackLimitsSteward.js +1 -1
  147. package/src/simulation/rulesConfig.js +5 -323
  148. package/src/simulation/snapshots/raceSnapshots.js +193 -0
  149. package/src/simulation/timing/drsTiming.js +87 -0
  150. package/src/simulation/timing/gapEstimation.js +38 -0
  151. package/src/simulation/timing/lapTelemetry.js +182 -0
  152. package/src/simulation/timing/raceTiming.js +15 -0
  153. package/src/simulation/timing/sectorPerformance.js +60 -0
  154. package/src/simulation/timing/timingConstants.js +5 -0
  155. package/src/simulation/timing/timingHistory.js +46 -0
  156. package/src/simulation/timing/timingLines.js +79 -0
  157. package/src/simulation/track/centerlineSpline.js +59 -0
  158. package/src/simulation/track/drsZones.js +67 -0
  159. package/src/simulation/track/pitLaneAccess.js +103 -0
  160. package/src/simulation/track/pitLaneLayout.js +395 -0
  161. package/src/simulation/track/pitLaneState.js +160 -0
  162. package/src/simulation/track/proceduralCenterline.js +237 -0
  163. package/src/simulation/track/sampleGeometry.js +49 -0
  164. package/src/simulation/track/spatialQueries.js +154 -0
  165. package/src/simulation/track/startStraight.js +141 -0
  166. package/src/simulation/track/trackConstants.js +109 -0
  167. package/src/simulation/track/trackGenerationOptions.js +283 -0
  168. package/src/simulation/track/trackMath.js +241 -0
  169. package/src/simulation/track/trackModel.js +240 -0
  170. package/src/simulation/track/trackQueryGrid.js +94 -0
  171. package/src/simulation/track/trackQueryIndex.js +659 -0
  172. package/src/simulation/track/trackQueryPitIndex.js +140 -0
  173. package/src/simulation/track/trackQueryScratch.js +46 -0
  174. package/src/simulation/track/trackQueryStats.js +28 -0
  175. package/src/simulation/track/trackSectors.js +19 -0
  176. package/src/simulation/track/trackStatePolicy.js +24 -0
  177. package/src/simulation/track/trackValidation.js +98 -0
  178. package/src/simulation/trackModel.js +1 -1706
  179. package/src/simulation/vehicle/contactResolution.js +181 -0
  180. package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
  181. package/src/simulation/vehicle/pitWheelSurface.js +69 -0
  182. package/src/simulation/vehicle/runoffResponse.js +87 -0
  183. package/src/simulation/vehicle/surfacePriority.js +28 -0
  184. package/src/simulation/vehicle/vehicleGeometry.js +247 -0
  185. package/src/simulation/vehicle/vehicleKinematics.js +37 -0
  186. package/src/simulation/vehicle/vehiclePhysics.js +364 -0
  187. package/src/simulation/vehicle/vehicleSnapshots.js +327 -0
  188. package/src/simulation/vehicle/vehicleState.js +220 -0
  189. package/src/simulation/vehicle/wheelSurface.js +152 -0
  190. package/src/simulation/vehicleGeometry.js +1 -247
  191. package/src/simulation/vehiclePhysics.js +1 -136
  192. package/src/simulation/warmup/runtimeWarmup.js +224 -0
  193. package/src/simulation/wheelSurface.js +1 -338
  194. package/src/styles.css +42 -0
  195. package/src/ui/cameraControlsTemplate.js +20 -0
  196. package/src/ui/carOverviewTemplate.js +33 -0
  197. package/src/ui/componentTemplates.js +14 -360
  198. package/src/ui/raceCanvasTemplate.js +69 -0
  199. package/src/ui/raceControlsTemplate.js +36 -0
  200. package/src/ui/shellTemplate.js +5 -4
  201. package/src/ui/telemetryDrawerTemplate.js +55 -0
  202. package/src/ui/telemetryTemplates.js +136 -0
  203. package/src/ui/timingTowerTemplate.js +34 -0
@@ -0,0 +1,424 @@
1
+ import { nearestTrackState } from '../../simulation/track/trackModel.js';
2
+ import { pitOverrideAllowedForCar } from '../../simulation/track/trackStatePolicy.js';
3
+ import { metersToSimUnits, simUnitsToMeters } from '../../simulation/units.js';
4
+ import {
5
+ ANALYTIC_TRACK_RAY_MAX_CURVATURE,
6
+ DRIVER_RAY_REFINE_STEPS,
7
+ PIT_CONNECTOR_RAY_FALLBACK_METERS,
8
+ TRACK_RAY_REFINE_STEPS,
9
+ TRACK_RAY_STEP_METERS,
10
+ } from './rayDefaults.js';
11
+ import { canUseIndexedRecoveryRayApproximation } from './rayGuards.js';
12
+ import { degreesToRadians, getCarRayOrigin, getCarRayVector, pointOnRay } from './rayGeometry.js';
13
+ import { findIndexedTrackBandBoundaries } from './indexedRayBands.js';
14
+
15
+ export function createTrackRayContext(car, snapshot, origin, precision = 'debug') {
16
+ if (!Array.isArray(snapshot.track?.samples) || snapshot.track.samples.length === 0) {
17
+ return { origin, originState: null, precision };
18
+ }
19
+ return {
20
+ origin,
21
+ originState: nearestRayTrackState(snapshot.track, car, origin, car.progress, precision),
22
+ precision,
23
+ };
24
+ }
25
+
26
+ export function estimateTrackHit(
27
+ car,
28
+ snapshot,
29
+ angleDegrees,
30
+ lengthMeters,
31
+ context = null,
32
+ { precision = 'driver', sharedRayQuery = null } = {},
33
+ ) {
34
+ if (!Array.isArray(snapshot.track?.samples) || snapshot.track.samples.length === 0) {
35
+ return estimateLocalTrackHit(car, snapshot, angleDegrees, lengthMeters);
36
+ }
37
+
38
+ const origin = context?.origin ?? getCarRayOrigin(car);
39
+ const ray = getCarRayVector(car, angleDegrees);
40
+ const maxDistance = metersToSimUnits(lengthMeters);
41
+ const step = metersToSimUnits(TRACK_RAY_STEP_METERS);
42
+ const originState = context?.precision === precision
43
+ ? context.originState
44
+ : nearestRayTrackState(snapshot.track, car, origin, car.progress, precision);
45
+ const includePitLane = Boolean(car.inPitLane || car.pitLanePart || originState.inPitLane);
46
+ const directionalAnalyticHit = canUseDirectionalAnalyticTrackHit({ car, track: snapshot.track, originState, ray, includePitLane })
47
+ ? estimateAnalyticMainTrackHit({
48
+ car,
49
+ track: snapshot.track,
50
+ origin,
51
+ originState,
52
+ ray,
53
+ lengthMeters,
54
+ includePitLane,
55
+ })
56
+ : null;
57
+ if (directionalAnalyticHit) return directionalAnalyticHit;
58
+ if (!usesSampledDriverContract(car, precision)) {
59
+ const indexedHit = estimateIndexedTrackHit({
60
+ car,
61
+ track: snapshot.track,
62
+ origin,
63
+ originState,
64
+ ray,
65
+ lengthMeters,
66
+ includePitLane,
67
+ precision,
68
+ sharedRayQuery,
69
+ });
70
+ if (indexedHit) return indexedHit;
71
+ }
72
+ const analyticHit = estimateAnalyticMainTrackHit({
73
+ car,
74
+ track: snapshot.track,
75
+ origin,
76
+ originState,
77
+ ray,
78
+ lengthMeters,
79
+ includePitLane,
80
+ });
81
+ if (analyticHit) return analyticHit;
82
+ let previousDistance = 0;
83
+ let previousInside = null;
84
+
85
+ for (let distance = 0; distance <= maxDistance; distance += step) {
86
+ const state = nearestRayTrackState(snapshot.track, car, pointOnRay(origin, ray, distance), car.progress, precision);
87
+ const inside = isInsideTrackBorder(state, snapshot.track, includePitLane);
88
+ if (previousInside == null) {
89
+ previousInside = inside;
90
+ previousDistance = distance;
91
+ continue;
92
+ }
93
+
94
+ if (inside !== previousInside) {
95
+ const kind = previousInside ? 'exit' : 'entry';
96
+ const hitDistance = refineTrackTransitionDistance(
97
+ snapshot.track,
98
+ car,
99
+ origin,
100
+ ray,
101
+ car.progress,
102
+ previousDistance,
103
+ distance,
104
+ kind,
105
+ includePitLane,
106
+ refineStepsForPrecision(precision),
107
+ precision,
108
+ );
109
+ return {
110
+ hit: true,
111
+ distanceMeters: simUnitsToMeters(hitDistance),
112
+ kind,
113
+ };
114
+ }
115
+ previousDistance = distance;
116
+ previousInside = inside;
117
+ }
118
+
119
+ return createTrackMiss(lengthMeters);
120
+ }
121
+
122
+ export function createTrackMiss(lengthMeters) {
123
+ return {
124
+ hit: false,
125
+ distanceMeters: lengthMeters,
126
+ kind: null,
127
+ };
128
+ }
129
+
130
+ function estimateAnalyticMainTrackHit({ car, track, origin, originState, ray, lengthMeters, includePitLane }) {
131
+ if (includePitLane || (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState))) return null;
132
+ if (!canUseIndexedRecoveryRayApproximation(track, originState)) return null;
133
+ if (Math.abs(originState.curvature ?? 0) > ANALYTIC_TRACK_RAY_MAX_CURVATURE) return null;
134
+
135
+ const lateral = ray.x * originState.normalX + ray.y * originState.normalY;
136
+ if (Math.abs(lateral) < 0.08) return createTrackMiss(lengthMeters);
137
+
138
+ const trackHalfWidth = track.width / 2;
139
+ const offset = originState.signedOffset ?? car.signedOffset ?? 0;
140
+ const inside = Math.abs(offset) <= trackHalfWidth;
141
+ const targetEdge = getLocalTrackTransitionTarget({
142
+ inside,
143
+ offsetMeters: offset,
144
+ lateral,
145
+ trackHalfWidthMeters: trackHalfWidth,
146
+ });
147
+ if (targetEdge == null) return createTrackMiss(lengthMeters);
148
+
149
+ const distance = (targetEdge - offset) / lateral;
150
+ const maxDistance = metersToSimUnits(lengthMeters);
151
+ if (distance < 0 || distance > maxDistance) return createTrackMiss(lengthMeters);
152
+
153
+ return {
154
+ hit: true,
155
+ distanceMeters: simUnitsToMeters(distance),
156
+ kind: inside ? 'exit' : 'entry',
157
+ };
158
+ }
159
+
160
+ function canUseDirectionalAnalyticTrackHit({ car, track, originState, ray, includePitLane }) {
161
+ if (includePitLane || (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState))) return false;
162
+ if (!canUseIndexedRecoveryRayApproximation(track, originState)) return false;
163
+ if (Math.abs(originState.curvature ?? 0) > ANALYTIC_TRACK_RAY_MAX_CURVATURE) return false;
164
+
165
+ const lateral = ray.x * originState.normalX + ray.y * originState.normalY;
166
+ return Math.abs(lateral) >= 0.08;
167
+ }
168
+
169
+ function estimateIndexedTrackHit({ car, track, origin, originState, ray, lengthMeters, includePitLane, precision, sharedRayQuery }) {
170
+ if (includePitLane || (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState))) return null;
171
+ if (!canUseIndexedRecoveryRayApproximation(track, originState)) return null;
172
+ const trackHalfWidth = track.width / 2;
173
+ const inside = Math.abs(originState.signedOffset ?? 0) <= trackHalfWidth;
174
+ const boundaries = findIndexedTrackBandBoundaries(
175
+ track,
176
+ origin,
177
+ ray,
178
+ lengthMeters,
179
+ trackHalfWidth,
180
+ trackHalfWidth + (track.kerbWidth ?? 0),
181
+ sharedRayQuery,
182
+ );
183
+ if (!boundaries.available) return null;
184
+ if (boundaries.trackEdgeDistance == null) return null;
185
+ const kind = inside ? 'exit' : 'entry';
186
+ const hitDistance = validatedIndexedTransitionDistance({
187
+ track,
188
+ car,
189
+ origin,
190
+ ray,
191
+ progressHint: car.progress,
192
+ distance: boundaries.trackEdgeDistance,
193
+ maxDistance: metersToSimUnits(lengthMeters),
194
+ kind,
195
+ includePitLane,
196
+ precision,
197
+ originInside: inside,
198
+ });
199
+ if (hitDistance == null) return null;
200
+ return {
201
+ hit: true,
202
+ distanceMeters: simUnitsToMeters(hitDistance),
203
+ kind,
204
+ };
205
+ }
206
+
207
+ function usesMainTrackOnlyRays(car) {
208
+ return car?.interaction?.profile === 'batch-training';
209
+ }
210
+
211
+ function isNearPitConnector(track, state) {
212
+ const pitLane = track.pitLane;
213
+ if (!pitLane?.enabled || !Number.isFinite(state?.distance)) return false;
214
+ const window = metersToSimUnits(PIT_CONNECTOR_RAY_FALLBACK_METERS);
215
+ const entryDistance = pitLane.entry?.trackDistance ?? pitLane.entry?.distanceFromStart;
216
+ const exitDistance = pitLane.exit?.trackDistance ?? pitLane.exit?.distanceFromStart;
217
+ return wrappedTrackDistance(state.distance, entryDistance, track.length) <= window ||
218
+ wrappedTrackDistance(state.distance, exitDistance, track.length) <= window;
219
+ }
220
+
221
+ function wrappedTrackDistance(first, second, totalLength) {
222
+ if (!Number.isFinite(first) || !Number.isFinite(second) || !Number.isFinite(totalLength) || totalLength <= 0) {
223
+ return Infinity;
224
+ }
225
+ const delta = Math.abs(first - second);
226
+ return Math.min(delta, totalLength - delta);
227
+ }
228
+
229
+ function estimateLocalTrackHit(car, snapshot, angleDegrees, lengthMeters) {
230
+ const trackHalfWidthMeters = simUnitsToMeters(snapshot.track.width / 2);
231
+ const offsetMeters = simUnitsToMeters(car.signedOffset ?? 0);
232
+ const lateral = Math.sin(degreesToRadians(angleDegrees));
233
+ if (Math.abs(lateral) < 0.08) {
234
+ return createTrackMiss(lengthMeters);
235
+ }
236
+
237
+ const inside = Math.abs(offsetMeters) <= trackHalfWidthMeters;
238
+ const targetEdge = getLocalTrackTransitionTarget({ inside, offsetMeters, lateral, trackHalfWidthMeters });
239
+ if (targetEdge == null) return createTrackMiss(lengthMeters);
240
+
241
+ const distanceMeters = (targetEdge - offsetMeters) / lateral;
242
+ if (distanceMeters < 0 || distanceMeters > lengthMeters) return createTrackMiss(lengthMeters);
243
+
244
+ return {
245
+ hit: true,
246
+ distanceMeters,
247
+ kind: inside ? 'exit' : 'entry',
248
+ };
249
+ }
250
+
251
+ function getLocalTrackTransitionTarget({ inside, offsetMeters, lateral, trackHalfWidthMeters }) {
252
+ if (inside) return lateral > 0 ? trackHalfWidthMeters : -trackHalfWidthMeters;
253
+ if (offsetMeters > trackHalfWidthMeters) return lateral < 0 ? trackHalfWidthMeters : null;
254
+ if (offsetMeters < -trackHalfWidthMeters) return lateral > 0 ? -trackHalfWidthMeters : null;
255
+ return null;
256
+ }
257
+
258
+ function refineTrackTransitionDistance(
259
+ track,
260
+ car,
261
+ origin,
262
+ ray,
263
+ progressHint,
264
+ lowDistance,
265
+ highDistance,
266
+ kind,
267
+ includePitLane = true,
268
+ refineSteps = TRACK_RAY_REFINE_STEPS,
269
+ precision = 'debug',
270
+ ) {
271
+ let low = lowDistance;
272
+ let high = highDistance;
273
+ for (let index = 0; index < refineSteps; index += 1) {
274
+ const middle = (low + high) / 2;
275
+ const state = nearestRayTrackState(track, car, pointOnRay(origin, ray, middle), progressHint, precision);
276
+ const inside = isInsideTrackBorder(state, track, includePitLane);
277
+ if (kind === 'entry') {
278
+ if (inside) high = middle;
279
+ else low = middle;
280
+ } else if (inside) {
281
+ low = middle;
282
+ } else {
283
+ high = middle;
284
+ }
285
+ }
286
+ return high;
287
+ }
288
+
289
+ function nearestRayTrackState(track, car, point, progressHint, precision = 'debug') {
290
+ const useBatchTrainingIndexMode = usesMainTrackOnlyRays(car);
291
+ return nearestTrackState(track, point, progressHint, {
292
+ allowPitOverride: pitOverrideAllowedForCar(car),
293
+ indexMode: usesSampledDriverContract(car, precision) ? 'legacy' : useBatchTrainingIndexMode ? 'sample' : undefined,
294
+ hintMaxDistance: useBatchTrainingIndexMode ? Infinity : undefined,
295
+ });
296
+ }
297
+
298
+ function usesSampledDriverContract(car, precision) {
299
+ return precision === 'driver' && !usesMainTrackOnlyRays(car);
300
+ }
301
+
302
+ function refineStepsForPrecision(precision) {
303
+ return precision === 'debug' ? TRACK_RAY_REFINE_STEPS : DRIVER_RAY_REFINE_STEPS;
304
+ }
305
+
306
+ function isInsideTrackBorder(state, track, includePitLane = true) {
307
+ if (includePitLane && state.inPitLane) return true;
308
+ return state.crossTrackError <= track.width / 2;
309
+ }
310
+
311
+ function validatedIndexedTransitionDistance({
312
+ track,
313
+ car,
314
+ origin,
315
+ ray,
316
+ progressHint,
317
+ distance,
318
+ maxDistance,
319
+ kind,
320
+ includePitLane,
321
+ precision = 'driver',
322
+ originInside = kind === 'exit',
323
+ }) {
324
+ const step = metersToSimUnits(TRACK_RAY_STEP_METERS);
325
+ const refineSteps = refineStepsForPrecision(precision);
326
+ if (refineSteps <= 0 && usesMainTrackOnlyRays(car)) return distance;
327
+ if (refineSteps <= 0) {
328
+ return sampledIndexedTransitionDistance({
329
+ track,
330
+ car,
331
+ origin,
332
+ ray,
333
+ progressHint,
334
+ distance,
335
+ maxDistance,
336
+ kind,
337
+ includePitLane,
338
+ });
339
+ }
340
+
341
+ const windows = [step * 1.5, step * 8];
342
+
343
+ for (const searchWindow of windows) {
344
+ const low = Math.max(0, distance - searchWindow);
345
+ const high = Math.min(maxDistance, distance + searchWindow);
346
+ if (high <= low) continue;
347
+ const previousInside = validatedInsideTrackBorder(track, car, origin, ray, low, progressHint, includePitLane);
348
+ const inside = validatedInsideTrackBorder(track, car, origin, ray, high, progressHint, includePitLane);
349
+ const matched = kind === 'entry'
350
+ ? !previousInside && inside
351
+ : previousInside && !inside;
352
+ if (matched) {
353
+ if (refineSteps <= 0) return distance;
354
+ return refineValidatedTransitionDistance(
355
+ track,
356
+ car,
357
+ origin,
358
+ ray,
359
+ progressHint,
360
+ low,
361
+ high,
362
+ kind,
363
+ includePitLane,
364
+ refineSteps,
365
+ );
366
+ }
367
+ }
368
+
369
+ return null;
370
+ }
371
+
372
+ function sampledIndexedTransitionDistance({
373
+ track,
374
+ car,
375
+ origin,
376
+ ray,
377
+ progressHint,
378
+ distance,
379
+ maxDistance,
380
+ kind,
381
+ includePitLane,
382
+ }) {
383
+ const step = metersToSimUnits(TRACK_RAY_STEP_METERS);
384
+ const firstIndex = Math.max(1, Math.floor(Math.max(0, distance - step * 1.5) / step));
385
+ const lastIndex = Math.ceil(Math.min(maxDistance, distance + step * 8) / step);
386
+ for (let index = firstIndex; index <= lastIndex; index += 1) {
387
+ const high = Math.min(maxDistance, index * step);
388
+ const low = Math.max(0, high - step);
389
+ if (high <= low) continue;
390
+ const previousInside = validatedInsideTrackBorder(track, car, origin, ray, low, progressHint, includePitLane);
391
+ const inside = validatedInsideTrackBorder(track, car, origin, ray, high, progressHint, includePitLane);
392
+ const matched = kind === 'entry'
393
+ ? !previousInside && inside
394
+ : previousInside && !inside;
395
+ if (matched) return high;
396
+ }
397
+ return null;
398
+ }
399
+
400
+ function refineValidatedTransitionDistance(track, car, origin, ray, progressHint, lowDistance, highDistance, kind, includePitLane, refineSteps) {
401
+ let low = lowDistance;
402
+ let high = highDistance;
403
+ for (let index = 0; index < refineSteps; index += 1) {
404
+ const middle = (low + high) / 2;
405
+ const inside = validatedInsideTrackBorder(track, car, origin, ray, middle, progressHint, includePitLane);
406
+ if (kind === 'entry') {
407
+ if (inside) high = middle;
408
+ else low = middle;
409
+ } else if (inside) {
410
+ low = middle;
411
+ } else {
412
+ high = middle;
413
+ }
414
+ }
415
+ return high;
416
+ }
417
+
418
+ function validatedInsideTrackBorder(track, car, origin, ray, distance, progressHint, includePitLane) {
419
+ const state = nearestTrackState(track, pointOnRay(origin, ray, distance), progressHint, {
420
+ allowPitOverride: pitOverrideAllowedForCar(car),
421
+ indexMode: 'legacy',
422
+ });
423
+ return isInsideTrackBorder(state, track, includePitLane);
424
+ }