@inventure71/paddockjs 4.2.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 (249) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/INSTALL_AND_UPDATE.md +23 -4
  3. package/README.md +31 -2
  4. package/docs/advanced_ai_pace.md +131 -0
  5. package/docs/advanced_physics_rebuild.md +166 -0
  6. package/docs/architecture.md +109 -30
  7. package/docs/cleanup_coverage.md +284 -0
  8. package/docs/component_inventory.md +31 -15
  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/grid_walk.md +80 -0
  16. package/docs/index.md +1 -0
  17. package/docs/keyboard_driving_fix.md +118 -0
  18. package/docs/limit_driving_2026-10-14.md +88 -0
  19. package/docs/maintainability_audit_2026-09-08.md +146 -0
  20. package/docs/racing_ai_improvements.md +145 -0
  21. package/docs/rules.md +43 -13
  22. package/docs/sense_contract.md +4 -1
  23. package/docs/system_specs.md +36 -11
  24. package/docs/testing-reliability-guide.md +5 -0
  25. package/docs/v11_improvements.md +108 -0
  26. package/package.json +23 -13
  27. package/src/api/MountedSimulatorComponents.js +101 -0
  28. package/src/api/PaddockSimulatorController.js +56 -81
  29. package/src/app/BrowserExpertAdapter.js +0 -6
  30. package/src/app/BrowserExpertExternalRenderer.js +72 -50
  31. package/src/app/ExternalTrackSnapshot.js +50 -0
  32. package/src/app/F1SimulatorApp.js +242 -297
  33. package/src/app/HostLifecycleEmitter.js +93 -0
  34. package/src/app/banners/raceDataBannerController.js +9 -4
  35. package/src/app/camera/cameraConstants.js +1 -1
  36. package/src/app/camera/cameraController.js +10 -13
  37. package/src/app/camera/cameraGeometry.js +1 -1
  38. package/src/app/controls/SimulatorViewControls.js +45 -0
  39. package/src/app/controls/workbenchBindings.js +64 -0
  40. package/src/app/controls/workbenchDisclosure.js +41 -0
  41. package/src/app/domBindings.js +21 -13
  42. package/src/app/gridWalk/GridWalkController.js +180 -0
  43. package/src/app/gridWalk/gridWalkCamera.js +88 -0
  44. package/src/app/gridWalk/gridWalkControls.js +24 -0
  45. package/src/app/gridWalk/gridWalkCrew.js +246 -0
  46. package/src/app/gridWalk/gridWalkLabels.js +104 -0
  47. package/src/app/gridWalk/gridWalkLayout.js +46 -0
  48. package/src/app/gridWalk/gridWalkNavigation.js +99 -0
  49. package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
  50. package/src/app/gridWalk/gridWalkSession.js +50 -0
  51. package/src/app/layoutSupport.js +2 -0
  52. package/src/app/raceOverlayClearanceSupport.js +21 -7
  53. package/src/app/readouts/carOverviewRenderer.js +9 -4
  54. package/src/app/readouts/readoutFormatters.js +1 -8
  55. package/src/app/readouts/telemetryRenderer.js +1 -3
  56. package/src/app/readouts/timingTowerRenderer.js +48 -50
  57. package/src/app/rendering/appAssets.js +8 -10
  58. package/src/app/rendering/carRenderer.js +27 -79
  59. package/src/app/rendering/displayUtils.js +0 -6
  60. package/src/app/rendering/expertSensorRenderer.js +1 -1
  61. package/src/app/rendering/replayGhostRenderer.js +4 -1
  62. package/src/app/rendering/trackRenderer.js +2 -2
  63. package/src/app/runtime/frameLoop.js +18 -17
  64. package/src/app/runtime/pixiStartupLifecycle.js +94 -0
  65. package/src/app/runtime/runtimeTiming.js +4 -1
  66. package/src/app/runtime/runtimeVisibility.js +7 -2
  67. package/src/config/cssValues.js +1 -1
  68. package/src/config/defaultOptions.js +12 -173
  69. package/src/config/restartOptions.js +1 -0
  70. package/src/config/runtimeSeed.js +10 -0
  71. package/src/config/simulatorDefaults.js +106 -0
  72. package/src/config/telemetryModules.js +20 -0
  73. package/src/config/theme/cssTokens.js +85 -0
  74. package/src/config/theme/themeCss.js +72 -0
  75. package/src/config/theme/themeInputs.js +155 -0
  76. package/src/config/theme/themeResolver.js +172 -0
  77. package/src/config/theme/themeSelectors.js +69 -0
  78. package/src/config/theme/themeTokens.js +257 -0
  79. package/src/config/theme/themeUtils.js +15 -0
  80. package/src/config/themeOptions.js +14 -830
  81. package/src/config/timingGapMode.js +0 -2
  82. package/src/config/uiOptions.js +53 -0
  83. package/src/data/championship.js +16 -158
  84. package/src/data/championshipDefaults.js +87 -0
  85. package/src/data/driverData.js +10 -20
  86. package/src/data/entryIdentity.js +67 -0
  87. package/src/data/entryPresentation.js +23 -0
  88. package/src/data/entryTypes.d.ts +160 -0
  89. package/src/data/index.d.ts +13 -259
  90. package/src/data/ratingScale.js +14 -0
  91. package/src/data/teamTypes.d.ts +23 -0
  92. package/src/data/trackTypes.d.ts +101 -0
  93. package/src/data/vehicleData.js +12 -23
  94. package/src/environment/actions.js +3 -2
  95. package/src/environment/controllerLoop.js +57 -44
  96. package/src/environment/controllerScheduler.js +30 -0
  97. package/src/environment/driverObservations.js +260 -0
  98. package/src/environment/driverReset.js +51 -0
  99. package/src/environment/episode.js +0 -1
  100. package/src/environment/evaluation.js +6 -90
  101. package/src/environment/evaluationTracker.js +100 -0
  102. package/src/environment/events.js +23 -0
  103. package/src/environment/index.d.ts +21 -140
  104. package/src/environment/metrics.js +3 -17
  105. package/src/environment/observationSchema.js +104 -0
  106. package/src/environment/observationVector.js +16 -115
  107. package/src/environment/observations.js +16 -316
  108. package/src/environment/options.js +5 -24
  109. package/src/environment/renderFrames.js +21 -0
  110. package/src/environment/resetOptions.js +41 -0
  111. package/src/environment/results.js +125 -0
  112. package/src/environment/rewards.js +10 -16
  113. package/src/environment/runtime.js +17 -300
  114. package/src/environment/scenarioOptions.js +90 -0
  115. package/src/environment/scenarioPlacements.js +70 -0
  116. package/src/environment/scenarioPresets.js +78 -0
  117. package/src/environment/scenarios.js +5 -236
  118. package/src/environment/scratch.js +18 -0
  119. package/src/environment/sensorOptions.js +20 -0
  120. package/src/environment/sensors/index.js +2 -556
  121. package/src/environment/sensors/indexedRayBands.js +0 -18
  122. package/src/environment/sensors/nearbyCars.js +18 -15
  123. package/src/environment/sensors/opponentRadar.js +3 -2
  124. package/src/environment/sensors/pitConnectorProximity.js +20 -0
  125. package/src/environment/sensors/rayBandTrace.js +1 -37
  126. package/src/environment/sensors/rayBatch.js +81 -0
  127. package/src/environment/sensors/rayConfig.js +26 -0
  128. package/src/environment/sensors/rayGeometry.js +0 -8
  129. package/src/environment/sensors/rayGuards.js +0 -6
  130. package/src/environment/sensors/rayOutputs.js +75 -0
  131. package/src/environment/sensors/rayQuery.js +110 -0
  132. package/src/environment/sensors/rayScratch.js +146 -0
  133. package/src/environment/sensors/sensorTargets.js +12 -4
  134. package/src/environment/sensors/surfaceRays.js +6 -61
  135. package/src/environment/sensors/trackRays.js +7 -80
  136. package/src/environment/simulationSetup.js +30 -0
  137. package/src/environment/specs.js +15 -109
  138. package/src/index.d.ts +34 -259
  139. package/src/index.js +28 -6
  140. package/src/placeholder/index.js +1 -8
  141. package/src/rendering/displayLifecycle.js +6 -0
  142. package/src/rendering/proceduralTrackAsset.js +1 -1
  143. package/src/rendering/renderSnapshot.js +3 -0
  144. package/src/rendering/track/finishLineRenderer.js +1 -1
  145. package/src/rendering/track/gridRenderer.js +1 -1
  146. package/src/rendering/track/offsetStrokeSafety.js +2 -2
  147. package/src/rendering/track/pitLaneRenderer.js +1 -1
  148. package/src/rendering/track/trackMaterialRenderer.js +4 -4
  149. package/src/rendering/track/trackRenderConstants.js +0 -1
  150. package/src/rendering/track/trackRenderGeometry.js +1 -7
  151. package/src/simulation/collisionGeometry.js +5 -11
  152. package/src/simulation/driver/advancedPathControls.js +53 -0
  153. package/src/simulation/driver/advancedRacePace.js +112 -0
  154. package/src/simulation/driver/advancedRacingControls.js +61 -0
  155. package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
  156. package/src/simulation/driver/advancedRacingSteering.js +76 -0
  157. package/src/simulation/driver/advancedRacingTiming.js +16 -0
  158. package/src/simulation/driver/arcadeRacePace.js +83 -0
  159. package/src/simulation/driver/arcadeRacingControls.js +111 -0
  160. package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
  161. package/src/simulation/driver/driverControlConstants.js +0 -2
  162. package/src/simulation/driver/driverController.js +14 -3
  163. package/src/simulation/driver/edgeRecovery.js +0 -1
  164. package/src/simulation/driver/racingControls.js +10 -406
  165. package/src/simulation/driver/racingDriverProfile.js +48 -0
  166. package/src/simulation/driver/racingLinePlan.js +48 -15
  167. package/src/simulation/driver/rejoinControls.js +58 -111
  168. package/src/simulation/driver/safetyCarControls.js +12 -0
  169. package/src/simulation/driver/trafficScan.js +89 -6
  170. package/src/simulation/pit/pitCarPlacement.js +29 -0
  171. package/src/simulation/pit/pitFlow.js +8 -30
  172. package/src/simulation/pit/pitIntent.js +4 -3
  173. package/src/simulation/pit/pitOccupancy.js +2 -2
  174. package/src/simulation/pit/pitQueue.js +2 -18
  175. package/src/simulation/pit/pitRouteMovement.js +1 -36
  176. package/src/simulation/pit/pitRouting.js +5 -81
  177. package/src/simulation/pit/pitService.js +3 -32
  178. package/src/simulation/pit/pitServiceConstants.js +1 -0
  179. package/src/simulation/pit/pitServiceProgress.js +34 -0
  180. package/src/simulation/pit/pitSnapshots.js +8 -4
  181. package/src/simulation/pit/pitState.js +1 -5
  182. package/src/simulation/race/classification.js +1 -8
  183. package/src/simulation/race/raceFinish.js +3 -8
  184. package/src/simulation/race/raceOrder.js +2 -13
  185. package/src/simulation/race/raceProgress.js +6 -20
  186. package/src/simulation/race/raceSetup.js +1 -1
  187. package/src/simulation/race/raceStep.js +1 -1
  188. package/src/simulation/race/redFlag.js +2 -4
  189. package/src/simulation/race/safetyCar.js +3 -8
  190. package/src/simulation/race/stalledDnf.js +2 -4
  191. package/src/simulation/raceSimulation.js +6 -38
  192. package/src/simulation/replay/replayGhosts.js +44 -32
  193. package/src/simulation/rules/collisionSteward.js +0 -14
  194. package/src/simulation/rules/penaltyActions.js +37 -0
  195. package/src/simulation/rules/penaltyLedger.js +8 -1
  196. package/src/simulation/rules/rulesReview.js +1 -7
  197. package/src/simulation/simMath.js +4 -0
  198. package/src/simulation/snapshots/raceSnapshots.js +30 -11
  199. package/src/simulation/timing/drsState.js +8 -0
  200. package/src/simulation/timing/drsTiming.js +2 -4
  201. package/src/simulation/timing/lapTelemetry.js +7 -4
  202. package/src/simulation/timing/sectorPerformance.js +3 -3
  203. package/src/simulation/timing/timingLines.js +12 -6
  204. package/src/simulation/track/pitLaneAccess.js +2 -2
  205. package/src/simulation/track/pitLaneLayout.js +4 -135
  206. package/src/simulation/track/pitLaneModel.js +70 -0
  207. package/src/simulation/track/pitLaneState.js +0 -1
  208. package/src/simulation/track/pitServiceLayout.js +121 -0
  209. package/src/simulation/track/proceduralTrack.js +77 -0
  210. package/src/simulation/track/spatialQueries.js +1 -25
  211. package/src/simulation/track/trackBuilder.js +54 -0
  212. package/src/simulation/track/trackConstants.js +0 -22
  213. package/src/simulation/track/trackMath.js +4 -29
  214. package/src/simulation/track/trackModel.js +4 -241
  215. package/src/simulation/track/trackModelCache.js +105 -0
  216. package/src/simulation/track/trackNearestQueries.js +1218 -0
  217. package/src/simulation/track/trackQueryConstants.js +3 -0
  218. package/src/simulation/track/trackQueryConstruction.js +368 -0
  219. package/src/simulation/track/trackQueryIndex.js +12 -1933
  220. package/src/simulation/track/trackQuerySegments.js +174 -0
  221. package/src/simulation/track/trackQueryStats.js +12 -0
  222. package/src/simulation/track/trackRayQueries.js +165 -0
  223. package/src/simulation/vehicle/advancedContactResponse.js +89 -0
  224. package/src/simulation/vehicle/advancedTireForces.js +92 -0
  225. package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
  226. package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
  227. package/src/simulation/vehicle/contactResolution.js +20 -6
  228. package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
  229. package/src/simulation/vehicle/pitWheelSurface.js +0 -12
  230. package/src/simulation/vehicle/runoffResponse.js +2 -4
  231. package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
  232. package/src/simulation/vehicle/vehicleGeometry.js +0 -56
  233. package/src/simulation/vehicle/vehicleKinematics.js +10 -0
  234. package/src/simulation/vehicle/vehiclePhysics.js +35 -194
  235. package/src/simulation/vehicle/vehicleSnapshots.js +13 -4
  236. package/src/simulation/vehicle/vehicleState.js +2 -1
  237. package/src/simulation/vehicle/wheelSurface.js +13 -432
  238. package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
  239. package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
  240. package/src/styles.css +173 -19
  241. package/src/ui/bannerTemplates.js +4 -2
  242. package/src/ui/cameraControlsTemplate.js +4 -2
  243. package/src/ui/gridWalkTemplate.js +29 -0
  244. package/src/ui/htmlEscaping.js +8 -0
  245. package/src/ui/shellTemplate.js +3 -0
  246. package/src/ui/telemetryDrawerTemplate.js +4 -2
  247. package/src/ui/telemetryTemplates.js +3 -22
  248. package/src/ui/templateUtils.js +2 -8
  249. package/src/ui/viewControlsTemplate.js +21 -0
