@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
@@ -0,0 +1,3 @@
1
+ export const AMBIGUOUS_DISTANCE_EPSILON = 1e-6;
2
+ export const HINTED_RUNOFF_WIDE_SEGMENT_RADIUS = 20;
3
+ export const LOW_DENSITY_HINT_RING1_SEGMENT_THRESHOLD = 158;
@@ -0,0 +1,368 @@
1
+ import { metersToSimUnits } from '../units.js';
2
+ import { clamp } from '../simMath.js';
3
+ import { createSpatialGrid, gridCellForPoint, insertIdIntoGridBounds } from './trackQueryGrid.js';
4
+ import { createPitQueryIndex } from './trackQueryPitIndex.js';
5
+ import { createQueryScratch } from './trackQueryScratch.js';
6
+ import { createQueryStats } from './trackQueryStats.js';
7
+ import { createCenterlineSegments, createPrecomputedSegmentNeighborhoods } from './trackQuerySegments.js';
8
+ import { AMBIGUOUS_DISTANCE_EPSILON, HINTED_RUNOFF_WIDE_SEGMENT_RADIUS, LOW_DENSITY_HINT_RING1_SEGMENT_THRESHOLD } from './trackQueryConstants.js';
9
+
10
+ const DEFAULT_GRID_CELL_SIZE = metersToSimUnits(32);
11
+ const ARC_BUCKET_COUNT = 512;
12
+ const PRECOMPUTED_SEGMENT_NEIGHBORHOOD_RADII = new Set([2, HINTED_RUNOFF_WIDE_SEGMENT_RADIUS]);
13
+
14
+ export function createTrackQueryIndex(track) {
15
+ const samples = Array.isArray(track.samples) ? track.samples : [];
16
+ const segmentCount = Math.max(0, samples.length - 1);
17
+ const bands = createTrackBands(track);
18
+ const expansion = bands.runoffEdge + (track.barrierWidth ?? 0) + metersToSimUnits(64);
19
+ const bounds = expandedSampleBounds(samples, expansion);
20
+ const centerline = createCenterlineSegments(samples, segmentCount);
21
+ centerline.precomputedNeighborhoodsByRadius = createPrecomputedSegmentNeighborhoods(segmentCount, PRECOMPUTED_SEGMENT_NEIGHBORHOOD_RADII);
22
+ const grid = createSpatialGrid(bounds, DEFAULT_GRID_CELL_SIZE);
23
+ const segmentGrid = createSpatialGrid(bounds, DEFAULT_GRID_CELL_SIZE);
24
+ const arcBuckets = createArcBuckets(track.length, ARC_BUCKET_COUNT);
25
+
26
+ for (let segmentId = 0; segmentId < segmentCount; segmentId += 1) {
27
+ insertSegmentIntoGrid(grid, centerline, segmentId, expansion);
28
+ insertSegmentIntoGrid(segmentGrid, centerline, segmentId, 0);
29
+ insertSegmentIntoArcBuckets(arcBuckets, centerline, segmentId);
30
+ }
31
+ finalizeArcBuckets(arcBuckets, segmentCount);
32
+
33
+ const lowDensityHintRing1Neighborhood = createLowDensityHintRing1Neighborhood(centerline, segmentGrid, LOW_DENSITY_HINT_RING1_SEGMENT_THRESHOLD);
34
+ const index = {
35
+ version: 1,
36
+ bands,
37
+ nearestGridExpansion: expansion,
38
+ centerline,
39
+ grid,
40
+ segmentGrid,
41
+ arcBuckets,
42
+ nearestOccupiedCellRadiusByCell: createNearestOccupiedCellRadiusByCell(segmentGrid),
43
+ nearestNonLocalHintDistanceSquared: createNearestNonLocalHintDistanceSquared(centerline, segmentGrid, bands.kerbEdge * 2),
44
+ lowDensityHintRing1SegmentCounts: lowDensityHintRing1Neighborhood.counts,
45
+ lowDensityHintRing1CandidateIdsByCell: lowDensityHintRing1Neighborhood.candidateIdsByCell,
46
+ pit: createPitQueryIndex(track, grid.cellSize),
47
+ stats: createQueryStats(),
48
+ };
49
+ attachQueryStatsToCenterline(centerline, index.stats);
50
+ index.queryScratch = createQueryScratch(index);
51
+ return index;
52
+ }
53
+
54
+ export function attachTrackQueryIndex(track, queryIndex) {
55
+ Object.defineProperty(track, 'queryIndex', {
56
+ configurable: true,
57
+ enumerable: false,
58
+ value: queryIndex,
59
+ writable: false,
60
+ });
61
+ return track;
62
+ }
63
+
64
+ export function forkTrackQueryIndex(sourceIndex, runtimeTrack = null) {
65
+ if (!sourceIndex || typeof sourceIndex !== 'object') return null;
66
+ const index = {
67
+ ...sourceIndex,
68
+ centerline: { ...sourceIndex.centerline },
69
+ pit: runtimeTrack ? createPitQueryIndex(runtimeTrack, sourceIndex.grid?.cellSize ?? DEFAULT_GRID_CELL_SIZE) : sourceIndex.pit,
70
+ stats: createQueryStats(),
71
+ };
72
+ attachQueryStatsToCenterline(index.centerline, index.stats);
73
+ Object.freeze(index.centerline);
74
+ index.queryScratch = createQueryScratch(index);
75
+ return index;
76
+ }
77
+
78
+ function attachQueryStatsToCenterline(centerline, stats) {
79
+ const descriptor = Object.getOwnPropertyDescriptor(centerline, 'stats');
80
+ if (!descriptor) {
81
+ Object.defineProperty(centerline, 'stats', {
82
+ configurable: true,
83
+ enumerable: false,
84
+ value: stats,
85
+ writable: true,
86
+ });
87
+ return;
88
+ }
89
+ if (descriptor.writable) centerline.stats = stats;
90
+ }
91
+
92
+ function createTrackBands(track) {
93
+ const trackEdge = track.width / 2;
94
+ const kerbEdge = trackEdge + (track.kerbWidth ?? 0);
95
+ const gravelEdge = kerbEdge + (track.gravelWidth ?? 0);
96
+ const runoffEdge = gravelEdge + (track.runoffWidth ?? 0);
97
+ const barrierInnerFace = runoffEdge - (track.barrierWidth ?? 0) / 2;
98
+ return {
99
+ trackEdge,
100
+ kerbEdge,
101
+ gravelEdge,
102
+ runoffEdge,
103
+ barrierInnerFace,
104
+ };
105
+ }
106
+
107
+ function createArcBuckets(totalLength, count) {
108
+ return {
109
+ count,
110
+ totalLength,
111
+ bucketLength: totalLength / count,
112
+ buckets: Array.from({ length: count }, () => []),
113
+ radius2CandidateIdsByBucket: Array.from({ length: count }, () => []),
114
+ };
115
+ }
116
+
117
+ function insertSegmentIntoArcBuckets(arcBuckets, centerline, segmentId) {
118
+ if (!Number.isFinite(arcBuckets.bucketLength) || arcBuckets.bucketLength <= 0) return;
119
+ const start = Math.floor(centerline.startDistance[segmentId] / arcBuckets.bucketLength);
120
+ const end = Math.floor(centerline.endDistance[segmentId] / arcBuckets.bucketLength);
121
+ for (let bucket = start; bucket <= end; bucket += 1) {
122
+ arcBuckets.buckets[((bucket % arcBuckets.count) + arcBuckets.count) % arcBuckets.count].push(segmentId);
123
+ }
124
+ }
125
+
126
+ function finalizeArcBuckets(arcBuckets, segmentCount) {
127
+ if (!arcBuckets?.count || !segmentCount) return;
128
+ const candidateMarks = new Uint32Array(Math.max(1, segmentCount));
129
+ let epoch = 0;
130
+ for (let center = 0; center < arcBuckets.count; center += 1) {
131
+ epoch += 1;
132
+ if (epoch === 0xffffffff) {
133
+ candidateMarks.fill(0);
134
+ epoch = 1;
135
+ }
136
+ const candidateIds = [];
137
+ for (let offset = -2; offset <= 2; offset += 1) {
138
+ const bucket = ((center + offset) % arcBuckets.count + arcBuckets.count) % arcBuckets.count;
139
+ for (const segmentId of arcBuckets.buckets[bucket]) {
140
+ if (candidateMarks[segmentId] === epoch) continue;
141
+ candidateMarks[segmentId] = epoch;
142
+ candidateIds.push(segmentId);
143
+ }
144
+ }
145
+ arcBuckets.radius2CandidateIdsByBucket[center] = candidateIds;
146
+ }
147
+ }
148
+
149
+ function expandedSampleBounds(samples, expansion) {
150
+ const bounds = samples.reduce((current, point) => ({
151
+ minX: Math.min(current.minX, point.x),
152
+ maxX: Math.max(current.maxX, point.x),
153
+ minY: Math.min(current.minY, point.y),
154
+ maxY: Math.max(current.maxY, point.y),
155
+ }), { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity });
156
+ return {
157
+ minX: bounds.minX - expansion,
158
+ maxX: bounds.maxX + expansion,
159
+ minY: bounds.minY - expansion,
160
+ maxY: bounds.maxY + expansion,
161
+ };
162
+ }
163
+
164
+ function insertSegmentIntoGrid(grid, centerline, segmentId, expansion) {
165
+ insertIdIntoGridBounds(grid, segmentId, {
166
+ minX: centerline.minX[segmentId] - expansion,
167
+ maxX: centerline.maxX[segmentId] + expansion,
168
+ minY: centerline.minY[segmentId] - expansion,
169
+ maxY: centerline.maxY[segmentId] + expansion,
170
+ });
171
+ }
172
+
173
+ function createNearestNonLocalHintDistanceSquared(centerline, grid, maxDistance) {
174
+ const segmentCount = centerline?.segmentCount ?? 0;
175
+ const distances = new Float64Array(segmentCount);
176
+ distances.fill(Infinity);
177
+ if (!segmentCount || !grid) return distances;
178
+ const candidateMarks = new Uint32Array(Math.max(1, segmentCount));
179
+ let epoch = 0;
180
+ const maxDistanceSquared = maxDistance * maxDistance;
181
+ for (let segmentId = 0; segmentId < segmentCount; segmentId += 1) {
182
+ epoch += 1;
183
+ if (epoch === 0xffffffff) {
184
+ candidateMarks.fill(0);
185
+ epoch = 1;
186
+ }
187
+ const bounds = {
188
+ minX: centerline.minX[segmentId] - maxDistance,
189
+ maxX: centerline.maxX[segmentId] + maxDistance,
190
+ minY: centerline.minY[segmentId] - maxDistance,
191
+ maxY: centerline.maxY[segmentId] + maxDistance,
192
+ };
193
+ const minCell = gridCellForPoint(grid, { x: bounds.minX, y: bounds.minY }, true);
194
+ const maxCell = gridCellForPoint(grid, { x: bounds.maxX, y: bounds.maxY }, true);
195
+ let bestDistanceSquared = Infinity;
196
+ for (let row = minCell.row; row <= maxCell.row; row += 1) {
197
+ for (let column = minCell.column; column <= maxCell.column; column += 1) {
198
+ const cell = grid.cells[row * grid.columns + column];
199
+ if (!cell) continue;
200
+ for (const candidateId of cell) {
201
+ if (candidateMarks[candidateId] === epoch) continue;
202
+ candidateMarks[candidateId] = epoch;
203
+ if (isLocalSegmentNeighbor(segmentId, candidateId, segmentCount, 2)) continue;
204
+ const candidateDistanceSquared = segmentDistanceSquared(centerline, segmentId, candidateId);
205
+ if (candidateDistanceSquared < bestDistanceSquared) {
206
+ bestDistanceSquared = candidateDistanceSquared;
207
+ }
208
+ if (bestDistanceSquared <= AMBIGUOUS_DISTANCE_EPSILON) {
209
+ break;
210
+ }
211
+ }
212
+ if (bestDistanceSquared <= AMBIGUOUS_DISTANCE_EPSILON) break;
213
+ }
214
+ if (bestDistanceSquared <= AMBIGUOUS_DISTANCE_EPSILON) break;
215
+ }
216
+ distances[segmentId] = bestDistanceSquared <= maxDistanceSquared ? bestDistanceSquared : Infinity;
217
+ }
218
+ return distances;
219
+ }
220
+
221
+ function createLowDensityHintRing1Neighborhood(centerline, grid, threshold) {
222
+ const cellCount = grid?.columns * grid?.rows;
223
+ if (!centerline?.segmentCount || !grid || !Number.isInteger(cellCount) || cellCount <= 0) {
224
+ return {
225
+ counts: new Uint16Array(0),
226
+ candidateIdsByCell: [],
227
+ };
228
+ }
229
+ const counts = new Uint16Array(cellCount);
230
+ const candidateIdsByCell = new Array(cellCount).fill(null);
231
+ const candidateMarks = new Uint32Array(Math.max(1, centerline.segmentCount));
232
+ let epoch = 0;
233
+ for (let row = 0; row < grid.rows; row += 1) {
234
+ for (let column = 0; column < grid.columns; column += 1) {
235
+ epoch += 1;
236
+ if (epoch === 0xffffffff) {
237
+ candidateMarks.fill(0);
238
+ epoch = 1;
239
+ }
240
+ let uniqueCount = 0;
241
+ const ids = [];
242
+ for (let localRow = Math.max(0, row - 1); localRow <= Math.min(grid.rows - 1, row + 1); localRow += 1) {
243
+ for (
244
+ let localColumn = Math.max(0, column - 1);
245
+ localColumn <= Math.min(grid.columns - 1, column + 1);
246
+ localColumn += 1
247
+ ) {
248
+ const cell = grid.cells[localRow * grid.columns + localColumn];
249
+ if (!cell) continue;
250
+ for (const segmentId of cell) {
251
+ if (candidateMarks[segmentId] === epoch) continue;
252
+ candidateMarks[segmentId] = epoch;
253
+ uniqueCount += 1;
254
+ ids.push(segmentId);
255
+ }
256
+ }
257
+ }
258
+ const cellIndex = row * grid.columns + column;
259
+ counts[cellIndex] = uniqueCount;
260
+ if (uniqueCount <= threshold) {
261
+ candidateIdsByCell[cellIndex] = ids;
262
+ }
263
+ }
264
+ }
265
+ return { counts, candidateIdsByCell };
266
+ }
267
+
268
+ function createNearestOccupiedCellRadiusByCell(grid) {
269
+ const cellCount = grid?.columns * grid?.rows;
270
+ if (!grid || !Number.isInteger(cellCount) || cellCount <= 0) {
271
+ return new Uint16Array(0);
272
+ }
273
+ const radii = new Uint16Array(cellCount);
274
+ radii.fill(0xffff);
275
+ const queue = new Uint32Array(cellCount);
276
+ let head = 0;
277
+ let tail = 0;
278
+
279
+ for (let cellIndex = 0; cellIndex < cellCount; cellIndex += 1) {
280
+ if (!grid.cells[cellIndex]?.length) continue;
281
+ radii[cellIndex] = 0;
282
+ queue[tail] = cellIndex;
283
+ tail += 1;
284
+ }
285
+
286
+ while (head < tail) {
287
+ const cellIndex = queue[head];
288
+ head += 1;
289
+ const row = Math.floor(cellIndex / grid.columns);
290
+ const column = cellIndex - row * grid.columns;
291
+ const nextRadius = radii[cellIndex] + 1;
292
+
293
+ for (let rowOffset = -1; rowOffset <= 1; rowOffset += 1) {
294
+ const neighborRow = row + rowOffset;
295
+ if (neighborRow < 0 || neighborRow >= grid.rows) continue;
296
+ for (let columnOffset = -1; columnOffset <= 1; columnOffset += 1) {
297
+ const neighborColumn = column + columnOffset;
298
+ if (neighborColumn < 0 || neighborColumn >= grid.columns) continue;
299
+ const neighborIndex = neighborRow * grid.columns + neighborColumn;
300
+ if (radii[neighborIndex] <= nextRadius) continue;
301
+ radii[neighborIndex] = nextRadius;
302
+ queue[tail] = neighborIndex;
303
+ tail += 1;
304
+ }
305
+ }
306
+ }
307
+
308
+ return radii;
309
+ }
310
+
311
+ function isLocalSegmentNeighbor(segmentId, candidateId, segmentCount, radius) {
312
+ const delta = Math.abs(segmentId - candidateId);
313
+ return Math.min(delta, segmentCount - delta) <= radius;
314
+ }
315
+
316
+ function segmentDistanceSquared(centerline, firstSegmentId, secondSegmentId) {
317
+ const ax = centerline.startX[firstSegmentId];
318
+ const ay = centerline.startY[firstSegmentId];
319
+ const bx = centerline.endX[firstSegmentId];
320
+ const by = centerline.endY[firstSegmentId];
321
+ const cx = centerline.startX[secondSegmentId];
322
+ const cy = centerline.startY[secondSegmentId];
323
+ const dx = centerline.endX[secondSegmentId];
324
+ const dy = centerline.endY[secondSegmentId];
325
+
326
+ if (segmentsIntersect(ax, ay, bx, by, cx, cy, dx, dy)) return 0;
327
+
328
+ return Math.min(
329
+ pointToSegmentDistanceSquared(ax, ay, cx, cy, dx, dy),
330
+ pointToSegmentDistanceSquared(bx, by, cx, cy, dx, dy),
331
+ pointToSegmentDistanceSquared(cx, cy, ax, ay, bx, by),
332
+ pointToSegmentDistanceSquared(dx, dy, ax, ay, bx, by),
333
+ );
334
+ }
335
+
336
+ function pointToSegmentDistanceSquared(px, py, ax, ay, bx, by) {
337
+ const dx = bx - ax;
338
+ const dy = by - ay;
339
+ const lengthSquared = dx * dx + dy * dy;
340
+ const amount = lengthSquared > 0
341
+ ? clamp(((px - ax) * dx + (py - ay) * dy) / lengthSquared, 0, 1)
342
+ : 0;
343
+ const x = ax + dx * amount;
344
+ const y = ay + dy * amount;
345
+ const deltaX = px - x;
346
+ const deltaY = py - y;
347
+ return deltaX * deltaX + deltaY * deltaY;
348
+ }
349
+
350
+ function segmentsIntersect(ax, ay, bx, by, cx, cy, dx, dy) {
351
+ const abx = bx - ax;
352
+ const aby = by - ay;
353
+ const acx = cx - ax;
354
+ const acy = cy - ay;
355
+ const adx = dx - ax;
356
+ const ady = dy - ay;
357
+ const cdx = dx - cx;
358
+ const cdy = dy - cy;
359
+ const cax = ax - cx;
360
+ const cay = ay - cy;
361
+ const cbx = bx - cx;
362
+ const cby = by - cy;
363
+ const cross1 = abx * acy - aby * acx;
364
+ const cross2 = abx * ady - aby * adx;
365
+ const cross3 = cdx * cay - cdy * cax;
366
+ const cross4 = cdx * cby - cdy * cbx;
367
+ return cross1 * cross2 <= 0 && cross3 * cross4 <= 0;
368
+ }