@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,561 +1,7 @@
1
- import { createCarRayMiss, estimateCarHit } from './carRays.js';
2
- import { normalizeRayOptions } from './rayConfig.js';
3
- import { degreesToRadians, getCarRayOrigin } from './rayGeometry.js';
4
- import { isSelfCarTarget, rayDetectableTargetsForSnapshot } from './sensorTargets.js';
5
- import { createTrackMiss, createTrackRayContext, estimateTrackHit } from './trackRays.js';
6
- import { createSurfaceMiss, estimateSurfaceHits } from './surfaceRays.js';
7
- import { canUseBatchTrainingRayApproximation } from './rayGuards.js';
8
- import { traceIndexedRayBands } from './rayBandTrace.js';
9
- import { createRayChannelFlags, writeRayChannelFlags } from './rayChannels.js';
10
- import { pitOverrideAllowedForCar } from '../../simulation/track/trackStatePolicy.js';
11
-
12
1
  export { buildNearbyCars } from './nearbyCars.js';
13
2
  export { DEFAULT_RAY_ANGLES_DEGREES } from './rayDefaults.js';
14
3
  export { RAY_CHANNELS } from './rayChannels.js';
15
4
  export { normalizeRayOptions, RAY_LAYOUT_PRESETS } from './rayConfig.js';
16
5
  export { getCarRayOrigin, getCarRayVector } from './rayGeometry.js';