@@ -1,87 +1,19 @@
1
- import { ensureOrientedRectCorners, getCurrentVehicleGeometryState } from './vehicleGeometry.js';
1
+ import { getCurrentVehicleGeometryState } from './vehicleGeometry.js';
2
+ import { analyticWheelProjectedHalfWidth, writeAnalyticWheelState, writeAnalyticWheelStateWithProjectedHalfWidth } from './mainTrackWheelSurface.js';
3
+ import { canUseAnalyticPitWheels, canSkipConnectorPitChecks, isNearPitConnector, writeAnalyticPitWheelState } from './pitWheelSurface.js';
4
+ import { cacheMatchesCenterState, writeCachedCenterState, canRefreshAutomaticCenterStateFromTrackState } from './wheelSurfaceCache.js';
5
+ import { resetWheelSummary, prepareWheelSummary, recordWheelSummary, finishWheelSummary } from './wheelSurfaceSummary.js';
2
6
  import {
3
- analyticWheelProjectedHalfWidth,
4
- wheelFullyOutside,
5
- writeAnalyticWheelState,
6
- writeAnalyticWheelStateWithProjectedHalfWidth,
7
- } from './mainTrackWheelSurface.js';
8
- import {
9
- canUseAnalyticPitWheels,
10
- canSkipConnectorPitChecks,
11
- isNearPitConnector,
12
- pitPatchInsideRoad,
13
- writeAnalyticPitWheelState,
14
- } from './pitWheelSurface.js';
15
- import { getEffectiveSurface, priority, worstState } from './surfacePriority.js';
16
- import {
17
- nearestTrackStateForCar,
18
- pitOverrideAllowedForCar,
19
- queryHintedTrackStateForCar,
20
- queryLocalSegmentTrackProjectionsForCar,
21
- queryLocalSegmentTrackStateForCar,
22
- queryLocalSegmentTrackStatesForCar,
23
- queryRunoffTrackStateForCar,
24
- writeLocalSegmentTrackStatesFromProjections,
25
- } from '../track/trackStatePolicy.js';
7
+ connectorWheelCenterStates,
8
+ connectorWheelCenterProjections,
9
+ canUseStraightConnectorMainTrackAnalytic,
10
+ writeConnectorWheelStatesWithSummary,
11
+ writeNearestSampledWheelStatesWithSummary,
12
+ } from './sampledWheelSurface.js';
13
+ import { nearestTrackStateForCar, pitOverrideAllowedForCar, writeLocalSegmentTrackStatesFromProjections } from '../track/trackStatePolicy.js';
26
14
 
