@inventure71/paddockjs 4.1.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/INSTALL_AND_UPDATE.md +31 -5
  3. package/README.md +34 -3
  4. package/docs/advanced_ai_pace.md +131 -0
  5. package/docs/advanced_physics_rebuild.md +166 -0
  6. package/docs/architecture.md +111 -30
  7. package/docs/cleanup_coverage.md +284 -0
  8. package/docs/component_inventory.md +32 -16
  9. package/docs/composable-layouts.md +2 -0
  10. package/docs/concepts.md +12 -1
  11. package/docs/custom_model_controller.md +1 -1
  12. package/docs/data_contract.md +33 -10
  13. package/docs/data_model.md +4 -2
  14. package/docs/efficiency_cleanup_2026-10-13.md +80 -0
  15. package/docs/getting-started.md +3 -0
  16. package/docs/grid_walk.md +80 -0
  17. package/docs/index.md +5 -2
  18. package/docs/keyboard_driving_fix.md +118 -0
  19. package/docs/limit_driving_2026-10-14.md +88 -0
  20. package/docs/loading.md +141 -0
  21. package/docs/maintainability_audit_2026-09-08.md +146 -0
  22. package/docs/racing_ai_improvements.md +145 -0
  23. package/docs/rules.md +43 -13
  24. package/docs/sense_contract.md +4 -1
  25. package/docs/system_specs.md +40 -12
  26. package/docs/testing-reliability-guide.md +67 -0
  27. package/docs/v11_improvements.md +108 -0
  28. package/package.json +23 -15
  29. package/src/api/MountedSimulatorComponents.js +101 -0
  30. package/src/api/PaddockSimulatorController.js +56 -81
  31. package/src/app/BrowserExpertAdapter.js +0 -6
  32. package/src/app/BrowserExpertExternalRenderer.js +72 -50
  33. package/src/app/ExternalTrackSnapshot.js +50 -0
  34. package/src/app/F1SimulatorApp.js +242 -297
  35. package/src/app/HostLifecycleEmitter.js +93 -0
  36. package/src/app/banners/raceDataBannerController.js +9 -4
  37. package/src/app/camera/cameraConstants.js +1 -1
  38. package/src/app/camera/cameraController.js +10 -13
  39. package/src/app/camera/cameraGeometry.js +1 -1
  40. package/src/app/controls/SimulatorViewControls.js +45 -0
  41. package/src/app/controls/workbenchBindings.js +64 -0
  42. package/src/app/controls/workbenchDisclosure.js +41 -0
  43. package/src/app/domBindings.js +21 -13
  44. package/src/app/gridWalk/GridWalkController.js +180 -0
  45. package/src/app/gridWalk/gridWalkCamera.js +88 -0
  46. package/src/app/gridWalk/gridWalkControls.js +24 -0
  47. package/src/app/gridWalk/gridWalkCrew.js +246 -0
  48. package/src/app/gridWalk/gridWalkLabels.js +104 -0
  49. package/src/app/gridWalk/gridWalkLayout.js +46 -0
  50. package/src/app/gridWalk/gridWalkNavigation.js +99 -0
  51. package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
  52. package/src/app/gridWalk/gridWalkSession.js +50 -0
  53. package/src/app/layoutSupport.js +2 -0
  54. package/src/app/raceOverlayClearanceSupport.js +21 -7
  55. package/src/app/readouts/carOverviewRenderer.js +9 -4
  56. package/src/app/readouts/readoutFormatters.js +1 -8
  57. package/src/app/readouts/telemetryRenderer.js +1 -3
  58. package/src/app/readouts/timingTowerRenderer.js +48 -50
  59. package/src/app/rendering/appAssets.js +8 -10
  60. package/src/app/rendering/carRenderer.js +27 -79
  61. package/src/app/rendering/displayUtils.js +0 -6
  62. package/src/app/rendering/expertSensorRenderer.js +1 -1
  63. package/src/app/rendering/replayGhostRenderer.js +4 -1
  64. package/src/app/rendering/trackRenderer.js +2 -2
  65. package/src/app/runtime/frameLoop.js +18 -17
  66. package/src/app/runtime/pixiStartupLifecycle.js +94 -0
  67. package/src/app/runtime/runtimeTiming.js +4 -1
  68. package/src/app/runtime/runtimeVisibility.js +7 -2
  69. package/src/config/cssValues.js +1 -1
  70. package/src/config/defaultOptions.js +12 -173
  71. package/src/config/restartOptions.js +1 -0
  72. package/src/config/runtimeSeed.js +10 -0
  73. package/src/config/simulatorDefaults.js +106 -0
  74. package/src/config/telemetryModules.js +20 -0
  75. package/src/config/theme/cssTokens.js +85 -0
  76. package/src/config/theme/themeCss.js +72 -0
  77. package/src/config/theme/themeInputs.js +155 -0
  78. package/src/config/theme/themeResolver.js +172 -0
  79. package/src/config/theme/themeSelectors.js +69 -0
  80. package/src/config/theme/themeTokens.js +257 -0
  81. package/src/config/theme/themeUtils.js +15 -0
  82. package/src/config/themeOptions.js +14 -830
  83. package/src/config/timingGapMode.js +0 -2
  84. package/src/config/uiOptions.js +53 -0
  85. package/src/data/championship.js +16 -158
  86. package/src/data/championshipDefaults.js +87 -0
  87. package/src/data/driverData.js +10 -20
  88. package/src/data/entryIdentity.js +67 -0
  89. package/src/data/entryPresentation.js +23 -0
  90. package/src/data/entryTypes.d.ts +160 -0
  91. package/src/data/index.d.ts +12 -195
  92. package/src/data/ratingScale.js +14 -0
  93. package/src/data/teamTypes.d.ts +23 -0
  94. package/src/data/trackTypes.d.ts +101 -0
  95. package/src/data/vehicleData.js +12 -23
  96. package/src/environment/actions.js +3 -2
  97. package/src/environment/controllerLoop.js +57 -44
  98. package/src/environment/controllerScheduler.js +30 -0
  99. package/src/environment/driverObservations.js +260 -0
  100. package/src/environment/driverReset.js +51 -0
  101. package/src/environment/episode.js +0 -1
  102. package/src/environment/evaluation.js +6 -90
  103. package/src/environment/evaluationTracker.js +100 -0
  104. package/src/environment/events.js +23 -0
  105. package/src/environment/index.d.ts +35 -91
  106. package/src/environment/metrics.js +3 -17
  107. package/src/environment/observationSchema.js +104 -0
  108. package/src/environment/observationVector.js +16 -115
  109. package/src/environment/observations.js +16 -316
  110. package/src/environment/options.js +5 -24
  111. package/src/environment/renderFrames.js +21 -0
  112. package/src/environment/resetOptions.js +41 -0
  113. package/src/environment/results.js +125 -0
  114. package/src/environment/rewards.js +10 -16
  115. package/src/environment/runtime.js +17 -300
  116. package/src/environment/scenarioOptions.js +90 -0
  117. package/src/environment/scenarioPlacements.js +70 -0
  118. package/src/environment/scenarioPresets.js +78 -0
  119. package/src/environment/scenarios.js +5 -236
  120. package/src/environment/scratch.js +18 -0
  121. package/src/environment/sensorOptions.js +20 -0
  122. package/src/environment/sensors/index.js +2 -556
  123. package/src/environment/sensors/indexedRayBands.js +0 -18
  124. package/src/environment/sensors/nearbyCars.js +18 -15
  125. package/src/environment/sensors/opponentRadar.js +3 -2
  126. package/src/environment/sensors/pitConnectorProximity.js +20 -0
  127. package/src/environment/sensors/rayBandTrace.js +1 -37
  128. package/src/environment/sensors/rayBatch.js +81 -0
  129. package/src/environment/sensors/rayConfig.js +26 -0
  130. package/src/environment/sensors/rayGeometry.js +0 -8
  131. package/src/environment/sensors/rayGuards.js +0 -6
  132. package/src/environment/sensors/rayOutputs.js +75 -0
  133. package/src/environment/sensors/rayQuery.js +110 -0
  134. package/src/environment/sensors/rayScratch.js +146 -0
  135. package/src/environment/sensors/sensorTargets.js +12 -4
  136. package/src/environment/sensors/surfaceRays.js +6 -61
  137. package/src/environment/sensors/trackRays.js +7 -80
  138. package/src/environment/simulationSetup.js +30 -0
  139. package/src/environment/specs.js +15 -109
  140. package/src/index.d.ts +43 -202
  141. package/src/index.js +28 -6
  142. package/src/placeholder/index.js +1 -8
  143. package/src/rendering/displayLifecycle.js +6 -0
  144. package/src/rendering/proceduralTrackAsset.js +1 -1
  145. package/src/rendering/renderSnapshot.js +3 -0
  146. package/src/rendering/track/finishLineRenderer.js +1 -1
  147. package/src/rendering/track/gridRenderer.js +1 -1
  148. package/src/rendering/track/offsetStrokeSafety.js +2 -2
  149. package/src/rendering/track/pitLaneRenderer.js +1 -1
  150. package/src/rendering/track/trackMaterialRenderer.js +4 -4
  151. package/src/rendering/track/trackRenderConstants.js +0 -1
  152. package/src/rendering/track/trackRenderGeometry.js +1 -7
  153. package/src/simulation/collisionGeometry.js +12 -20
  154. package/src/simulation/driver/advancedPathControls.js +53 -0
  155. package/src/simulation/driver/advancedRacePace.js +112 -0
  156. package/src/simulation/driver/advancedRacingControls.js +61 -0
  157. package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
  158. package/src/simulation/driver/advancedRacingSteering.js +76 -0
  159. package/src/simulation/driver/advancedRacingTiming.js +16 -0
  160. package/src/simulation/driver/arcadeRacePace.js +83 -0
  161. package/src/simulation/driver/arcadeRacingControls.js +111 -0
  162. package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
  163. package/src/simulation/driver/driverControlConstants.js +0 -2
  164. package/src/simulation/driver/driverController.js +16 -5
  165. package/src/simulation/driver/edgeRecovery.js +2 -3
  166. package/src/simulation/driver/racingControls.js +10 -396
  167. package/src/simulation/driver/racingDriverProfile.js +48 -0
  168. package/src/simulation/driver/racingLinePlan.js +54 -20
  169. package/src/simulation/driver/rejoinControls.js +59 -112
  170. package/src/simulation/driver/safetyCarControls.js +12 -0
  171. package/src/simulation/driver/trafficScan.js +136 -27
  172. package/src/simulation/participants/participantInteractions.js +24 -15
  173. package/src/simulation/pit/pitCarPlacement.js +29 -0
  174. package/src/simulation/pit/pitFlow.js +8 -30
  175. package/src/simulation/pit/pitIntent.js +4 -3
  176. package/src/simulation/pit/pitOccupancy.js +2 -2
  177. package/src/simulation/pit/pitQueue.js +2 -18
  178. package/src/simulation/pit/pitRouteMovement.js +1 -36
  179. package/src/simulation/pit/pitRouting.js +5 -81
  180. package/src/simulation/pit/pitService.js +3 -32
  181. package/src/simulation/pit/pitServiceConstants.js +1 -0
  182. package/src/simulation/pit/pitServiceProgress.js +34 -0
  183. package/src/simulation/pit/pitSnapshots.js +8 -4
  184. package/src/simulation/pit/pitState.js +1 -5
  185. package/src/simulation/race/classification.js +1 -8
  186. package/src/simulation/race/raceFinish.js +3 -8
  187. package/src/simulation/race/raceOrder.js +2 -13
  188. package/src/simulation/race/raceProgress.js +6 -20
  189. package/src/simulation/race/raceSetup.js +1 -1
  190. package/src/simulation/race/raceStep.js +20 -11
  191. package/src/simulation/race/redFlag.js +2 -4
  192. package/src/simulation/race/safetyCar.js +5 -9
  193. package/src/simulation/race/stalledDnf.js +2 -4
  194. package/src/simulation/raceSimulation.js +6 -38
  195. package/src/simulation/replay/replayGhosts.js +44 -32
  196. package/src/simulation/rules/collisionSteward.js +0 -14
  197. package/src/simulation/rules/penaltyActions.js +37 -0
  198. package/src/simulation/rules/penaltyLedger.js +8 -1
  199. package/src/simulation/rules/pitLaneSpeedingSteward.js +1 -1
  200. package/src/simulation/rules/rulesReview.js +20 -16
  201. package/src/simulation/rules/trackLimitsSteward.js +2 -2
  202. package/src/simulation/simMath.js +4 -0
  203. package/src/simulation/snapshots/raceSnapshots.js +30 -11
  204. package/src/simulation/timing/drsState.js +8 -0
  205. package/src/simulation/timing/drsTiming.js +2 -4
  206. package/src/simulation/timing/lapTelemetry.js +7 -4
  207. package/src/simulation/timing/sectorPerformance.js +14 -6
  208. package/src/simulation/timing/timingLines.js +12 -6
  209. package/src/simulation/track/pitLaneAccess.js +2 -2
  210. package/src/simulation/track/pitLaneLayout.js +4 -135
  211. package/src/simulation/track/pitLaneModel.js +70 -0
  212. package/src/simulation/track/pitLaneState.js +0 -1
  213. package/src/simulation/track/pitServiceLayout.js +121 -0
  214. package/src/simulation/track/proceduralTrack.js +77 -0
  215. package/src/simulation/track/spatialQueries.js +76 -31
  216. package/src/simulation/track/trackBuilder.js +54 -0
  217. package/src/simulation/track/trackConstants.js +0 -22
  218. package/src/simulation/track/trackMath.js +4 -29
  219. package/src/simulation/track/trackModel.js +12 -240
  220. package/src/simulation/track/trackModelCache.js +105 -0
  221. package/src/simulation/track/trackNearestQueries.js +1218 -0
  222. package/src/simulation/track/trackQueryConstants.js +3 -0
  223. package/src/simulation/track/trackQueryConstruction.js +368 -0
  224. package/src/simulation/track/trackQueryIndex.js +12 -1933
  225. package/src/simulation/track/trackQuerySegments.js +174 -0
  226. package/src/simulation/track/trackQueryStats.js +12 -0
  227. package/src/simulation/track/trackRayQueries.js +165 -0
  228. package/src/simulation/vehicle/advancedContactResponse.js +89 -0
  229. package/src/simulation/vehicle/advancedTireForces.js +92 -0
  230. package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
  231. package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
  232. package/src/simulation/vehicle/contactResolution.js +23 -9
  233. package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
  234. package/src/simulation/vehicle/pitWheelSurface.js +0 -12
  235. package/src/simulation/vehicle/runoffResponse.js +2 -4
  236. package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
  237. package/src/simulation/vehicle/vehicleGeometry.js +85 -103
  238. package/src/simulation/vehicle/vehicleKinematics.js +10 -0
  239. package/src/simulation/vehicle/vehiclePhysics.js +42 -196
  240. package/src/simulation/vehicle/vehicleSnapshots.js +18 -6
  241. package/src/simulation/vehicle/vehicleState.js +5 -3
  242. package/src/simulation/vehicle/wheelSurface.js +13 -432
  243. package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
  244. package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
  245. package/src/styles.css +173 -19
  246. package/src/ui/bannerTemplates.js +4 -2
  247. package/src/ui/cameraControlsTemplate.js +4 -2
  248. package/src/ui/gridWalkTemplate.js +29 -0
  249. package/src/ui/htmlEscaping.js +8 -0
  250. package/src/ui/raceCanvasTemplate.js +3 -2
  251. package/src/ui/shellTemplate.js +3 -0
  252. package/src/ui/telemetryDrawerTemplate.js +4 -2
  253. package/src/ui/telemetryTemplates.js +3 -22
  254. package/src/ui/templateUtils.js +2 -8
  255. package/src/ui/viewControlsTemplate.js +21 -0
  256. package/examples/train-basic-policy.mjs +0 -276
  257. package/examples/trainingData.mjs +0 -133
@@ -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
+ }