17
-
18
- const CHANNEL_CONTEXT = Symbol('rayChannelContext');
19
-
20
- export function createRayBatchContext(snapshot, { scratch = false } = {}) {
21
- const contextScratch = scratch === true
22
- ? {}
23
- : scratch && typeof scratch === 'object'
24
- ? scratch
25
- : null;
26
- const context = {
27
- rayTargets: rayDetectableTargetsForSnapshot(snapshot, contextScratch),
28
- };
29
- if (contextScratch) context.scratch = contextScratch;
30
- return context;
31
- }
32
-
33
- function getRayScratch(batchContext) {
34
- return batchContext?.scratch ?? null;
35
- }
36
-
37
- function getNormalizedRayOptions(rayOptions, scratch) {
38
- if (!scratch || !rayOptions || typeof rayOptions !== 'object') return normalizeRayOptions(rayOptions);
39
- if (rayOptions?.rays && rayOptions?.channels && rayOptions?.anglesDegrees) {
40
- return normalizeRayOptions(rayOptions);
41
- }
42
- const cache = scratch.normalizedRayOptionsBySource ?? new WeakMap();
43
- scratch.normalizedRayOptionsBySource = cache;
44
- const cached = cache.get(rayOptions);
45
- if (cached) return cached;
46
- const normalized = normalizeRayOptions(rayOptions);
47
- cache.set(rayOptions, normalized);
48
- return normalized;
49
- }
50
-
51
- function getRayOrigin(car, scratch) {
52
- if (!scratch) return getCarRayOrigin(car);
53
- const origin = scratch.origin ?? {};
54
- scratch.origin = origin;
55
- origin.x = car.x;
56
- origin.y = car.y;
57
- return origin;
58
- }
59
-
60
- function getFilteredCarTargets(car, snapshot, batchContext, scratch) {
61
- const targets = batchContext?.rayTargets ?? rayDetectableTargetsForSnapshot(snapshot);
62
- if (!scratch) return targets.filter((target) => !isSelfCarTarget(car, target));
63
- const filtered = scratch.carTargets ?? [];
64
- scratch.carTargets = filtered;
65
- let count = 0;
66
- for (let index = 0; index < targets.length; index += 1) {
67
- const target = targets[index];
68
- if (isSelfCarTarget(car, target)) continue;
69
- filtered[count] = target;
70
- count += 1;
71
- }
72
- filtered.length = count;
73
- return filtered;
74
- }
75
-
76
- function prepareScratchArray(scratch, arrayKey, poolKey, count, createItem) {
77
- if (!scratch) return null;
78
- const values = scratch[arrayKey] ?? [];
79
- const pool = scratch[poolKey] ?? [];
80
- scratch[arrayKey] = values;
81
- scratch[poolKey] = pool;
82
- values.length = count;
83
- for (let index = 0; index < count; index += 1) {
84
- const item = pool[index] ?? createItem();
85
- pool[index] = item;
86
- values[index] = item;
87
- }
88
- return values;
89
- }
90
-
91
- function getScratchRayVector(scratch, index, car, angleRadians) {
92
- if (!scratch) {
93
- return {
94
- x: Math.cos((car.heading ?? 0) + angleRadians),
95
- y: Math.sin((car.heading ?? 0) + angleRadians),
96
- };
97
- }
98
- const vectors = scratch.rayVectors ?? [];
99
- scratch.rayVectors = vectors;
100
- const vector = vectors[index] ?? {};
101
- vectors[index] = vector;
102
- vector.x = Math.cos((car.heading ?? 0) + angleRadians);
103
- vector.y = Math.sin((car.heading ?? 0) + angleRadians);
104
- return vector;
105
- }
106
-
107
- function getSharedRayQuery(scratch, index) {
108
- if (!scratch) return {};
109
- const queries = scratch.sharedRayQueries ?? [];
110
- scratch.sharedRayQueries = queries;
111
- const query = queries[index] ?? {};
112
- queries[index] = query;
113
- query.carHitResult = query.carHitResult ?? {};
114
- query.surfaceBoundaries = null;
115
- query.trackBandBoundaries = null;
116
- query.boundaryStateCache = null;
117
- query.sampleStateCache = null;
118
- return query;
119
- }
120
-
121
- function getCarRayStats(scratch) {
122
- if (!scratch) return null;
123
- const stats = scratch.carRayStats ?? {
124
- callerVectorCount: 0,
125
- computedVectorCount: 0,
126
- resultTargetCount: 0,
127
- };
128
- scratch.carRayStats = stats;
129
- return stats;
130
- }
131
-
132
- function estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch) {
133
- return estimateCarHit(car, snapshot, ray.angleDegrees, ray.lengthMeters, origin, carTargets, {
134
- rayVector: vector,
135
- resultTarget: sharedRayQuery?.carHitResult,
136
- stats: getCarRayStats(scratch),
137
- });
138
- }
139
-
140
- function getRayBandTraceStats(scratch) {
141
- if (!scratch) return null;
142
- const stats = scratch.rayBandTraceStats ?? {
143
- directPathCount: 0,
144
- sampledPathCount: 0,
145
- fallbackCount: 0,
146
- channelSetAllocations: 0,
147
- };
148
- scratch.rayBandTraceStats = stats;
149
- return stats;
150
- }
151
-
152
- function createRayChannelContext(normalized) {
153
- if (normalized?.[CHANNEL_CONTEXT]) return normalized[CHANNEL_CONTEXT];
154
- const channels = writeRayChannelFlags(createRayChannelFlags(), normalized.channels);
155
- const context = {
156
- channels,
157
- surfaceChannels: channels.surfaceChannels,
158
- hasRoadEdge: channels.hasRoadEdge,
159
- hasCar: channels.hasCar,
160
- hasRoadOrSurface: channels.hasRoadEdge || channels.surfaceChannels.length > 0,
161
- };
162
- if (normalized) {
163
- Object.defineProperty(normalized, CHANNEL_CONTEXT, {
164
- value: context,
165
- enumerable: false,
166
- });
167
- }
168
- return context;
169
- }
170
-
171
- function estimateRayBands({
172
- car,
173
- snapshot,
174
- ray,
175
- angleDegrees,
176
- origin,
177
- vector,
178
- normalized,
179
- channelContext,
180
- trackContext,
181
- sharedRayQuery,
182
- scratch,
183
- }) {
184
- const stats = getRayBandTraceStats(scratch);
185
- const canUseRayBandTrace = channelContext.hasRoadOrSurface &&
186
- trackContext?.originState;
187
- if (canUseRayBandTrace) {
188
- const direct = traceIndexedRayBands({
189
- track: snapshot.track,
190
- origin,
191
- vector,
192
- lengthMeters: ray.lengthMeters,
193
- originState: trackContext.originState,
194
- channels: channelContext.channels,
195
- precision: normalized.precision,
196
- sharedRayQuery,
197
- stats,
198
- validateBoundaries: car?.interaction?.profile !== 'batch-training',
199
- allowPitOverride: car?.interaction?.profile === 'batch-training'
200
- ? false
201
- : pitOverrideAllowedForCar(car),
202
- mainTrackOnly: car?.interaction?.profile === 'batch-training',
203
- });
204
- if (direct.available) {
205
- if (stats) {
206
- if (direct.path === 'sampled') stats.sampledPathCount += 1;
207
- else stats.directPathCount += 1;
208
- }
209
- return {
210
- roadEdge: direct.roadEdge,
211
- surfaceHits: {
212
- kerb: direct.kerb,
213
- illegalSurface: direct.illegalSurface,
214
- },
215
- };
216
- }
217
- if (stats) stats.fallbackCount += 1;
218
- }
219
-
220
- return {
221
- roadEdge: channelContext.hasRoadEdge
222
- ? estimateTrackHit(
223
- car,
224
- snapshot,
225
- angleDegrees,
226
- ray.lengthMeters,
227
- trackContext,
228
- { precision: normalized.precision, sharedRayQuery },
229
- )
230
- : createTrackMiss(ray.lengthMeters),
231
- surfaceHits: estimateSurfaceHits(
232
- car,
233
- snapshot,
234
- ray,
235
- origin,
236
- vector,
237
- channelContext.surfaceChannels,
238
- trackContext,
239
- { precision: normalized.precision, sharedRayQuery },
240
- ),
241
- };
242
- }
243
-
244
- function writeRichRay(target, ray, angleRadians, roadEdge, carHit, surfaceHits, channels) {
245
- target.id = ray.id;
246
- target.angleDegrees = ray.angleDegrees;
247
- target.angleRadians = angleRadians;
248
- target.lengthMeters = ray.lengthMeters;
249
- target.roadEdge = writeTrackRayHit(target.roadEdge ?? {}, roadEdge);
250
- target.track = target.roadEdge;
251
- target.kerb = channels.hasKerb
252
- ? writeSurfaceRayHit(target.kerb ?? {}, surfaceHits.kerb ?? createSurfaceMiss(ray.lengthMeters))
253
- : writeSurfaceRayHit(target.kerb ?? {}, createSurfaceMiss(ray.lengthMeters));
254
- target.illegalSurface = channels.hasIllegalSurface
255
- ? writeSurfaceRayHit(target.illegalSurface ?? {}, surfaceHits.illegalSurface ?? createSurfaceMiss(ray.lengthMeters))
256
- : writeSurfaceRayHit(target.illegalSurface ?? {}, createSurfaceMiss(ray.lengthMeters));
257
- target.car = writeCarRayHit(target.car ?? {}, carHit);
258
- return target;
259
- }
260
-
261
- function writeTrackRayHit(target, source) {
262
- target.hit = Boolean(source?.hit);
263
- target.distanceMeters = source?.distanceMeters;
264
- target.kind = source?.kind ?? null;
265
- return target;
266
- }
267
-
268
- function writeSurfaceRayHit(target, source) {
269
- target.hit = Boolean(source?.hit);
270
- target.distanceMeters = source?.distanceMeters;
271
- target.surface = source?.surface ?? null;
272
- return target;
273
- }
274
-
275
- function writeCarRayHit(target, source) {
276
- target.hit = Boolean(source?.hit);
277
- target.distanceMeters = source?.distanceMeters;
278
- target.driverId = source?.driverId ?? null;
279
- target.targetId = source?.targetId ?? null;
280
- target.targetType = source?.targetType ?? null;
281
- target.relativeSpeedKph = source?.relativeSpeedKph ?? 0;
282
- return target;
283
- }
284
-
285
- export function buildRaySensors(car, snapshot, rayOptions = {}, batchContext = null) {
286
- const scratch = getRayScratch(batchContext);
287
- const normalized = getNormalizedRayOptions(rayOptions, scratch);
288
- if (!normalized.enabled) return [];
289
- const channelContext = createRayChannelContext(normalized);
290
- if (car?.destroyed || car?.outOfRace) {
291
- return buildInactiveCarRays(normalized, scratch);
292
- }
293
- const origin = getRayOrigin(car, scratch);
294
- const usesTrackContext = channelContext.hasRoadOrSurface;
295
- const trackContext = !usesTrackContext
296
- ? null
297
- : createTrackRayContext(car, snapshot, origin, normalized.precision);
298
- const carTargets = channelContext.hasCar
299
- ? getFilteredCarTargets(car, snapshot, batchContext, scratch)
300
- : [];
301
-
302
- if (canUseFastBatchTrainingRays(car, snapshot, trackContext)) {
303
- return buildFastBatchTrainingRays(car, snapshot, normalized, channelContext, origin, carTargets, trackContext, scratch);
304
- }
305
-
306
- const scratchRays = prepareScratchArray(scratch, 'rays', 'rayPool', normalized.rays.length, () => ({}));
307
- const rays = scratchRays ?? normalized.rays.map(() => ({}));
308
- for (let index = 0; index < normalized.rays.length; index += 1) {
309
- const ray = normalized.rays[index];
310
- const angleDegrees = ray.angleDegrees;
311
- const angleRadians = degreesToRadians(angleDegrees);
312
- const vector = getScratchRayVector(scratch, index, car, angleRadians);
313
- const sharedRayQuery = getSharedRayQuery(scratch, index);
314
- const { roadEdge, surfaceHits } = estimateRayBands({
315
- car,
316
- snapshot,
317
- ray,
318
- angleDegrees,
319
- origin,
320
- vector,
321
- normalized,
322
- channelContext,
323
- trackContext,
324
- sharedRayQuery,
325
- scratch,
326
- });
327
- const carHit = channelContext.hasCar
328
- ? estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch)
329
- : createCarRayMiss(ray.lengthMeters);
330
-
331
- writeRichRay(rays[index], ray, angleRadians, roadEdge, carHit, surfaceHits, channelContext.channels);
332
- }
333
- return rays;
334
- }
335
-
336
- export function buildRaySensorVectorValues(car, snapshot, rayOptions = {}, batchContext = null) {
337
- const scratch = getRayScratch(batchContext);
338
- const normalized = getNormalizedRayOptions(rayOptions, scratch);
339
- if (!normalized.enabled) return [];
340
- const channelContext = createRayChannelContext(normalized);
341
- const surfaceChannels = channelContext.surfaceChannels;
342
- if (car?.destroyed || car?.outOfRace) {
343
- return buildInactiveRayVectorValues(normalized, surfaceChannels, scratch);
344
- }
345
-
346
- const origin = getRayOrigin(car, scratch);
347
- const usesTrackContext = channelContext.hasRoadOrSurface;
348
- const trackContext = !usesTrackContext
349
- ? null
350
- : createTrackRayContext(car, snapshot, origin, normalized.precision);
351
- const carTargets = channelContext.hasCar
352
- ? getFilteredCarTargets(car, snapshot, batchContext, scratch)
353
- : [];
354
-
355
- const vectorValues = prepareScratchArray(scratch, 'rayVectorValues', 'rayVectorValuePool', normalized.rays.length, () => []);
356
- const values = vectorValues ?? normalized.rays.map(() => []);
357
- for (let index = 0; index < normalized.rays.length; index += 1) {
358
- const ray = normalized.rays[index];
359
- const angleRadians = degreesToRadians(ray.angleDegrees);
360
- const vector = getScratchRayVector(scratch, index, car, angleRadians);
361
- const sharedRayQuery = getSharedRayQuery(scratch, index);
362
- const { roadEdge, surfaceHits } = estimateRayBands({
363
- car,
364
- snapshot,
365
- ray,
366
- angleDegrees: ray.angleDegrees,
367
- origin,
368
- vector,
369
- normalized,
370
- channelContext,
371
- trackContext,
372
- sharedRayQuery,
373
- scratch,
374
- });
375
- const carHit = channelContext.hasCar
376
- ? estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch)
377
- : createCarRayMiss(ray.lengthMeters);
378
- writeRayVectorValues(values[index], ray.lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels);
379
- }
380
- return values;
381
- }
382
-
383
- export function appendRaySensorVectorValues(target, car, snapshot, rayOptions = {}, batchContext = null) {
384
- const scratch = getRayScratch(batchContext);
385
- const normalized = getNormalizedRayOptions(rayOptions, scratch);
386
- if (!normalized.enabled) return target;
387
- const channelContext = createRayChannelContext(normalized);
388
- const surfaceChannels = channelContext.surfaceChannels;
389
- if (car?.destroyed || car?.outOfRace) {
390
- for (let index = 0; index < normalized.rays.length; index += 1) {
391
- const ray = normalized.rays[index];
392
- appendRayVectorValues(
393
- target,
394
- ray.lengthMeters,
395
- createTrackMiss(ray.lengthMeters),
396
- createCarRayMiss(ray.lengthMeters),
397
- {},
398
- surfaceChannels,
399
- );
400
- }
401
- return target;
402
- }
403
-
404
- const origin = getRayOrigin(car, scratch);
405
- const usesTrackContext = channelContext.hasRoadOrSurface;
406
- const trackContext = !usesTrackContext
407
- ? null
408
- : createTrackRayContext(car, snapshot, origin, normalized.precision);
409
- const carTargets = channelContext.hasCar
410
- ? getFilteredCarTargets(car, snapshot, batchContext, scratch)
411
- : [];
412
-
413
- for (let index = 0; index < normalized.rays.length; index += 1) {
414
- const ray = normalized.rays[index];
415
- const angleRadians = degreesToRadians(ray.angleDegrees);
416
- const vector = getScratchRayVector(scratch, index, car, angleRadians);
417
- const sharedRayQuery = getSharedRayQuery(scratch, index);
418
- const { roadEdge, surfaceHits } = estimateRayBands({
419
- car,
420
- snapshot,
421
- ray,
422
- angleDegrees: ray.angleDegrees,
423
- origin,
424
- vector,
425
- normalized,
426
- channelContext,
427
- trackContext,
428
- sharedRayQuery,
429
- scratch,
430
- });
431
- const carHit = channelContext.hasCar
432
- ? estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch)
433
- : createCarRayMiss(ray.lengthMeters);
434
- appendRayVectorValues(target, ray.lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels);
435
- }
436
- return target;
437
- }
438
-
439
- function buildInactiveCarRays(normalized, scratch = null) {
440
- const channelContext = createRayChannelContext(normalized);
441
- const rays = prepareScratchArray(scratch, 'rays', 'rayPool', normalized.rays.length, () => ({})) ??
442
- normalized.rays.map(() => ({}));
443
- for (let index = 0; index < normalized.rays.length; index += 1) {
444
- const ray = normalized.rays[index];
445
- const roadEdge = createTrackMiss(ray.lengthMeters);
446
- writeRichRay(
447
- rays[index],
448
- ray,
449
- degreesToRadians(ray.angleDegrees),
450
- roadEdge,
451
- createCarRayMiss(ray.lengthMeters),
452
- {},
453
- channelContext.channels,
454
- );
455
- }
456
- return rays;
457
- }
458
-
459
- function createInactiveRayVectorValues(ray, surfaceChannels) {
460
- return rayVectorValues(
461
- ray.lengthMeters,
462
- createTrackMiss(ray.lengthMeters),
463
- createCarRayMiss(ray.lengthMeters),
464
- {},
465
- surfaceChannels,
466
- );
467
- }
468
-
469
- function buildInactiveRayVectorValues(normalized, surfaceChannels, scratch = null) {
470
- const values = prepareScratchArray(scratch, 'rayVectorValues', 'rayVectorValuePool', normalized.rays.length, () => []) ??
471
- normalized.rays.map(() => []);
472
- for (let index = 0; index < normalized.rays.length; index += 1) {
473
- const ray = normalized.rays[index];
474
- writeRayVectorValues(
475
- values[index],
476
- ray.lengthMeters,
477
- createTrackMiss(ray.lengthMeters),
478
- createCarRayMiss(ray.lengthMeters),
479
- {},
480
- surfaceChannels,
481
- );
482
- }
483
- return values;
484
- }
485
-
486
- function rayVectorValues(lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels) {
487
- return writeRayVectorValues([], lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels);
488
- }
489
-
490
- function writeRayVectorValues(values, lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels) {
491
- values.length = 0;
492
- appendRayVectorValues(values, lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels);
493
- return values;
494
- }
495
-
496
- function appendRayVectorValues(values, lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels) {
497
- values.push(
498
- ratio(roadEdge.distanceMeters, lengthMeters),
499
- roadEdge.hit ? 1 : 0,
500
- roadEdge.kind === 'exit' ? 1 : 0,
501
- roadEdge.kind === 'entry' ? 1 : 0,
502
- ratio(carHit.distanceMeters, lengthMeters),
503
- carHit.hit ? 1 : 0,
504
- carHit.relativeSpeedKph / 200,
505
- carHit.targetType === 'replayGhost' ? 1 : 0,
506
- );
507
- for (let index = 0; index < surfaceChannels.length; index += 1) {
508
- const channel = surfaceChannels[index];
509
- const hit = surfaceHits[channel] ?? createSurfaceMiss(lengthMeters);
510
- values.push(
511
- ratio(hit.distanceMeters, lengthMeters),
512
- hit.hit ? 1 : 0,
513
- );
514
- }
515
- return values;
516
- }
517
-
518
- function ratio(value, max) {
519
- const finite = Number.isFinite(value) ? value : max;
520
- return Math.max(0, Math.min(1, finite / Math.max(1e-9, max)));
521
- }
522
-
523
- function canUseFastBatchTrainingRays(car, snapshot, trackContext) {
524
- return car?.interaction?.profile === 'batch-training' &&
525
- !car.inPitLane &&
526
- !trackContext?.originState?.inPitLane &&
527
- Array.isArray(snapshot.track?.samples) &&
528
- snapshot.track.samples.length > 0 &&
529
- trackContext?.originState &&
530
- canUseBatchTrainingRayApproximation(snapshot.track, trackContext.originState);
531
- }
532
-
533
- function buildFastBatchTrainingRays(car, snapshot, normalized, channelContext, origin, carTargets, trackContext, scratch = null) {
534
- const rays = prepareScratchArray(scratch, 'rays', 'rayPool', normalized.rays.length, () => ({})) ??
535
- normalized.rays.map(() => ({}));
536
- for (let index = 0; index < normalized.rays.length; index += 1) {
537
- const ray = normalized.rays[index];
538
- const angleRadians = degreesToRadians(ray.angleDegrees);
539
- const vector = getScratchRayVector(scratch, index, car, angleRadians);
540
- const sharedRayQuery = getSharedRayQuery(scratch, index);
541
- const { roadEdge, surfaceHits } = estimateRayBands({
542
- car,
543
- snapshot,
544
- ray,
545
- angleDegrees: ray.angleDegrees,
546
- origin,
547
- vector,
548
- normalized,
549
- channelContext,
550
- trackContext,
551
- sharedRayQuery,
552
- scratch,
553
- });
554
- const carHit = channelContext.hasCar && carTargets.length > 0
555
- ? estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch)
556
- : createCarRayMiss(ray.lengthMeters);
557
-
558
- writeRichRay(rays[index], ray, angleRadians, roadEdge, carHit, surfaceHits, channelContext.channels);
559
- }
560
- return rays;
561
- }
6
+ export { createRayBatchContext } from './rayScratch.js';
7
+ export { buildRaySensors, buildRaySensorVectorValues, appendRaySensorVectorValues } from './rayBatch.js';
@@ -3,15 +3,6 @@ import { metersToSimUnits } from '../../simulation/units.js';
3
3
 