27
15
  export { getEffectiveSurface } from './surfacePriority.js';
28
-
29
- const STRAIGHT_CONNECTOR_CURVATURE_EPSILON = 1e-8;
30
- const MAIN_TRACK_PATCH_MARGIN = 0.001;
31
-
32
- function usesAutomaticCenterState(centerState, cacheAsAuto = false) {
33
- return cacheAsAuto || centerState == null;
34
- }
35
-
36
- function cacheMatchesCenterState(cache, centerState, cacheAsAuto = false) {
37
- const automatic = usesAutomaticCenterState(centerState, cacheAsAuto);
38
- if ((cache.centerStateAuto ?? true) !== automatic) return false;
39
- if (automatic) return true;
40
- return cache.centerStateDistance === centerState.distance &&
41
- cache.centerStateSignedOffset === centerState.signedOffset &&
42
- cache.centerStateCrossTrackError === centerState.crossTrackError &&
43
- cache.centerStateSurface === (centerState.surface ?? null) &&
44
- cache.centerStateInPitLane === Boolean(centerState.inPitLane) &&
45
- cache.centerStatePitLanePart === (centerState.pitLanePart ?? null) &&
46
- cache.centerStatePitBoxId === (centerState.pitBoxId ?? null);
47
- }
48
-
49
- function writeCachedCenterState(cache, centerState, cacheAsAuto = false) {
50
- const automatic = usesAutomaticCenterState(centerState, cacheAsAuto);
51
- cache.centerStateAuto = automatic;
52
- if (automatic) {
53
- cache.centerStateDistance = null;
54
- cache.centerStateSignedOffset = null;
55
- cache.centerStateCrossTrackError = null;
56
- cache.centerStateSurface = null;
57
- cache.centerStateInPitLane = false;
58
- cache.centerStatePitLanePart = null;
59
- cache.centerStatePitBoxId = null;
60
- return cache;
61
- }
62
- cache.centerStateDistance = centerState.distance;
63
- cache.centerStateSignedOffset = centerState.signedOffset;
64
- cache.centerStateCrossTrackError = centerState.crossTrackError;
65
- cache.centerStateSurface = centerState.surface ?? null;
66
- cache.centerStateInPitLane = Boolean(centerState.inPitLane);
67
- cache.centerStatePitLanePart = centerState.pitLanePart ?? null;
68
- cache.centerStatePitBoxId = centerState.pitBoxId ?? null;
69
- return cache;
70
- }
71
-
72
- function canRefreshAutomaticCenterStateFromTrackState(car, track) {
73
- const trackState = car?.trackState;
74
- if (!trackState || trackState.inPitLane) return false;
75
- if (
76
- car?.trackStatePoseX !== car?.x ||
77
- car?.trackStatePoseY !== car?.y ||
78
- car?.trackStatePoseHeading !== car?.heading
79
- ) {
80
- return false;
81
- }
82
- const mainRoadEdge = track.width / 2 + (track.kerbWidth ?? 0);
83
- return Number.isFinite(trackState.crossTrackError) && trackState.crossTrackError <= mainRoadEdge;
84
- }
16
+ export { isWholeCarOutsideTrackLimits } from './wheelSurfaceSummary.js';
85
17
 
86
18
  function writeRepresentativeTrackState(target, centerState, effectiveSurface, representative, onTrack, inPitLane) {
87
19
  target.segmentId = centerState.segmentId;
@@ -137,287 +69,6 @@ function createWheelTargets(count) {
137
69
  return wheels;
138
70
  }
139
71
 
140
- function writeFullWheelState(target, patch, sampledStates, trackLimit) {
141
- const state = worstState(sampledStates);
142
- let minimumSignedOffset = Infinity;
143
- let maximumSignedOffset = -Infinity;
144
- for (let index = 0; index < sampledStates.length; index += 1) {
145
- const signedOffset = sampledStates[index].signedOffset;
146
- if (signedOffset < minimumSignedOffset) minimumSignedOffset = signedOffset;
147
- if (signedOffset > maximumSignedOffset) maximumSignedOffset = signedOffset;
148
- }
149
- const outside = wheelFullyOutside(sampledStates, trackLimit);
150
-
151
- target.id = patch.id;
152
- target.x = patch.center.x;
153
- target.y = patch.center.y;
154
- target.signedOffset = state.signedOffset;
155
- target.crossTrackError = state.crossTrackError;
156
- target.surface = state.surface;
157
- target.onTrack = Boolean(state.onTrack);
158
- target.inPitLane = Boolean(state.inPitLane);
159
- target.pitLanePart = state.pitLanePart ?? null;
160
- target.pitBoxId = state.pitBoxId ?? null;
161
- target.minimumSignedOffset = minimumSignedOffset;
162
- target.maximumSignedOffset = maximumSignedOffset;
163
- target.fullyOutsideWhiteLine = outside.fullyOutsideWhiteLine;
164
- target.outsideSide = outside.outsideSide;
165
- target.sampledStates = sampledStates;
166
- return target;
167
- }
168
-
169
- function resetWheelSummary(summary) {
170
- const trackLimits = summary.trackLimits ?? {
171
- violating: false,
172
- side: 0,
173
- outsideBy: 0,
174
- };
175
- summary.effectiveSurface = 'track';
176
- summary.effectivePriority = priority(summary.effectiveSurface);
177
- summary.representative = null;
178
- summary.allRightOutside = true;
179
- summary.allLeftOutside = true;
180
- summary.allOnTrackOrPit = true;
181
- summary.allInPitLane = true;
182
- summary.minimumSignedOffset = Infinity;
183
- summary.maximumSignedOffset = -Infinity;
184
- summary.trackLimits = trackLimits;
185
- return summary;
186
- }
187
-
188
- function prepareWheelSummary(scratch) {
189
- if (!scratch) return null;
190
- const summary = scratch.summary ?? {};
191
- scratch.summary = summary;
192
- return resetWheelSummary(summary);
193
- }
194
-
195
- function recordWheelSummary(summary, wheel) {
196
- const wheelPriority = priority(wheel.surface);
197
- if (wheelPriority > summary.effectivePriority) {
198
- summary.effectivePriority = wheelPriority;
199
- summary.effectiveSurface = wheel.surface;
200
- }
201
- if (!summary.representative || Math.abs(wheel.signedOffset) > Math.abs(summary.representative.signedOffset)) {
202
- summary.representative = wheel;
203
- }
204
- if (wheel.outsideSide !== 1) summary.allRightOutside = false;
205
- if (wheel.outsideSide !== -1) summary.allLeftOutside = false;
206
- if (!(wheel.onTrack || wheel.inPitLane)) summary.allOnTrackOrPit = false;
207
- if (!wheel.inPitLane) summary.allInPitLane = false;
208
- if (wheel.minimumSignedOffset < summary.minimumSignedOffset) summary.minimumSignedOffset = wheel.minimumSignedOffset;
209
- if (wheel.maximumSignedOffset > summary.maximumSignedOffset) summary.maximumSignedOffset = wheel.maximumSignedOffset;
210
- return summary;
211
- }
212
-
213
- function finishWheelSummary(summary, track, trackLimit = track?.width / 2) {
214
- const trackLimits = summary.trackLimits;
215
- trackLimits.violating = false;
216
- trackLimits.side = 0;
217
- trackLimits.outsideBy = 0;
218
- if (track && (summary.allRightOutside || summary.allLeftOutside)) {
219
- const side = summary.allRightOutside ? 1 : -1;
220
- const outsideBy = summary.allRightOutside
221
- ? summary.minimumSignedOffset - trackLimit
222
- : -trackLimit - summary.maximumSignedOffset;
223
- trackLimits.violating = outsideBy > 0;
224
- trackLimits.side = side;
225
- trackLimits.outsideBy = Math.max(0, outsideBy);
226
- }
227
- return summary;
228
- }
229
-
230
- function queryConnectorSampleState(track, car, point, progressHint, centerState) {
231
- const baseSegmentId = Number.isInteger(centerState?.segmentId) ? centerState.segmentId : null;
232
- if (baseSegmentId == null) return queryHintedTrackStateForCar(track, car, point, progressHint);
233
- return queryLocalSegmentTrackStateForCar(
234
- track,
235
- car,
236
- point,
237
- baseSegmentId,
238
- progressHint,
239
- { skipPitOverrideInsideMainRoad: true },
240
- );
241
- }
242
-
243
- function sampleFullConnectorPatchInto(sampledStates, patch, track, car, progressHint, centerState) {
244
- const corners = ensureOrientedRectCorners(patch);
245
- sampledStates.length = 1 + corners.length;
246
- sampledStates[0] = queryConnectorSampleState(track, car, patch.center, progressHint, centerState);
247
- for (let index = 0; index < corners.length; index += 1) {
248
- sampledStates[index + 1] = queryConnectorSampleState(track, car, corners[index], progressHint, centerState);
249
- }
250
- return sampledStates;
251
- }
252
-
253
- function sampleFullNearestPatchInto(sampledStates, patch, track, car) {
254
- const corners = ensureOrientedRectCorners(patch);
255
- sampledStates.length = 1 + corners.length;
256
- sampledStates[0] = nearestTrackStateForCar(track, car, patch.center, car.progress);
257
- for (let index = 0; index < corners.length; index += 1) {
258
- sampledStates[index + 1] = nearestTrackStateForCar(track, car, corners[index], car.progress);
259
- }
260
- return sampledStates;
261
- }
262
-
263
- function writeConnectorWheelState(target, {
264
- patch,
265
- wheelCenterState,
266
- centerState,
267
- car,
268
- track,
269
- trackLimit,
270
- progressHint,
271
- sampledStates,
272
- }) {
273
- if (!wheelCenterState) {
274
- return writeAnalyticWheelState(target, patch, centerState, track, trackLimit);
275
- }
276
- if (!wheelCenterState.inPitLane) {
277
- return writeAnalyticWheelState(target, patch, wheelCenterState, track, trackLimit);
278
- }
279
- if (pitPatchInsideRoad(patch, wheelCenterState)) {
280
- return writeAnalyticPitWheelState(target, patch, wheelCenterState);
281
- }
282
- sampleFullConnectorPatchInto(sampledStates, patch, track, car, progressHint, centerState);
283
- return writeFullWheelState(target, patch, sampledStates, trackLimit);
284
- }
285
-
286
- function collectPatchCenters(target, patches) {
287
- target.length = patches.length;
288
- for (let index = 0; index < patches.length; index += 1) {
289
- target[index] = patches[index].center;
290
- }
291
- return target;
292
- }
293
-
294
- function connectorWheelCenterStates(
295
- track,
296
- car,
297
- patches,
298
- centerState,
299
- progressHint,
300
- scratchStates = null,
301
- scratchProjections = null,
302
- scratchPositions = null,
303
- ) {
304
- const baseSegmentId = Number.isInteger(centerState?.segmentId) ? centerState.segmentId : null;
305
- const states = scratchStates ?? new Array(patches.length);
306
- states.length = patches.length;
307
- if (baseSegmentId == null) {
308
- for (let index = 0; index < patches.length; index += 1) {
309
- states[index] = queryHintedTrackStateForCar(track, car, patches[index].center, progressHint);
310
- }
311
- return states;
312
- }
313
- const positions = collectPatchCenters(scratchPositions ?? new Array(patches.length), patches);
314
- return queryLocalSegmentTrackStatesForCar(
315
- track,
316
- car,
317
- positions,
318
- baseSegmentId,
319
- progressHint,
320
- {
321
- radius: 2,
322
- skipPitOverrideInsideMainRoad: true,
323
- target: states,
324
- projections: scratchProjections,
325
- },
326
- );
327
- }
328
-
329
- function connectorWheelCenterProjections(
330
- track,
331
- car,
332
- patches,
333
- centerState,
334
- progressHint,
335
- scratchProjections = null,
336
- scratchPositions = null,
337
- ) {
338
- const baseSegmentId = Number.isInteger(centerState?.segmentId) ? centerState.segmentId : null;
339
- if (baseSegmentId == null) return null;
340
- const positions = collectPatchCenters(scratchPositions ?? new Array(patches.length), patches);
341
- return queryLocalSegmentTrackProjectionsForCar(
342
- track,
343
- car,
344
- positions,
345
- baseSegmentId,
346
- progressHint,
347
- {
348
- radius: 2,
349
- projections: scratchProjections,
350
- },
351
- );
352
- }
353
-
354
- function canUseStraightConnectorMainTrackAnalytic(track, geometry, centerState, trackLimit) {
355
- if (
356
- !track ||
357
- !geometry?.contactPatches?.length ||
358
- !centerState ||
359
- centerState.inPitLane ||
360
- !centerState.onTrack ||
361
- Math.abs(centerState.curvature ?? 0) > STRAIGHT_CONNECTOR_CURVATURE_EPSILON
362
- ) {
363
- return false;
364
- }
365
-
366
- for (let index = 0; index < geometry.contactPatches.length; index += 1) {
367
- const patch = geometry.contactPatches[index];
368
- const wheelCenterOffset =
369
- (patch.center.x - centerState.x) * centerState.normalX +
370
- (patch.center.y - centerState.y) * centerState.normalY;
371
- const projectedHalfWidth = analyticWheelProjectedHalfWidth(patch, centerState);
372
- if (
373
- wheelCenterOffset - projectedHalfWidth < -trackLimit - MAIN_TRACK_PATCH_MARGIN ||
374
- wheelCenterOffset + projectedHalfWidth > trackLimit + MAIN_TRACK_PATCH_MARGIN
375
- ) {
376
- return false;
377
- }
378
- }
379
- return true;
380
- }
381
-
382
- export function isWholeCarOutsideTrackLimits(wheels = [], track, relaxedMargin = 0) {
383
- if (!wheels.length || !track) {
384
- return {
385
- violating: false,
386
- side: 0,
387
- outsideBy: 0,
388
- };
389
- }
390
- const trackLimit = track.width / 2 + relaxedMargin;
391
- let right = true;
392
- let left = true;
393
- let minimumSignedOffset = Infinity;
394
- let maximumSignedOffset = -Infinity;
395
- for (let index = 0; index < wheels.length; index += 1) {
396
- const wheel = wheels[index];
397
- if (wheel.outsideSide !== 1) right = false;
398
- if (wheel.outsideSide !== -1) left = false;
399
- if (wheel.minimumSignedOffset < minimumSignedOffset) minimumSignedOffset = wheel.minimumSignedOffset;
400
- if (wheel.maximumSignedOffset > maximumSignedOffset) maximumSignedOffset = wheel.maximumSignedOffset;
401
- }
402
- if (!right && !left) {
403
- return {
404
- violating: false,
405
- side: 0,
406
- outsideBy: 0,
407
- };
408
- }
409
- const side = right ? 1 : -1;
410
- const outsideBy = right
411
- ? minimumSignedOffset - trackLimit
412
- : -trackLimit - maximumSignedOffset;
413
-
414
- return {
415
- violating: outsideBy > 0,
416
- side,
417
- outsideBy: Math.max(0, outsideBy),
418
- };
419
- }
420
-
421
72
  function writeAnalyticWheelStatesWithSummary(targetWheels, patches, centerState, track, trackLimit, summaryTarget = null) {
422
73
  const sharedProjectedHalfWidth = patches.length > 0
423
74
  ? analyticWheelProjectedHalfWidth(patches[0], centerState)
@@ -498,76 +149,6 @@ function writeAnalyticWheelStatesFromTrackStatesWithSummary(
498
149
  };
499
150
  }
500
151
 
501
- function writeConnectorWheelStatesWithSummary({
502
- targetWheels,
503
- patches,
504
- wheelCenterStates,
505
- centerState,
506
- car,
507
- track,
508
- trackLimit,
509
- progressHint,
510
- sampledStatePools,
511
- summaryTarget = null,
512
- }) {
513
- const summary = resetWheelSummary(summaryTarget ?? {});
514
- for (let index = 0; index < patches.length; index += 1) {
515
- const sampledStates = sampledStatePools[index] ?? [];
516
- sampledStatePools[index] = sampledStates;
517
- const wheel = writeConnectorWheelState(targetWheels[index], {
518
- patch: patches[index],
519
- wheelCenterState: wheelCenterStates[index],
520
- centerState,
521
- car,
522
- track,
523
- trackLimit,
524
- progressHint,
525
- sampledStates,
526
- });
527
- recordWheelSummary(summary, wheel);
528
- }
529
- finishWheelSummary(summary, track, trackLimit);
530
-
531
- return {
532
- wheels: targetWheels,
533
- effectiveSurface: summary.effectiveSurface,
534
- representative: summary.representative,
535
- trackLimits: summary.trackLimits,
536
- allOnTrackOrPit: summary.allOnTrackOrPit,
537
- allInPitLane: summary.allInPitLane,
538
- };
539
- }
540
-
541
- function writeNearestSampledWheelStatesWithSummary({
542
- targetWheels,
543
- patches,
544
- car,
545
- track,
546
- trackLimit,
547
- sampledStatePools,
548
- summaryTarget = null,
549
- }) {
550
- const summary = resetWheelSummary(summaryTarget ?? {});
551
- for (let index = 0; index < patches.length; index += 1) {
552
- const patch = patches[index];
553
- const sampledStates = sampledStatePools[index] ?? [];
554
- sampledStatePools[index] = sampledStates;
555
- sampleFullNearestPatchInto(sampledStates, patch, track, car);
556
- const wheel = writeFullWheelState(targetWheels[index], patch, sampledStates, trackLimit);
557
- recordWheelSummary(summary, wheel);
558
- }
559
- finishWheelSummary(summary, track, trackLimit);
560
-
561
- return {
562
- wheels: targetWheels,
563
- effectiveSurface: summary.effectiveSurface,
564
- representative: summary.representative,
565
- trackLimits: summary.trackLimits,
566
- allOnTrackOrPit: summary.allOnTrackOrPit,
567
- allInPitLane: summary.allInPitLane,
568
- };
569
- }
570
-
571
152
  export function calculateWheelSurfaceState({
572
153
  car,
573
154
  track,
@@ -0,0 +1,55 @@
1
+ // Pose and explicit-center validity for the per-car wheel-surface cache.
2
+
3
+ function usesAutomaticCenterState(centerState, cacheAsAuto = false) {
4
+ return cacheAsAuto || centerState == null;
5
+ }
6
+
7
+ export function cacheMatchesCenterState(cache, centerState, cacheAsAuto = false) {
8
+ const automatic = usesAutomaticCenterState(centerState, cacheAsAuto);
9
+ if ((cache.centerStateAuto ?? true) !== automatic) return false;
10
+ if (automatic) return true;
11
+ return cache.centerStateDistance === centerState.distance &&
12
+ cache.centerStateSignedOffset === centerState.signedOffset &&
13
+ cache.centerStateCrossTrackError === centerState.crossTrackError &&
14
+ cache.centerStateSurface === (centerState.surface ?? null) &&
15
+ cache.centerStateInPitLane === Boolean(centerState.inPitLane) &&
16
+ cache.centerStatePitLanePart === (centerState.pitLanePart ?? null) &&
17
+ cache.centerStatePitBoxId === (centerState.pitBoxId ?? null);
18
+ }
19
+
20
+ export function writeCachedCenterState(cache, centerState, cacheAsAuto = false) {
21
+ const automatic = usesAutomaticCenterState(centerState, cacheAsAuto);
22
+ cache.centerStateAuto = automatic;
23
+ if (automatic) {
24
+ cache.centerStateDistance = null;
25
+ cache.centerStateSignedOffset = null;
26
+ cache.centerStateCrossTrackError = null;
27
+ cache.centerStateSurface = null;
28
+ cache.centerStateInPitLane = false;
29
+ cache.centerStatePitLanePart = null;
30
+ cache.centerStatePitBoxId = null;
31
+ return cache;
32
+ }
33
+ cache.centerStateDistance = centerState.distance;
34
+ cache.centerStateSignedOffset = centerState.signedOffset;
35
+ cache.centerStateCrossTrackError = centerState.crossTrackError;
36
+ cache.centerStateSurface = centerState.surface ?? null;
37
+ cache.centerStateInPitLane = Boolean(centerState.inPitLane);
38
+ cache.centerStatePitLanePart = centerState.pitLanePart ?? null;
39
+ cache.centerStatePitBoxId = centerState.pitBoxId ?? null;
40
+ return cache;
41
+ }
42
+
43
+ export function canRefreshAutomaticCenterStateFromTrackState(car, track) {
44
+ const trackState = car?.trackState;
45
+ if (!trackState || trackState.inPitLane) return false;
46
+ if (
47
+ car?.trackStatePoseX !== car?.x ||
48
+ car?.trackStatePoseY !== car?.y ||
49
+ car?.trackStatePoseHeading !== car?.heading
50
+ ) {
51
+ return false;
52
+ }
53
+ const mainRoadEdge = track.width / 2 + (track.kerbWidth ?? 0);
54
+ return Number.isFinite(trackState.crossTrackError) && trackState.crossTrackError <= mainRoadEdge;
55
+ }
@@ -0,0 +1,101 @@
1
+ import { priority } from './surfacePriority.js';
2
+
3
+ export function resetWheelSummary(summary) {
4
+ const trackLimits = summary.trackLimits ?? {
5
+ violating: false,
6
+ side: 0,
7
+ outsideBy: 0,
8
+ };
9
+ summary.effectiveSurface = 'track';
10
+ summary.effectivePriority = priority(summary.effectiveSurface);
11
+ summary.representative = null;
12
+ summary.allRightOutside = true;
13
+ summary.allLeftOutside = true;
14
+ summary.allOnTrackOrPit = true;
15
+ summary.allInPitLane = true;
16
+ summary.minimumSignedOffset = Infinity;
17
+ summary.maximumSignedOffset = -Infinity;
18
+ summary.trackLimits = trackLimits;
19
+ return summary;
20
+ }
21
+
22
+ export function prepareWheelSummary(scratch) {
23
+ if (!scratch) return null;
24
+ const summary = scratch.summary ?? {};
25
+ scratch.summary = summary;
26
+ return resetWheelSummary(summary);
27
+ }
28
+
29
+ export function recordWheelSummary(summary, wheel) {
30
+ const wheelPriority = priority(wheel.surface);
31
+ if (wheelPriority > summary.effectivePriority) {
32
+ summary.effectivePriority = wheelPriority;
33
+ summary.effectiveSurface = wheel.surface;
34
+ }
35
+ if (!summary.representative || Math.abs(wheel.signedOffset) > Math.abs(summary.representative.signedOffset)) {
36
+ summary.representative = wheel;
37
+ }
38
+ if (wheel.outsideSide !== 1) summary.allRightOutside = false;
39
+ if (wheel.outsideSide !== -1) summary.allLeftOutside = false;
40
+ if (!(wheel.onTrack || wheel.inPitLane)) summary.allOnTrackOrPit = false;
41
+ if (!wheel.inPitLane) summary.allInPitLane = false;
42
+ if (wheel.minimumSignedOffset < summary.minimumSignedOffset) summary.minimumSignedOffset = wheel.minimumSignedOffset;
43
+ if (wheel.maximumSignedOffset > summary.maximumSignedOffset) summary.maximumSignedOffset = wheel.maximumSignedOffset;
44
+ return summary;
45
+ }
46
+
47
+ export function finishWheelSummary(summary, track, trackLimit = track?.width / 2) {
48
+ const trackLimits = summary.trackLimits;
49
+ trackLimits.violating = false;
50
+ trackLimits.side = 0;
51
+ trackLimits.outsideBy = 0;
52
+ if (track && (summary.allRightOutside || summary.allLeftOutside)) {
53
+ const side = summary.allRightOutside ? 1 : -1;
54
+ const outsideBy = summary.allRightOutside
55
+ ? summary.minimumSignedOffset - trackLimit
56
+ : -trackLimit - summary.maximumSignedOffset;
57
+ trackLimits.violating = outsideBy > 0;
58
+ trackLimits.side = side;
59
+ trackLimits.outsideBy = Math.max(0, outsideBy);
60
+ }
61
+ return summary;
62
+ }
63
+
64
+ export function isWholeCarOutsideTrackLimits(wheels = [], track, relaxedMargin = 0) {
65
+ if (!wheels.length || !track) {
66
+ return {
67
+ violating: false,
68
+ side: 0,
69
+ outsideBy: 0,
70
+ };
71
+ }
72
+ const trackLimit = track.width / 2 + relaxedMargin;
73
+ let right = true;
74
+ let left = true;
75
+ let minimumSignedOffset = Infinity;
76
+ let maximumSignedOffset = -Infinity;
77
+ for (let index = 0; index < wheels.length; index += 1) {
78
+ const wheel = wheels[index];
79
+ if (wheel.outsideSide !== 1) right = false;
80
+ if (wheel.outsideSide !== -1) left = false;
81
+ if (wheel.minimumSignedOffset < minimumSignedOffset) minimumSignedOffset = wheel.minimumSignedOffset;
82
+ if (wheel.maximumSignedOffset > maximumSignedOffset) maximumSignedOffset = wheel.maximumSignedOffset;
83
+ }
84
+ if (!right && !left) {
85
+ return {
86
+ violating: false,
87
+ side: 0,
88
+ outsideBy: 0,
89
+ };
90
+ }
91
+ const side = right ? 1 : -1;
92
+ const outsideBy = right
93
+ ? minimumSignedOffset - trackLimit
94
+ : -trackLimit - maximumSignedOffset;
95
+
96
+ return {
97
+ violating: outsideBy > 0,
98
+ side,
99
+ outsideBy: Math.max(0, outsideBy),
100
+ };
101
+ }