4
4
  const RAY_BOUND_QUERY_MARGIN_METERS = 18;
5
5
 
6
- export function findIndexedRayBoundaryHit(track, origin, vector, lengthMeters, offsets, target = null) {
7
- const boundaries = findIndexedRayBoundaryDistances(track, origin, vector, lengthMeters, offsets, target);
8
- if (!boundaries.available) return { available: false, distance: null };
9
- return {
10
- available: true,
11
- distance: minFiniteArray(boundaries.distances),
12
- };
13
- }
14
-
15
6
  export function findIndexedRayBoundaryDistances(track, origin, vector, lengthMeters, offsets, target = null) {
16
7
  const result = target ?? {};
17
8
  const finiteOffsets = target?.finiteOffsets ?? [];
@@ -173,15 +164,6 @@ function minFinite(first, second) {
173
164
  return first <= second ? first : second;
174
165
  }
175
166
 
176
- function minFiniteArray(values) {
177
- let minimum = Infinity;
178
- for (let index = 0; index < values.length; index += 1) {
179
- const value = values[index];
180
- if (Number.isFinite(value) && value < minimum) minimum = value;
181
- }
182
- return Number.isFinite(minimum) ? minimum : null;
183
- }
184
-
185
167
  function nearestSegmentId(firstDistance, firstSegmentId, secondDistance, secondSegmentId) {
186
168
  if (!Number.isFinite(firstDistance)) return Number.isInteger(secondSegmentId) ? secondSegmentId : null;
187
169
  if (!Number.isFinite(secondDistance)) return Number.isInteger(firstSegmentId) ? firstSegmentId : null;
@@ -1,8 +1,8 @@
1
1
  import { metersToSimUnits, simUnitsToMeters } from '../../simulation/units.js';
2
2
  import { normalizeRelativeHeading } from './rayGeometry.js';
3
- import { nearbyDetectableTargets } from './sensorTargets.js';
3
+ import { isSelfCarTarget, nearbyDetectableTargetsForSnapshot } from './sensorTargets.js';
4
4
 
5
- export function buildNearbyCars(car, snapshot, { maxCars = 6, radiusMeters = 150 } = {}) {
5
+ export function buildNearbyCars(car, snapshot, { maxCars = 6, radiusMeters = 150 } = {}, batchContext = null) {
6
6
  const limit = Math.max(0, Math.floor(maxCars));
7
7
  if (limit === 0) return [];
8
8
  const radius = metersToSimUnits(radiusMeters);
@@ -13,11 +13,15 @@ export function buildNearbyCars(car, snapshot, { maxCars = 6, radiusMeters = 150
13
13
  const rightY = Math.cos(car.heading);
14
14
  const nearest = [];
15
15
 
16
- nearbyDetectableTargets(car, snapshot).forEach((other) => {
16
+ const targets = batchContext?.nearbyTargets ?? nearbyDetectableTargetsForSnapshot(snapshot);
17
+ for (const other of targets) {
18
+ if (isSelfCarTarget(car, other)) continue;
17
19
  const dx = other.x - car.x;
18
20
  const dy = other.y - car.y;
19
21
  const distanceSquared = dx * dx + dy * dy;
20
- if (distanceSquared > radiusSquared) return;
22
+ if (distanceSquared > radiusSquared) continue;
23
+ const insertAt = nearestInsertIndex(nearest, distanceSquared, other.order);
24
+ if (insertAt >= limit) continue;
21
25
 
22
26
  const forward = forwardX * dx + forwardY * dy;
23
27
  const right = rightX * dx + rightY * dy;
@@ -34,19 +38,18 @@ export function buildNearbyCars(car, snapshot, { maxCars = 6, radiusMeters = 150
34
38
  distanceSquared,
35
39
  order: other.order,
36
40
  };
37
- insertNearestCar(nearest, entry, limit);
38
- });
41
+ nearest.splice(insertAt, 0, entry);
42
+ if (nearest.length > limit) nearest.pop();
43
+ }
39
44
 
40
45
  return nearest.map(({ distanceSquared, order, ...entry }) => entry);
41
46
  }
42
47
 
43
- function insertNearestCar(nearest, entry, limit) {
44
- let insertAt = nearest.findIndex((candidate) => (
45
- entry.distanceSquared < candidate.distanceSquared ||
46
- (entry.distanceSquared === candidate.distanceSquared && entry.order < candidate.order)
47
- ));
48
- if (insertAt < 0) insertAt = nearest.length;
49
- if (insertAt >= limit) return;
50
- nearest.splice(insertAt, 0, entry);
51
- if (nearest.length > limit) nearest.pop();
48
+ function nearestInsertIndex(nearest, distanceSquared, order) {
49
+ for (let index = 0; index < nearest.length; index += 1) {
50
+ const candidate = nearest[index];
51
+ if (distanceSquared < candidate.distanceSquared ||
52
+ (distanceSquared === candidate.distanceSquared && order < candidate.order)) return index;
53
+ }
54
+ return nearest.length;
52
55
  }
@@ -1,8 +1,9 @@
1
1
  import { kphToSimSpeed, simSpeedToMetersPerSecond, simUnitsToMeters } from '../../simulation/units.js';
2
2
  import { VEHICLE_GEOMETRY } from '../../simulation/vehicle/vehicleGeometry.js';
3
3
 
4
- export function enrichOpponentRadar(car, nearbyCars, snapshot) {
5
- const targetsById = new Map([
4
+ export function enrichOpponentRadar(car, nearbyCars, snapshot, batchContext = null) {
5
+ if (!nearbyCars.length) return [];
6
+ const targetsById = batchContext?.targetsById ?? new Map([
6
7
  ...(snapshot.cars ?? []).map((entry) => [targetKey('car', entry.id), entry]),
7
8
  ...(snapshot.replayGhosts ?? []).map((entry) => [targetKey('replayGhost', entry.id), entry]),
8
9
  ]);
@@ -0,0 +1,20 @@
1
+ import { metersToSimUnits } from '../../simulation/units.js';
2
+ import { PIT_CONNECTOR_RAY_FALLBACK_METERS } from './rayDefaults.js';
3
+
4
+ export function isNearPitConnector(track, state) {
5
+ const pitLane = track?.pitLane;
6
+ if (!pitLane?.enabled || !Number.isFinite(state?.distance)) return false;
7
+ const window = metersToSimUnits(PIT_CONNECTOR_RAY_FALLBACK_METERS);
8
+ const entryDistance = pitLane.entry?.trackDistance ?? pitLane.entry?.distanceFromStart;
9
+ const exitDistance = pitLane.exit?.trackDistance ?? pitLane.exit?.distanceFromStart;
10
+ return wrappedTrackDistance(state.distance, entryDistance, track.length) <= window ||
11
+ wrappedTrackDistance(state.distance, exitDistance, track.length) <= window;
12
+ }
13
+
14
+ function wrappedTrackDistance(first, second, totalLength) {
15
+ if (!Number.isFinite(first) || !Number.isFinite(second) || !Number.isFinite(totalLength) || totalLength <= 0) {
16
+ return Infinity;
17
+ }
18
+ const delta = Math.abs(first - second);
19
+ return Math.min(delta, totalLength - delta);
20
+ }