@jgengine/core 0.10.0 → 0.12.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 (727) hide show
  1. package/CHANGELOG.md +39 -2
  2. package/LICENSE +203 -661
  3. package/README.md +1 -1
  4. package/dist/ai/crowd.d.ts +55 -0
  5. package/dist/ai/crowd.js +114 -0
  6. package/dist/ai/flock.d.ts +5 -2
  7. package/dist/ai/flock.js +5 -2
  8. package/dist/ai/groupAssist.d.ts +1 -0
  9. package/dist/ai/groupAssist.js +1 -0
  10. package/dist/ai/heatSystem.d.ts +76 -0
  11. package/dist/ai/heatSystem.js +97 -0
  12. package/dist/ai/interestScheduler.d.ts +107 -0
  13. package/dist/ai/interestScheduler.js +118 -0
  14. package/dist/ai/jobBoard.d.ts +1 -0
  15. package/dist/ai/jobBoard.js +1 -0
  16. package/dist/ai/laneSelect.d.ts +5 -2
  17. package/dist/ai/laneSelect.js +5 -2
  18. package/dist/ai/mobBrain.d.ts +1 -0
  19. package/dist/ai/mobBrain.js +1 -0
  20. package/dist/ai/populationDirector.d.ts +103 -0
  21. package/dist/ai/populationDirector.js +190 -0
  22. package/dist/ai/pursuit.d.ts +56 -0
  23. package/dist/ai/pursuit.js +53 -0
  24. package/dist/ai/spawnDirector.d.ts +7 -2
  25. package/dist/ai/spawnDirector.js +18 -15
  26. package/dist/ai/spawnPoint.d.ts +3 -1
  27. package/dist/ai/spawnPoint.js +3 -1
  28. package/dist/ai/targetAcquisition.d.ts +84 -0
  29. package/dist/ai/targetAcquisition.js +92 -0
  30. package/dist/anim/easing.d.ts +19 -0
  31. package/dist/anim/easing.js +19 -0
  32. package/dist/anim/oscillator.d.ts +12 -0
  33. package/dist/anim/oscillator.js +25 -0
  34. package/dist/area/areaEffectField.d.ts +127 -0
  35. package/dist/area/areaEffectField.js +175 -0
  36. package/dist/area/stackPolicy.d.ts +47 -0
  37. package/dist/area/stackPolicy.js +99 -0
  38. package/dist/authoring.d.ts +2 -1
  39. package/dist/authoring.js +2 -1
  40. package/dist/board/laneBoard.d.ts +13 -0
  41. package/dist/board/laneBoard.js +13 -0
  42. package/dist/board/timelineBoard.d.ts +10 -0
  43. package/dist/board/timelineBoard.js +10 -0
  44. package/dist/combat/abilityKit.d.ts +18 -0
  45. package/dist/combat/abilityKit.js +24 -9
  46. package/dist/combat/animationState.d.ts +5 -0
  47. package/dist/combat/animationState.js +5 -0
  48. package/dist/combat/breakMeters.d.ts +10 -0
  49. package/dist/combat/breakMeters.js +10 -0
  50. package/dist/combat/castRunner.d.ts +5 -0
  51. package/dist/combat/castRunner.js +5 -0
  52. package/dist/combat/comboPoints.d.ts +5 -0
  53. package/dist/combat/comboPoints.js +5 -0
  54. package/dist/combat/comboString.d.ts +5 -0
  55. package/dist/combat/comboString.js +5 -0
  56. package/dist/combat/damageInterceptors.d.ts +192 -0
  57. package/dist/combat/damageInterceptors.js +251 -0
  58. package/dist/combat/damageMatchup.d.ts +68 -0
  59. package/dist/combat/damageMatchup.js +42 -0
  60. package/dist/combat/damageResolution.d.ts +63 -0
  61. package/dist/combat/damageResolution.js +50 -0
  62. package/dist/combat/death.d.ts +7 -1
  63. package/dist/combat/death.js +5 -0
  64. package/dist/combat/deathReason.d.ts +14 -0
  65. package/dist/combat/deathReason.js +1 -0
  66. package/dist/combat/defensiveWindow.d.ts +5 -0
  67. package/dist/combat/defensiveWindow.js +5 -0
  68. package/dist/combat/dotField.d.ts +5 -1
  69. package/dist/combat/dotField.js +5 -1
  70. package/dist/combat/downed.d.ts +5 -0
  71. package/dist/combat/downed.js +5 -0
  72. package/dist/combat/effects.d.ts +5 -0
  73. package/dist/combat/effects.js +5 -0
  74. package/dist/combat/encounterSequence.d.ts +258 -0
  75. package/dist/combat/encounterSequence.js +426 -0
  76. package/dist/combat/hitReaction.d.ts +57 -1
  77. package/dist/combat/hitReaction.js +45 -5
  78. package/dist/combat/magazine.d.ts +54 -0
  79. package/dist/combat/magazine.js +96 -0
  80. package/dist/combat/projectiles.d.ts +5 -0
  81. package/dist/combat/projectiles.js +32 -14
  82. package/dist/combat/receivedDamage.d.ts +117 -0
  83. package/dist/combat/receivedDamage.js +92 -0
  84. package/dist/combat/regenShield.d.ts +5 -1
  85. package/dist/combat/regenShield.js +5 -1
  86. package/dist/combat/renderCues.d.ts +51 -0
  87. package/dist/combat/renderCues.js +44 -0
  88. package/dist/combat/resourcePool.d.ts +5 -0
  89. package/dist/combat/resourcePool.js +5 -0
  90. package/dist/combat/rewardAllocation.d.ts +121 -0
  91. package/dist/combat/rewardAllocation.js +251 -0
  92. package/dist/combat/shotOrigin.d.ts +18 -1
  93. package/dist/combat/shotOrigin.js +39 -0
  94. package/dist/combat/statusApplication.d.ts +96 -0
  95. package/dist/combat/statusApplication.js +81 -0
  96. package/dist/combat/telegraph.d.ts +11 -2
  97. package/dist/combat/telegraph.js +11 -2
  98. package/dist/combat/thresholdCrossings.d.ts +85 -0
  99. package/dist/combat/thresholdCrossings.js +116 -0
  100. package/dist/combat.d.ts +35 -0
  101. package/dist/combat.js +35 -0
  102. package/dist/commands/commandRegistry.d.ts +11 -0
  103. package/dist/commands/commandRegistry.js +9 -2
  104. package/dist/crafting/crop.d.ts +18 -0
  105. package/dist/crafting/crop.js +18 -0
  106. package/dist/crafting/production.d.ts +5 -0
  107. package/dist/crafting/production.js +5 -0
  108. package/dist/devtools/controls.d.ts +38 -0
  109. package/dist/devtools/controls.js +181 -0
  110. package/dist/devtools/devtools.d.ts +5 -197
  111. package/dist/devtools/devtools.js +38 -774
  112. package/dist/devtools/discover.d.ts +32 -0
  113. package/dist/devtools/discover.js +240 -0
  114. package/dist/devtools/fallbackSeams.d.ts +50 -0
  115. package/dist/devtools/fallbackSeams.js +111 -0
  116. package/dist/devtools/frame.d.ts +15 -0
  117. package/dist/devtools/frame.js +247 -0
  118. package/dist/devtools/logs.d.ts +15 -0
  119. package/dist/devtools/logs.js +86 -0
  120. package/dist/devtools/overrides.d.ts +16 -0
  121. package/dist/devtools/overrides.js +66 -0
  122. package/dist/devtools/rewriteTunables.d.ts +17 -0
  123. package/dist/devtools/rewriteTunables.js +276 -0
  124. package/dist/devtools/saveEndpoint.d.ts +46 -0
  125. package/dist/devtools/saveEndpoint.js +48 -0
  126. package/dist/devtools/transformTunables.d.ts +3 -0
  127. package/dist/devtools/transformTunables.js +3 -0
  128. package/dist/devtools/tunableSchema.d.ts +31 -0
  129. package/dist/devtools/tunableSchema.js +31 -0
  130. package/dist/devtools/types.d.ts +212 -0
  131. package/dist/devtools/types.js +1 -0
  132. package/dist/economy/auctionBook.d.ts +122 -0
  133. package/dist/economy/auctionBook.js +213 -0
  134. package/dist/economy/currency.d.ts +12 -1
  135. package/dist/economy/currency.js +12 -1
  136. package/dist/economy/listingBook.d.ts +91 -0
  137. package/dist/economy/listingBook.js +144 -0
  138. package/dist/economy/priceHistory.d.ts +44 -0
  139. package/dist/economy/priceHistory.js +89 -0
  140. package/dist/economy/resourceLedger.d.ts +222 -0
  141. package/dist/economy/resourceLedger.js +353 -0
  142. package/dist/economy/sharedWallet.d.ts +14 -0
  143. package/dist/economy/sharedWallet.js +14 -0
  144. package/dist/economy/techTree.d.ts +12 -0
  145. package/dist/economy/techTree.js +12 -0
  146. package/dist/economy/unlockPoints.d.ts +92 -0
  147. package/dist/economy/unlockPoints.js +103 -0
  148. package/dist/economy/wallet.d.ts +26 -2
  149. package/dist/economy/wallet.js +35 -6
  150. package/dist/editor/commandApply.d.ts +5 -0
  151. package/dist/editor/commandApply.js +755 -0
  152. package/dist/editor/commands.d.ts +235 -7
  153. package/dist/editor/commands.js +82 -124
  154. package/dist/editor/directives.d.ts +94 -0
  155. package/dist/editor/directives.js +206 -0
  156. package/dist/editor/document.d.ts +194 -13
  157. package/dist/editor/document.js +1166 -19
  158. package/dist/editor/entityCatalog.d.ts +42 -0
  159. package/dist/editor/entityCatalog.js +101 -0
  160. package/dist/editor/environment.d.ts +74 -0
  161. package/dist/editor/environment.js +169 -0
  162. package/dist/editor/grid.d.ts +229 -0
  163. package/dist/editor/grid.js +366 -0
  164. package/dist/editor/gridAdapters.d.ts +80 -0
  165. package/dist/editor/gridAdapters.js +134 -0
  166. package/dist/editor/index.d.ts +98 -6
  167. package/dist/editor/index.js +13 -2
  168. package/dist/editor/liveSync.d.ts +127 -0
  169. package/dist/editor/liveSync.js +249 -0
  170. package/dist/editor/minimap.d.ts +31 -0
  171. package/dist/editor/minimap.js +73 -0
  172. package/dist/editor/runtimeInspector.d.ts +88 -0
  173. package/dist/editor/runtimeInspector.js +239 -0
  174. package/dist/editor/streamer.d.ts +66 -0
  175. package/dist/editor/streamer.js +68 -0
  176. package/dist/editor/types.d.ts +250 -1
  177. package/dist/editor/world.d.ts +136 -0
  178. package/dist/editor/world.js +303 -0
  179. package/dist/faction/reputation.d.ts +5 -0
  180. package/dist/faction/reputation.js +5 -0
  181. package/dist/format/distance.d.ts +17 -0
  182. package/dist/format/distance.js +18 -0
  183. package/dist/format/duration.d.ts +27 -0
  184. package/dist/format/duration.js +45 -0
  185. package/dist/format/speed.d.ts +17 -0
  186. package/dist/format/speed.js +25 -0
  187. package/dist/game/breeding.d.ts +161 -0
  188. package/dist/game/breeding.js +181 -0
  189. package/dist/game/cameraConfig.d.ts +451 -0
  190. package/dist/game/cameraConfig.js +1 -0
  191. package/dist/game/connectedPlayers.d.ts +6 -4
  192. package/dist/game/connectedPlayers.js +2 -2
  193. package/dist/game/cosmetics.d.ts +9 -0
  194. package/dist/game/cosmetics.js +16 -0
  195. package/dist/game/defineGame.d.ts +110 -5
  196. package/dist/game/defineGame.js +49 -4
  197. package/dist/game/defineSystem.d.ts +90 -0
  198. package/dist/game/defineSystem.js +47 -0
  199. package/dist/game/dialogue.d.ts +33 -0
  200. package/dist/game/dialogue.js +21 -0
  201. package/dist/game/events.d.ts +63 -13
  202. package/dist/game/events.js +5 -0
  203. package/dist/game/feed.d.ts +33 -0
  204. package/dist/game/feed.js +35 -2
  205. package/dist/game/gamePhase.d.ts +11 -0
  206. package/dist/game/gamePhase.js +16 -0
  207. package/dist/game/leaderboard.d.ts +5 -0
  208. package/dist/game/leaderboard.js +5 -0
  209. package/dist/game/loadout.d.ts +5 -0
  210. package/dist/game/loadout.js +5 -0
  211. package/dist/game/lootFilter.d.ts +2 -0
  212. package/dist/game/lootFilter.js +2 -0
  213. package/dist/game/lootPipeline.d.ts +164 -0
  214. package/dist/game/lootPipeline.js +216 -0
  215. package/dist/game/lootTable.d.ts +19 -2
  216. package/dist/game/lootTable.js +15 -5
  217. package/dist/game/objectives.d.ts +43 -0
  218. package/dist/game/objectives.js +36 -0
  219. package/dist/game/paramLayers.d.ts +148 -0
  220. package/dist/game/paramLayers.js +185 -0
  221. package/dist/game/ping.d.ts +5 -0
  222. package/dist/game/ping.js +5 -0
  223. package/dist/game/playableGame.d.ts +116 -432
  224. package/dist/game/playableGame.js +1 -1
  225. package/dist/game/progression.d.ts +35 -0
  226. package/dist/game/progression.js +19 -0
  227. package/dist/game/quest.d.ts +8 -0
  228. package/dist/game/quest.js +30 -3
  229. package/dist/game/race.d.ts +141 -0
  230. package/dist/game/race.js +169 -0
  231. package/dist/game/recordBook.d.ts +3 -1
  232. package/dist/game/recordBook.js +4 -1
  233. package/dist/game/ruleSelection.d.ts +75 -0
  234. package/dist/game/ruleSelection.js +127 -0
  235. package/dist/game/runDraft.d.ts +5 -0
  236. package/dist/game/runDraft.js +5 -0
  237. package/dist/game/saveStore.d.ts +111 -0
  238. package/dist/game/saveStore.js +312 -0
  239. package/dist/game/snapshotHistory.d.ts +67 -0
  240. package/dist/game/snapshotHistory.js +95 -0
  241. package/dist/game/social.d.ts +38 -1
  242. package/dist/game/social.js +71 -0
  243. package/dist/game/spawnPoints.d.ts +5 -0
  244. package/dist/game/spawnPoints.js +5 -0
  245. package/dist/game/systemRuntime.d.ts +50 -0
  246. package/dist/game/systemRuntime.js +157 -0
  247. package/dist/game/systemSchedule.d.ts +72 -0
  248. package/dist/game/systemSchedule.js +242 -0
  249. package/dist/game/toasts.d.ts +43 -0
  250. package/dist/game/toasts.js +44 -0
  251. package/dist/game/trade.d.ts +10 -0
  252. package/dist/game/trade.js +10 -0
  253. package/dist/game/tribe.d.ts +184 -0
  254. package/dist/game/tribe.js +432 -0
  255. package/dist/game/unlocks.d.ts +8 -0
  256. package/dist/game/unlocks.js +16 -0
  257. package/dist/game/vfxInstance.d.ts +138 -0
  258. package/dist/game/vfxInstance.js +96 -0
  259. package/dist/game/worldItem.d.ts +17 -4
  260. package/dist/game/worldItem.js +17 -4
  261. package/dist/gameplay.d.ts +93 -0
  262. package/dist/gameplay.js +93 -0
  263. package/dist/index.d.ts +5 -14
  264. package/dist/index.js +5 -14
  265. package/dist/input/actionBindings.d.ts +23 -6
  266. package/dist/input/actionBindings.js +23 -6
  267. package/dist/input/axisInput.d.ts +7 -2
  268. package/dist/input/axisInput.js +7 -2
  269. package/dist/input/controlGroups.d.ts +67 -0
  270. package/dist/input/controlGroups.js +41 -0
  271. package/dist/input/touchScheme.d.ts +82 -6
  272. package/dist/input/touchScheme.js +123 -10
  273. package/dist/interaction/lockpick.d.ts +61 -0
  274. package/dist/interaction/lockpick.js +168 -0
  275. package/dist/interaction/proximityPrompt.d.ts +2 -0
  276. package/dist/interaction/proximityPrompt.js +2 -0
  277. package/dist/interaction/qte.d.ts +5 -0
  278. package/dist/interaction/qte.js +5 -0
  279. package/dist/interaction/skillCheck.d.ts +5 -0
  280. package/dist/interaction/skillCheck.js +5 -0
  281. package/dist/inventory/encumbrance.d.ts +69 -0
  282. package/dist/inventory/encumbrance.js +68 -0
  283. package/dist/inventory/inventoryModel.d.ts +12 -0
  284. package/dist/inventory/inventoryModel.js +12 -0
  285. package/dist/inventory/shapedGrid.d.ts +16 -0
  286. package/dist/inventory/shapedGrid.js +16 -0
  287. package/dist/inventory/slotModel.d.ts +10 -0
  288. package/dist/inventory/slotModel.js +10 -0
  289. package/dist/item/durability.d.ts +5 -0
  290. package/dist/item/durability.js +6 -3
  291. package/dist/item/itemIdentity.d.ts +171 -0
  292. package/dist/item/itemIdentity.js +138 -0
  293. package/dist/item/itemInstanceRegistry.d.ts +32 -0
  294. package/dist/item/itemInstanceRegistry.js +34 -0
  295. package/dist/item/itemgen.d.ts +163 -0
  296. package/dist/item/itemgen.js +151 -0
  297. package/dist/item/modularItem.d.ts +5 -0
  298. package/dist/item/modularItem.js +5 -0
  299. package/dist/item/use.d.ts +5 -0
  300. package/dist/item/use.js +5 -0
  301. package/dist/item/useBehavior.d.ts +142 -0
  302. package/dist/item/useBehavior.js +105 -0
  303. package/dist/item/weapon.d.ts +5 -0
  304. package/dist/item/weapon.js +5 -0
  305. package/dist/math/scalar.d.ts +25 -0
  306. package/dist/math/scalar.js +47 -0
  307. package/dist/meta/changelog.d.ts +4 -1
  308. package/dist/meta/changelog.js +39 -2
  309. package/dist/movement/avoidance.d.ts +45 -0
  310. package/dist/movement/avoidance.js +123 -0
  311. package/dist/movement/cameraRig.d.ts +8 -0
  312. package/dist/movement/cameraRig.js +8 -0
  313. package/dist/movement/dash.d.ts +11 -0
  314. package/dist/movement/dash.js +11 -0
  315. package/dist/movement/formation.d.ts +135 -0
  316. package/dist/movement/formation.js +194 -0
  317. package/dist/movement/glideModel.d.ts +5 -0
  318. package/dist/movement/glideModel.js +5 -0
  319. package/dist/movement/grappleSwing.d.ts +5 -0
  320. package/dist/movement/grappleSwing.js +5 -0
  321. package/dist/movement/leaderTrail.d.ts +5 -0
  322. package/dist/movement/leaderTrail.js +5 -0
  323. package/dist/movement/movementModel.d.ts +50 -9
  324. package/dist/movement/movementModel.js +75 -29
  325. package/dist/movement/playerMovement.js +167 -12
  326. package/dist/movement/poseState.d.ts +13 -0
  327. package/dist/movement/poseState.js +16 -0
  328. package/dist/movement/steering.d.ts +8 -0
  329. package/dist/movement/steering.js +18 -0
  330. package/dist/multiplayer/combatSnapshot.d.ts +3 -0
  331. package/dist/multiplayer/combatSnapshot.js +16 -8
  332. package/dist/multiplayer/lagCompensation.d.ts +5 -0
  333. package/dist/multiplayer/lagCompensation.js +5 -0
  334. package/dist/multiplayer/presenceModel.d.ts +11 -1
  335. package/dist/multiplayer/presenceModel.js +17 -1
  336. package/dist/multiplayer/simultaneousCommit.d.ts +3 -0
  337. package/dist/multiplayer/simultaneousCommit.js +3 -0
  338. package/dist/multiplayer.d.ts +11 -0
  339. package/dist/multiplayer.js +11 -0
  340. package/dist/nav/pathFollow.d.ts +57 -0
  341. package/dist/nav/pathFollow.js +109 -0
  342. package/dist/orders/orderKinds.d.ts +156 -0
  343. package/dist/orders/orderKinds.js +197 -0
  344. package/dist/orders/orderQueue.d.ts +208 -0
  345. package/dist/orders/orderQueue.js +211 -0
  346. package/dist/physics/drivableVehicle.d.ts +31 -0
  347. package/dist/physics/drivableVehicle.js +16 -0
  348. package/dist/physics/flightDynamics.d.ts +82 -0
  349. package/dist/physics/flightDynamics.js +165 -0
  350. package/dist/physics/gravityField.d.ts +25 -0
  351. package/dist/physics/gravityField.js +41 -0
  352. package/dist/physics/kinematicVehicle.d.ts +108 -1
  353. package/dist/physics/kinematicVehicle.js +212 -15
  354. package/dist/physics/physicsWorld.d.ts +50 -12
  355. package/dist/physics/physicsWorld.js +120 -122
  356. package/dist/physics/radialImpulse.d.ts +25 -0
  357. package/dist/physics/radialImpulse.js +27 -0
  358. package/dist/physics/vehicleObstacles.d.ts +44 -0
  359. package/dist/physics/vehicleObstacles.js +115 -0
  360. package/dist/procedural.d.ts +3 -0
  361. package/dist/procedural.js +3 -0
  362. package/dist/progression/statGraph.d.ts +116 -0
  363. package/dist/progression/statGraph.js +318 -0
  364. package/dist/puzzle/cellGrid.d.ts +9 -0
  365. package/dist/puzzle/cellGrid.js +9 -0
  366. package/dist/puzzle/fallingPiece.d.ts +9 -0
  367. package/dist/puzzle/fallingPiece.js +9 -0
  368. package/dist/puzzle/nonogram.d.ts +39 -0
  369. package/dist/puzzle/nonogram.js +136 -0
  370. package/dist/random/nameGen.d.ts +5 -0
  371. package/dist/random/nameGen.js +5 -0
  372. package/dist/random/pick.d.ts +8 -0
  373. package/dist/random/pick.js +31 -0
  374. package/dist/random/rng.d.ts +26 -1
  375. package/dist/random/rng.js +32 -9
  376. package/dist/random/seedLink.d.ts +4 -0
  377. package/dist/random/seedLink.js +4 -0
  378. package/dist/relation/keyedValues.d.ts +54 -0
  379. package/dist/relation/keyedValues.js +115 -0
  380. package/dist/relation/thresholds.d.ts +62 -0
  381. package/dist/relation/thresholds.js +62 -0
  382. package/dist/render/color.d.ts +12 -0
  383. package/dist/render/color.js +26 -0
  384. package/dist/render/lookPreset.d.ts +41 -0
  385. package/dist/render/lookPreset.js +29 -0
  386. package/dist/render/postProcessing.d.ts +20 -0
  387. package/dist/render/postProcessing.js +21 -1
  388. package/dist/rules/predicate.d.ts +53 -0
  389. package/dist/rules/predicate.js +64 -0
  390. package/dist/rules/ruleEffects.d.ts +38 -0
  391. package/dist/rules/ruleEffects.js +37 -0
  392. package/dist/rules/triggeredRules.d.ts +160 -0
  393. package/dist/rules/triggeredRules.js +241 -0
  394. package/dist/runtime/adapter.d.ts +55 -3
  395. package/dist/runtime/adapter.js +57 -1
  396. package/dist/runtime/cameraDirector.d.ts +1 -0
  397. package/dist/runtime/cameraDirector.js +1 -0
  398. package/dist/runtime/commandRunner.d.ts +1 -0
  399. package/dist/runtime/commandRunner.js +1 -0
  400. package/dist/runtime/context/combat.d.ts +51 -0
  401. package/dist/runtime/context/combat.js +130 -0
  402. package/dist/runtime/context/combatFx.d.ts +23 -0
  403. package/dist/runtime/context/combatFx.js +161 -0
  404. package/dist/runtime/context/deathLoot.d.ts +30 -0
  405. package/dist/runtime/context/deathLoot.js +39 -0
  406. package/dist/runtime/context/player.d.ts +48 -0
  407. package/dist/runtime/context/player.js +125 -0
  408. package/dist/runtime/context/registries.d.ts +13 -0
  409. package/dist/runtime/context/registries.js +84 -0
  410. package/dist/runtime/context/scene.d.ts +59 -0
  411. package/dist/runtime/context/scene.js +291 -0
  412. package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
  413. package/dist/runtime/context/snapshotCodecs.js +26 -0
  414. package/dist/runtime/context/worldItems.d.ts +21 -0
  415. package/dist/runtime/context/worldItems.js +37 -0
  416. package/dist/runtime/descriptors/baseline.d.ts +48 -0
  417. package/dist/runtime/descriptors/baseline.js +157 -0
  418. package/dist/runtime/descriptors/features.d.ts +79 -0
  419. package/dist/runtime/descriptors/features.js +299 -0
  420. package/dist/runtime/gameContext.d.ts +2 -327
  421. package/dist/runtime/gameContext.js +273 -778
  422. package/dist/runtime/gameContextTypes.d.ts +467 -0
  423. package/dist/runtime/gameContextTypes.js +1 -0
  424. package/dist/runtime/gameRuntime.d.ts +1 -0
  425. package/dist/runtime/gameRuntime.js +1 -0
  426. package/dist/runtime/headlessRunner.d.ts +62 -0
  427. package/dist/runtime/headlessRunner.js +45 -0
  428. package/dist/runtime/hostPersistence.d.ts +12 -0
  429. package/dist/runtime/hostPersistence.js +12 -0
  430. package/dist/runtime/hostPolicy.d.ts +51 -0
  431. package/dist/runtime/hostPolicy.js +68 -0
  432. package/dist/runtime/hostedGameRunner.d.ts +11 -4
  433. package/dist/runtime/hostedGameRunner.js +27 -3
  434. package/dist/runtime/hostedWorldSession.d.ts +15 -5
  435. package/dist/runtime/hostedWorldSession.js +12 -4
  436. package/dist/runtime/inputSnapshot.d.ts +8 -3
  437. package/dist/runtime/inputSnapshot.js +6 -2
  438. package/dist/runtime/motionIntents.d.ts +11 -2
  439. package/dist/runtime/motionIntents.js +16 -2
  440. package/dist/runtime/perContext.d.ts +15 -0
  441. package/dist/runtime/perContext.js +21 -0
  442. package/dist/runtime/runtimeSave.d.ts +79 -0
  443. package/dist/runtime/runtimeSave.js +105 -0
  444. package/dist/runtime/save.d.ts +4 -0
  445. package/dist/runtime/save.js +4 -0
  446. package/dist/runtime/snapshot.d.ts +7 -0
  447. package/dist/runtime/snapshot.js +7 -0
  448. package/dist/runtime/visibility.d.ts +2 -1
  449. package/dist/runtime/visibility.js +2 -1
  450. package/dist/runtime/worldChannel.d.ts +7 -3
  451. package/dist/runtime/worldChannel.js +51 -6
  452. package/dist/runtime/worldMirror.d.ts +14 -2
  453. package/dist/runtime/worldMirror.js +15 -2
  454. package/dist/runtime/worldProjection.d.ts +47 -0
  455. package/dist/runtime/worldProjection.js +52 -0
  456. package/dist/runtime/worldReplication.d.ts +30 -7
  457. package/dist/runtime/worldReplication.js +41 -9
  458. package/dist/runtime/worldSnapshot.d.ts +42 -3
  459. package/dist/runtime/worldSnapshot.js +34 -7
  460. package/dist/scene/assetCatalog.d.ts +26 -0
  461. package/dist/scene/assetCatalog.js +4 -0
  462. package/dist/scene/assetDiagnostics.d.ts +69 -0
  463. package/dist/scene/assetDiagnostics.js +110 -0
  464. package/dist/scene/assetGenerator.d.ts +65 -0
  465. package/dist/scene/assetGenerator.js +64 -0
  466. package/dist/scene/assetSpace.d.ts +137 -0
  467. package/dist/scene/assetSpace.js +257 -0
  468. package/dist/scene/authoredTriggers.d.ts +219 -0
  469. package/dist/scene/authoredTriggers.js +454 -0
  470. package/dist/scene/behaviorRuntime.d.ts +75 -0
  471. package/dist/scene/behaviorRuntime.js +217 -0
  472. package/dist/scene/behaviors.d.ts +14 -2
  473. package/dist/scene/behaviors.js +7 -2
  474. package/dist/scene/bodyBind.d.ts +60 -0
  475. package/dist/scene/bodyBind.js +51 -0
  476. package/dist/scene/builtinSceneKinds.d.ts +5 -0
  477. package/dist/scene/builtinSceneKinds.js +67 -0
  478. package/dist/scene/colliders.d.ts +71 -1
  479. package/dist/scene/colliders.js +145 -1
  480. package/dist/scene/collisionMesh.d.ts +94 -0
  481. package/dist/scene/collisionMesh.js +699 -0
  482. package/dist/scene/companion.d.ts +147 -0
  483. package/dist/scene/companion.js +193 -0
  484. package/dist/scene/entityStats.d.ts +18 -11
  485. package/dist/scene/entityStats.js +21 -16
  486. package/dist/scene/entityStore.d.ts +12 -1
  487. package/dist/scene/entityStore.js +15 -0
  488. package/dist/scene/facing.d.ts +18 -0
  489. package/dist/scene/facing.js +26 -0
  490. package/dist/scene/modelSockets.d.ts +33 -0
  491. package/dist/scene/modelSockets.js +29 -0
  492. package/dist/scene/objectQuery.d.ts +2 -0
  493. package/dist/scene/objectQuery.js +2 -0
  494. package/dist/scene/objectStore.d.ts +4 -0
  495. package/dist/scene/objectStore.js +6 -0
  496. package/dist/scene/possession.d.ts +8 -0
  497. package/dist/scene/possession.js +14 -0
  498. package/dist/scene/roster.d.ts +3 -0
  499. package/dist/scene/roster.js +12 -0
  500. package/dist/scene/sceneKinds.d.ts +256 -0
  501. package/dist/scene/sceneKinds.js +218 -0
  502. package/dist/scene/sceneOwnership.d.ts +169 -0
  503. package/dist/scene/sceneOwnership.js +179 -0
  504. package/dist/scene/sceneRaycast.js +15 -0
  505. package/dist/scene/selectionBookmarks.d.ts +93 -0
  506. package/dist/scene/selectionBookmarks.js +122 -0
  507. package/dist/scene/vehicleSeat.d.ts +68 -0
  508. package/dist/scene/vehicleSeat.js +59 -0
  509. package/dist/sensor/concealment.d.ts +3 -0
  510. package/dist/sensor/concealment.js +3 -0
  511. package/dist/sensor/freezeMonitor.d.ts +1 -0
  512. package/dist/sensor/freezeMonitor.js +1 -0
  513. package/dist/sensor/frustumSensor.d.ts +7 -3
  514. package/dist/sensor/frustumSensor.js +7 -3
  515. package/dist/sensor/hiddenStateProbe.d.ts +5 -2
  516. package/dist/sensor/hiddenStateProbe.js +5 -2
  517. package/dist/sensor/recordingBuffer.d.ts +2 -1
  518. package/dist/sensor/recordingBuffer.js +2 -1
  519. package/dist/sensor/replayLoop.d.ts +6 -2
  520. package/dist/sensor/replayLoop.js +6 -2
  521. package/dist/sensor/revealQuery.d.ts +2 -1
  522. package/dist/sensor/revealQuery.js +2 -1
  523. package/dist/sensor/visionCone.d.ts +10 -3
  524. package/dist/sensor/visionCone.js +10 -3
  525. package/dist/session/contestedChannel.d.ts +1 -0
  526. package/dist/session/contestedChannel.js +1 -0
  527. package/dist/session/extraction.d.ts +1 -0
  528. package/dist/session/extraction.js +1 -0
  529. package/dist/session/roles.d.ts +6 -0
  530. package/dist/session/roles.js +6 -0
  531. package/dist/session/roundState.d.ts +8 -0
  532. package/dist/session/roundState.js +2 -0
  533. package/dist/settings/settingsModel.d.ts +1 -0
  534. package/dist/settings/settingsModel.js +1 -0
  535. package/dist/stats/accumulatorMeter.d.ts +6 -0
  536. package/dist/stats/accumulatorMeter.js +6 -0
  537. package/dist/stats/eventMeter.d.ts +5 -0
  538. package/dist/stats/eventMeter.js +5 -0
  539. package/dist/stats/rollCheck.d.ts +5 -0
  540. package/dist/stats/rollCheck.js +5 -0
  541. package/dist/stats/spawnLevelStats.d.ts +110 -0
  542. package/dist/stats/spawnLevelStats.js +140 -0
  543. package/dist/stats/statModifiers.d.ts +5 -0
  544. package/dist/stats/statModifiers.js +5 -0
  545. package/dist/stats/statPool.d.ts +70 -0
  546. package/dist/stats/statPool.js +59 -0
  547. package/dist/store/changeSignal.d.ts +3 -0
  548. package/dist/store/changeSignal.js +2 -0
  549. package/dist/store/defineKeyedStore.d.ts +38 -0
  550. package/dist/store/defineKeyedStore.js +42 -0
  551. package/dist/store/defineStore.d.ts +33 -0
  552. package/dist/store/defineStore.js +35 -0
  553. package/dist/store/observableKeyedStore.d.ts +7 -0
  554. package/dist/store/observableKeyedStore.js +35 -2
  555. package/dist/survival/decayMeter.d.ts +55 -0
  556. package/dist/survival/decayMeter.js +130 -57
  557. package/dist/survival/moodle.d.ts +2 -0
  558. package/dist/survival/regionHealth.d.ts +6 -0
  559. package/dist/survival/regionHealth.js +6 -3
  560. package/dist/tactics/fallingGrid.d.ts +4 -2
  561. package/dist/tactics/fallingGrid.js +4 -2
  562. package/dist/tactics/predictiveQuery.d.ts +3 -0
  563. package/dist/tactics/predictiveQuery.js +3 -0
  564. package/dist/tactics/snapshot.d.ts +2 -0
  565. package/dist/tactics/snapshot.js +2 -0
  566. package/dist/tactics/surface.d.ts +1 -0
  567. package/dist/tactics/surface.js +1 -0
  568. package/dist/tactics/tacticalGrid.d.ts +1 -0
  569. package/dist/tactics/tacticalGrid.js +1 -0
  570. package/dist/time/beatClock.d.ts +14 -4
  571. package/dist/time/beatClock.js +14 -4
  572. package/dist/time/calendarClock.d.ts +3 -0
  573. package/dist/time/calendarClock.js +3 -0
  574. package/dist/time/idleProgress.d.ts +4 -0
  575. package/dist/time/idleProgress.js +4 -0
  576. package/dist/time/simClock.d.ts +2 -0
  577. package/dist/time/simClock.js +20 -5
  578. package/dist/ui/actionModel.d.ts +110 -0
  579. package/dist/ui/actionModel.js +159 -0
  580. package/dist/ui/gameLayout.d.ts +30 -11
  581. package/dist/ui/gameLayout.js +30 -11
  582. package/dist/ui/hudDocument.d.ts +114 -0
  583. package/dist/ui/hudDocument.js +220 -0
  584. package/dist/ui/hudLayout.d.ts +38 -1
  585. package/dist/ui/hudLayout.js +220 -17
  586. package/dist/ui/selectionModel.d.ts +92 -0
  587. package/dist/ui/selectionModel.js +88 -0
  588. package/dist/ui/tooltipModel.d.ts +63 -0
  589. package/dist/ui/tooltipModel.js +82 -0
  590. package/dist/ui.d.ts +15 -0
  591. package/dist/ui.js +15 -0
  592. package/dist/visibility/bounds.d.ts +8 -1
  593. package/dist/visibility/bounds.js +8 -1
  594. package/dist/visibility/frustum.d.ts +12 -3
  595. package/dist/visibility/frustum.js +12 -3
  596. package/dist/work/jobQueue.d.ts +201 -0
  597. package/dist/work/jobQueue.js +253 -0
  598. package/dist/work/unitTraining.d.ts +58 -0
  599. package/dist/work/unitTraining.js +52 -0
  600. package/dist/world/authoredEntities.d.ts +29 -0
  601. package/dist/world/authoredEntities.js +29 -0
  602. package/dist/world/authoredObjects.d.ts +72 -0
  603. package/dist/world/authoredObjects.js +81 -0
  604. package/dist/world/authoredSpawn.d.ts +39 -0
  605. package/dist/world/authoredSpawn.js +37 -0
  606. package/dist/world/buildSockets.d.ts +152 -0
  607. package/dist/world/buildSockets.js +188 -0
  608. package/dist/world/buildingGenerator.d.ts +18 -0
  609. package/dist/world/buildingGenerator.js +62 -0
  610. package/dist/world/buildings.d.ts +5 -0
  611. package/dist/world/buildings.js +5 -0
  612. package/dist/world/catenary.d.ts +22 -0
  613. package/dist/world/catenary.js +69 -0
  614. package/dist/world/cityBlocks.d.ts +152 -0
  615. package/dist/world/cityBlocks.js +670 -0
  616. package/dist/world/cityContent.d.ts +77 -0
  617. package/dist/world/cityContent.js +261 -0
  618. package/dist/world/cityGeometry.d.ts +114 -0
  619. package/dist/world/cityGeometry.js +573 -0
  620. package/dist/world/cityKind.d.ts +314 -0
  621. package/dist/world/cityKind.js +1578 -0
  622. package/dist/world/features.d.ts +133 -10
  623. package/dist/world/features.js +66 -6
  624. package/dist/world/footprintGrid.d.ts +59 -0
  625. package/dist/world/footprintGrid.js +141 -0
  626. package/dist/world/geometry.d.ts +64 -0
  627. package/dist/world/geometry.js +94 -0
  628. package/dist/world/grassKind.d.ts +51 -0
  629. package/dist/world/grassKind.js +101 -0
  630. package/dist/world/markers.d.ts +50 -4
  631. package/dist/world/markers.js +49 -0
  632. package/dist/world/massing.js +1 -1
  633. package/dist/world/minimapBake.d.ts +78 -0
  634. package/dist/world/minimapBake.js +208 -0
  635. package/dist/world/minimapTrack.d.ts +17 -0
  636. package/dist/world/minimapTrack.js +22 -0
  637. package/dist/world/pathInstances.d.ts +27 -0
  638. package/dist/world/pathInstances.js +27 -0
  639. package/dist/world/pathNetwork.d.ts +159 -0
  640. package/dist/world/pathNetwork.js +684 -0
  641. package/dist/world/pathTerrain.d.ts +80 -0
  642. package/dist/world/pathTerrain.js +142 -0
  643. package/dist/world/place.d.ts +166 -0
  644. package/dist/world/place.js +96 -0
  645. package/dist/world/placeAsset.d.ts +81 -0
  646. package/dist/world/placeAsset.js +79 -0
  647. package/dist/world/placement.d.ts +4 -0
  648. package/dist/world/placement.js +4 -0
  649. package/dist/world/placementController.d.ts +6 -0
  650. package/dist/world/placementController.js +6 -0
  651. package/dist/world/poleLineKind.d.ts +40 -0
  652. package/dist/world/poleLineKind.js +97 -0
  653. package/dist/world/polyline.d.ts +34 -0
  654. package/dist/world/polyline.js +88 -0
  655. package/dist/world/resourceNode.d.ts +109 -0
  656. package/dist/world/resourceNode.js +140 -0
  657. package/dist/world/roads.d.ts +8 -0
  658. package/dist/world/roads.js +38 -0
  659. package/dist/world/scatter.js +2 -20
  660. package/dist/world/scatterRegion.d.ts +164 -0
  661. package/dist/world/scatterRegion.js +409 -0
  662. package/dist/world/sceneShapes.d.ts +43 -0
  663. package/dist/world/sceneShapes.js +1 -0
  664. package/dist/world/segment.d.ts +27 -0
  665. package/dist/world/segment.js +43 -0
  666. package/dist/world/soilKind.d.ts +48 -0
  667. package/dist/world/soilKind.js +92 -0
  668. package/dist/world/spatialSample.d.ts +241 -0
  669. package/dist/world/spatialSample.js +465 -0
  670. package/dist/world/streets.d.ts +29 -0
  671. package/dist/world/streets.js +55 -0
  672. package/dist/world/terraform.d.ts +213 -1
  673. package/dist/world/terraform.js +627 -22
  674. package/dist/world/terrain.d.ts +164 -20
  675. package/dist/world/terrain.js +325 -41
  676. package/dist/world/terrainGuides.d.ts +182 -0
  677. package/dist/world/terrainGuides.js +343 -0
  678. package/dist/world/vec2.d.ts +81 -0
  679. package/dist/world/vec2.js +128 -0
  680. package/dist/world/vec3.d.ts +49 -0
  681. package/dist/world/vec3.js +76 -0
  682. package/dist/world/vegetation.d.ts +71 -0
  683. package/dist/world/vegetation.js +151 -0
  684. package/dist/world/visibilityField.d.ts +154 -0
  685. package/dist/world/visibilityField.js +271 -0
  686. package/dist/world/volumetricClouds.d.ts +52 -0
  687. package/dist/world/volumetricClouds.js +43 -0
  688. package/dist/world/walls.d.ts +9 -0
  689. package/dist/world/walls.js +9 -0
  690. package/dist/world/waterKind.d.ts +55 -0
  691. package/dist/world/waterKind.js +99 -0
  692. package/dist/world.d.ts +141 -0
  693. package/dist/world.js +141 -0
  694. package/package.json +32 -8
  695. package/skills/game-design/SKILL.md +28 -0
  696. package/skills/game-design/agents/openai.yaml +4 -0
  697. package/skills/game-design/references/application-playbook.md +170 -0
  698. package/skills/game-design/references/field-guide.md +288 -0
  699. package/skills/jgengine/SKILL.md +54 -0
  700. package/skills/jgengine/api.md +527 -0
  701. package/skills/jgengine/capabilities.md +131 -0
  702. package/skills/jgengine/recipes/minimal-game.md +88 -0
  703. package/skills/jgengine-combat/SKILL.md +38 -0
  704. package/skills/jgengine-combat/api.md +585 -0
  705. package/skills/jgengine-combat/capabilities.md +213 -0
  706. package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
  707. package/skills/jgengine-combat/reference.md +108 -0
  708. package/skills/jgengine-gameplay/SKILL.md +39 -0
  709. package/skills/jgengine-gameplay/api.md +1920 -0
  710. package/skills/jgengine-gameplay/capabilities.md +454 -0
  711. package/skills/jgengine-gameplay/reference-systems.md +56 -0
  712. package/skills/jgengine-world/SKILL.md +51 -0
  713. package/skills/jgengine-world/api.md +3056 -0
  714. package/skills/jgengine-world/capabilities.md +476 -0
  715. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  716. package/skills/jgengine-world/reference.md +261 -0
  717. package/dist/cartridge/runtime.d.ts +0 -15
  718. package/dist/cartridge/runtime.js +0 -556
  719. package/dist/cartridge/spec.d.ts +0 -188
  720. package/dist/cartridge/spec.js +0 -19
  721. package/dist/cartridge/validate.d.ts +0 -2
  722. package/dist/cartridge/validate.js +0 -78
  723. package/dist/store/engineStore.d.ts +0 -22
  724. package/dist/store/engineStore.js +0 -50
  725. package/dist/store/features.d.ts +0 -10
  726. package/dist/store/features.js +0 -23
  727. package/llms.txt +0 -5389
@@ -0,0 +1,684 @@
1
+ /**
2
+ * The unified, seed-driven procedural PATH NETWORK — one generator that grows an entire road/track
3
+ * graph inside a box volume and answers to sliders instead of hardcoded geometry. It is deliberately
4
+ * genre-agnostic: a city street net and a closed race circuit are the *same* engine at opposite
5
+ * slider extremes, not two code paths. Drop a volume, pick a seed, and turn the dials:
6
+ *
7
+ * - `gridness` — 1 lays nodes on a regular lattice (Manhattan); 0 scatters them (organic hills).
8
+ * - `loopiness` — 0 grows a tree that dead-ends; 1 collapses to a single closed loop (a circuit).
9
+ * - `connectivity` — extra chord edges between neighbors → mesh density (a dense city grid).
10
+ * - `branching` — spur lanes forking off the mains, ending in cul-de-sacs.
11
+ * - `deadEnds` — fraction of dangling ends KEPT as cul-de-sacs vs reconnected into loops.
12
+ * - `winding` — sideways wander amplitude of every edge, capped so curvature never exceeds
13
+ * `1 / minCurveRadius`; `minTurnAngle`/`maxTurnAngle` are HARD clamps on the corner a street may
14
+ * take at any vertex (shallow wiggles straightened, hairpins beveled away).
15
+ * - `bridges` / `tunnels` (with a ground sampler) — a span that dives under `minElevation` becomes a
16
+ * BRIDGE deck; a span buried under a ridge becomes a TUNNEL bore. Both are path FEATURES on a
17
+ * continuous edge, so a circuit stays closed as it crosses water or pierces a hill.
18
+ *
19
+ * Output is two coupled views of the graph: atomic node-to-node {@link PathEdge}s (fed straight into
20
+ * the block/parcel fabric — a race circuit is many edges between distinct nodes, never one fragile
21
+ * self-loop) and chained {@link PathStreet}s (through-streets for rendering, furniture, and
22
+ * junctions). Pure deterministic math — same rules + seed + volume ⇒ identical network — with bounded
23
+ * work caps so a huge volume can never generate unbounded content. Local (volume-centered) coords;
24
+ * the caller rotates/translates into world space.
25
+ *
26
+ * @capability path-network seed-driven procedural roads and race circuits from one slider-driven engine
27
+ */
28
+ import { seededStreams } from "../random/rng.js";
29
+ const TAU = Math.PI * 2;
30
+ const MAX_NODES = 900;
31
+ const MAX_STREETS = 360;
32
+ const MAX_EDGES = 1400;
33
+ const MAX_LANES = 260;
34
+ const LEVEL_RANK = { boulevard: 3, avenue: 2, street: 1, lane: 0 };
35
+ const WIDTH_MULT = { boulevard: 2.2, avenue: 1.5, street: 1, lane: 0.65 };
36
+ /** Turn angle (radians, 0..π) at `b` going a→b→c; 0 = straight, π = full reversal. */
37
+ function turnAngle(a, b, c) {
38
+ const ux = b[0] - a[0];
39
+ const uz = b[1] - a[1];
40
+ const vx = c[0] - b[0];
41
+ const vz = c[1] - b[1];
42
+ const lu = Math.hypot(ux, uz);
43
+ const lv = Math.hypot(vx, vz);
44
+ if (lu < 1e-6 || lv < 1e-6)
45
+ return 0;
46
+ const dot = (ux * vx + uz * vz) / (lu * lv);
47
+ return Math.acos(Math.max(-1, Math.min(1, dot)));
48
+ }
49
+ /**
50
+ * Hard turn-angle clamp on a polyline with fixed endpoints: interior corners gentler than `minRad`
51
+ * are straightened away (deliberate winding only, no micro-wiggle) and corners sharper than `maxRad`
52
+ * are beveled — the corner vertex is replaced by two points pulled back along each leg, which
53
+ * strictly reduces the angle. Endpoints never move, so a graph node stays welded.
54
+ * @internal
55
+ */
56
+ export function clampTurns(points, minRad, maxRad) {
57
+ if (points.length < 3)
58
+ return points.map((p) => [p[0], p[1]]);
59
+ // 1. straighten pass — drop interior vertices whose corner is shallower than the minimum.
60
+ let pts = points.map((p) => [p[0], p[1]]);
61
+ if (minRad > 0) {
62
+ let changed = true;
63
+ let guard = 0;
64
+ while (changed && guard < 40) {
65
+ changed = false;
66
+ guard += 1;
67
+ const out = [pts[0]];
68
+ for (let i = 1; i < pts.length - 1; i += 1) {
69
+ const angle = turnAngle(out[out.length - 1], pts[i], pts[i + 1]);
70
+ if (angle > 1e-4 && angle < minRad) {
71
+ changed = true; // skip this vertex — collinearize the bend
72
+ continue;
73
+ }
74
+ out.push(pts[i]);
75
+ }
76
+ out.push(pts[pts.length - 1]);
77
+ pts = out;
78
+ }
79
+ }
80
+ // 2. bevel pass — repeatedly cut the sharpest over-max corner until all corners obey the ceiling.
81
+ let guard = 0;
82
+ while (guard < 200) {
83
+ guard += 1;
84
+ let worst = -1;
85
+ let worstAngle = maxRad;
86
+ for (let i = 1; i < pts.length - 1; i += 1) {
87
+ const angle = turnAngle(pts[i - 1], pts[i], pts[i + 1]);
88
+ if (angle > worstAngle) {
89
+ worstAngle = angle;
90
+ worst = i;
91
+ }
92
+ }
93
+ if (worst < 0)
94
+ break;
95
+ const a = pts[worst - 1];
96
+ const v = pts[worst];
97
+ const b = pts[worst + 1];
98
+ const la = Math.hypot(a[0] - v[0], a[1] - v[1]) || 1;
99
+ const lb = Math.hypot(b[0] - v[0], b[1] - v[1]) || 1;
100
+ const cut = 0.42;
101
+ const p0 = [v[0] + ((a[0] - v[0]) / la) * la * cut, v[1] + ((a[1] - v[1]) / la) * la * cut];
102
+ const p1 = [v[0] + ((b[0] - v[0]) / lb) * lb * cut, v[1] + ((b[1] - v[1]) / lb) * lb * cut];
103
+ pts.splice(worst, 1, p0, p1);
104
+ }
105
+ return pts;
106
+ }
107
+ /** Two-octave deterministic sideways wander, capped by a max amplitude. */
108
+ function makeWander(rng, amplitude, wavelength) {
109
+ const f1 = TAU / (wavelength * (0.75 + rng() * 0.5));
110
+ const f2 = f1 * (2.3 + rng() * 0.8);
111
+ const p1 = rng() * TAU;
112
+ const p2 = rng() * TAU;
113
+ return (t) => amplitude * (Math.sin(t * f1 + p1) * 0.65 + Math.sin(t * f2 + p2) * 0.35);
114
+ }
115
+ /** The wander amplitude for a segment, capped so peak curvature never dips under `minCurveRadius`. */
116
+ function windingAmplitude(rules, wavelength) {
117
+ const want = rules.winding * rules.segmentLength * 0.34;
118
+ // Peak curvature of A·sin(2πt/λ) is A·(2π/λ)²; keep radius ≥ minCurveRadius (0.35 covers the octave mix).
119
+ const cap = (0.35 * wavelength * wavelength) / (4 * Math.PI * Math.PI * Math.max(1, rules.minCurveRadius));
120
+ return Math.max(0, Math.min(want, cap));
121
+ }
122
+ /** Sample a straight chord a→b into a wandered polyline (endpoints pinned exactly on the nodes). */
123
+ function wanderEdge(a, b, rng, rules) {
124
+ const dx = b[0] - a[0];
125
+ const dz = b[1] - a[1];
126
+ const len = Math.hypot(dx, dz);
127
+ if (len < 1e-3)
128
+ return [[a[0], a[1]], [b[0], b[1]]];
129
+ const ux = dx / len;
130
+ const uz = dz / len;
131
+ const nx = -uz;
132
+ const nz = ux;
133
+ const wavelength = rules.segmentLength * (2.6 + rng() * 1.4);
134
+ const amp = windingAmplitude(rules, wavelength);
135
+ const wander = makeWander(rng, amp, wavelength);
136
+ const steps = Math.max(1, Math.ceil(len / Math.min(Math.max(rules.segmentLength / 3, 6), 16)));
137
+ const out = [];
138
+ for (let s = 0; s <= steps; s += 1) {
139
+ const t = s / steps;
140
+ // Window the wander to zero at both ends so the node coords stay exact for graph welding.
141
+ const window = Math.sin(Math.PI * t);
142
+ const off = amp === 0 ? 0 : wander(t * len) * window;
143
+ out.push([a[0] + ux * len * t + nx * off, a[1] + uz * len * t + nz * off]);
144
+ }
145
+ return out;
146
+ }
147
+ /** Lay seed nodes: a regular lattice at `gridness` 1, jittered toward organic scatter as it drops. */
148
+ function seedLattice(rules, hx, hz, rng) {
149
+ const spacingX = rules.segmentLength * Math.max(1, rules.aspect);
150
+ const spacingZ = rules.segmentLength;
151
+ const cols = Math.max(2, Math.min(Math.round((hx * 2) / spacingX), 30));
152
+ const rows = Math.max(2, Math.min(Math.round((hz * 2) / spacingZ), 30));
153
+ const dx = (hx * 2) / cols;
154
+ const dz = (hz * 2) / rows;
155
+ const jitter = (1 - rules.gridness) * 0.42;
156
+ const nodes = [];
157
+ for (let j = 0; j <= rows; j += 1) {
158
+ for (let i = 0; i <= cols; i += 1) {
159
+ if (nodes.length >= MAX_NODES)
160
+ break;
161
+ const baseX = -hx + i * dx;
162
+ const baseZ = -hz + j * dz;
163
+ // Edge-column/row nodes keep their x/z pinned to the rim so the network fills the footprint.
164
+ const jx = i === 0 || i === cols ? 0 : (rng() - 0.5) * dx * jitter;
165
+ const jz = j === 0 || j === rows ? 0 : (rng() - 0.5) * dz * jitter;
166
+ const x = Math.max(-hx, Math.min(hx, baseX + jx));
167
+ const z = Math.max(-hz, Math.min(hz, baseZ + jz));
168
+ nodes.push([x, z]);
169
+ }
170
+ }
171
+ const stride = cols + 1;
172
+ return { nodes, cols, rows, index: (i, j) => j * stride + i };
173
+ }
174
+ function makeUf(n) {
175
+ const parent = Array.from({ length: n }, (_, i) => i);
176
+ const find = (a) => {
177
+ while (parent[a] !== a) {
178
+ parent[a] = parent[parent[a]];
179
+ a = parent[a];
180
+ }
181
+ return a;
182
+ };
183
+ return {
184
+ find,
185
+ union: (a, b) => {
186
+ const ra = find(a);
187
+ const rb = find(b);
188
+ if (ra === rb)
189
+ return false;
190
+ parent[ra] = rb;
191
+ return true;
192
+ },
193
+ };
194
+ }
195
+ /** Fisher-Yates over an index array using a seeded stream. */
196
+ function shuffled(n, rng) {
197
+ const arr = Array.from({ length: n }, (_, i) => i);
198
+ for (let i = n - 1; i > 0; i -= 1) {
199
+ const j = Math.floor(rng() * (i + 1));
200
+ const t = arr[i];
201
+ arr[i] = arr[j];
202
+ arr[j] = t;
203
+ }
204
+ return arr;
205
+ }
206
+ /** Grow the open street NET: lattice nodes, a spanning tree (guaranteed connectivity), then chords,
207
+ * loop-closing reconnections, and spur lanes — all driven by the sliders. */
208
+ function buildNet(rules, hx, hz, streams) {
209
+ const lattice = seedLattice(rules, hx, hz, streams("lattice"));
210
+ const nodes = lattice.nodes;
211
+ const { cols, rows, index } = lattice;
212
+ // Candidate lattice edges: right + down neighbors.
213
+ const candidates = [];
214
+ for (let j = 0; j <= rows; j += 1) {
215
+ for (let i = 0; i <= cols; i += 1) {
216
+ const here = index(i, j);
217
+ if (here >= nodes.length)
218
+ continue;
219
+ if (i < cols) {
220
+ const r = index(i + 1, j);
221
+ if (r < nodes.length)
222
+ candidates.push({ a: here, b: r, lane: false });
223
+ }
224
+ if (j < rows) {
225
+ const d = index(i, j + 1);
226
+ if (d < nodes.length)
227
+ candidates.push({ a: here, b: d, lane: false });
228
+ }
229
+ }
230
+ }
231
+ const order = shuffled(candidates.length, streams("tree"));
232
+ const uf = makeUf(nodes.length);
233
+ const chosen = new Array(candidates.length).fill(false);
234
+ // 1. spanning tree — every node reachable ("roads that actually connect").
235
+ for (const idx of order) {
236
+ const e = candidates[idx];
237
+ if (uf.union(e.a, e.b))
238
+ chosen[idx] = true;
239
+ }
240
+ // 2. connectivity chords — extra mesh edges close grid loops.
241
+ const chordRng = streams("chords");
242
+ for (let k = 0; k < candidates.length; k += 1) {
243
+ if (chosen[k])
244
+ continue;
245
+ if (chordRng() < rules.connectivity)
246
+ chosen[k] = true;
247
+ }
248
+ const edges = [];
249
+ for (let k = 0; k < candidates.length; k += 1)
250
+ if (chosen[k])
251
+ edges.push(candidates[k]);
252
+ // 3. loopiness — reconnect leaf dead-ends into loops (add a chord to a nearby non-neighbor).
253
+ const degree = new Array(nodes.length).fill(0);
254
+ const adj = new Map();
255
+ const addAdj = (a, b) => {
256
+ (adj.get(a) ?? adj.set(a, new Set()).get(a)).add(b);
257
+ (adj.get(b) ?? adj.set(b, new Set()).get(b)).add(a);
258
+ degree[a] += 1;
259
+ degree[b] += 1;
260
+ };
261
+ for (const e of edges)
262
+ addAdj(e.a, e.b);
263
+ const loopRng = streams("loops");
264
+ for (let n = 0; n < nodes.length; n += 1) {
265
+ if (degree[n] !== 1)
266
+ continue;
267
+ const keep = loopRng() < rules.deadEnds;
268
+ if (keep)
269
+ continue; // becomes a cul-de-sac
270
+ if (loopRng() > rules.loopiness + 0.15)
271
+ continue; // otherwise mostly left as-is
272
+ // Reconnect to the nearest node that isn't already a neighbor, forming a loop.
273
+ let best = -1;
274
+ let bestD = Infinity;
275
+ for (let m = 0; m < nodes.length; m += 1) {
276
+ if (m === n || adj.get(n)?.has(m))
277
+ continue;
278
+ const d = Math.hypot(nodes[n][0] - nodes[m][0], nodes[n][1] - nodes[m][1]);
279
+ if (d < bestD && d < rules.segmentLength * 1.8) {
280
+ bestD = d;
281
+ best = m;
282
+ }
283
+ }
284
+ if (best >= 0) {
285
+ edges.push({ a: n, b: best, lane: false });
286
+ addAdj(n, best);
287
+ }
288
+ }
289
+ // 4. branching — spur lanes forking outward off existing nodes, ending in a fresh cul-de-sac node.
290
+ const laneCount = Math.min(Math.round(rules.branching * nodes.length * 0.6), MAX_LANES);
291
+ const branchRng = streams("branches");
292
+ for (let b = 0; b < laneCount && nodes.length < MAX_NODES; b += 1) {
293
+ const host = Math.floor(branchRng() * nodes.length);
294
+ const hp = nodes[host];
295
+ const angle = branchRng() * TAU;
296
+ const len = rules.segmentLength * (0.6 + branchRng() * 0.7);
297
+ const nx = hp[0] + Math.cos(angle) * len;
298
+ const nz = hp[1] + Math.sin(angle) * len;
299
+ if (Math.abs(nx) > hx - 1 || Math.abs(nz) > hz - 1)
300
+ continue;
301
+ // Reject spurs that would spawn on top of an existing node.
302
+ let clash = false;
303
+ for (let m = 0; m < nodes.length; m += 1) {
304
+ if (Math.hypot(nodes[m][0] - nx, nodes[m][1] - nz) < rules.segmentLength * 0.45) {
305
+ clash = true;
306
+ break;
307
+ }
308
+ }
309
+ if (clash)
310
+ continue;
311
+ const id = nodes.length;
312
+ nodes.push([nx, nz]);
313
+ edges.push({ a: host, b: id, lane: true });
314
+ }
315
+ return { nodes, edges: edges.slice(0, MAX_EDGES) };
316
+ }
317
+ /** Grow a closed CIRCUIT: nodes ordered around the volume centroid into one loop, radii smoothed so
318
+ * no corner exceeds the turn ceiling, with an optional pit-lane spur when branching is up. */
319
+ function buildCircuit(rules, hx, hz, streams) {
320
+ const rng = streams("circuit");
321
+ const rx = hx * 0.86;
322
+ const rz = hz * 0.86;
323
+ const perimeter = Math.PI * (rx + rz);
324
+ const k = Math.max(6, Math.min(Math.round(perimeter / rules.segmentLength), 40));
325
+ // Per-node radius factor: winding pulls the loop in and out to carve corners and chicanes.
326
+ const radii = [];
327
+ for (let i = 0; i < k; i += 1) {
328
+ const wobble = 1 - rules.winding * (0.12 + rng() * 0.4) * (0.5 + 0.5 * Math.sin(i * 1.7));
329
+ radii.push(Math.max(0.45, wobble));
330
+ }
331
+ // Smooth the radii so consecutive corners can't be sharper than the ceiling allows.
332
+ const smoothPasses = 2 + Math.round((1 - rules.maxTurnAngle / 180) * 4);
333
+ for (let pass = 0; pass < smoothPasses; pass += 1) {
334
+ const next = radii.slice();
335
+ for (let i = 0; i < k; i += 1) {
336
+ const prev = radii[(i - 1 + k) % k];
337
+ const cur = radii[i];
338
+ const nxt = radii[(i + 1) % k];
339
+ next[i] = cur * 0.5 + (prev + nxt) * 0.25;
340
+ }
341
+ for (let i = 0; i < k; i += 1)
342
+ radii[i] = next[i];
343
+ }
344
+ const nodes = [];
345
+ for (let i = 0; i < k; i += 1) {
346
+ const angle = (i / k) * TAU;
347
+ const f = radii[i];
348
+ nodes.push([Math.cos(angle) * rx * f, Math.sin(angle) * rz * f]);
349
+ }
350
+ const edges = [];
351
+ for (let i = 0; i < k; i += 1)
352
+ edges.push({ a: i, b: (i + 1) % k, lane: false });
353
+ // Optional pit-lane spur: a short dead-end tap off one node (only when branching is meaningful).
354
+ if (rules.branching > 0.25 && nodes.length < MAX_NODES) {
355
+ const host = Math.floor(rng() * k);
356
+ const hp = nodes[host];
357
+ const toCenter = Math.atan2(-hp[1], -hp[0]);
358
+ const len = rules.segmentLength * (0.8 + rng() * 0.6);
359
+ const nx = hp[0] + Math.cos(toCenter) * len;
360
+ const nz = hp[1] + Math.sin(toCenter) * len;
361
+ if (Math.abs(nx) < hx - 1 && Math.abs(nz) < hz - 1) {
362
+ const id = nodes.length;
363
+ nodes.push([nx, nz]);
364
+ edges.push({ a: host, b: id, lane: true });
365
+ }
366
+ }
367
+ return { nodes, edges };
368
+ }
369
+ /** Decide which topology family the sliders call for. Circuit wins when loops dominate and both
370
+ * branching and mesh connectivity are low — exactly the "race track" corner of the slider space.
371
+ * @internal */
372
+ export function pathNetworkMode(rules) {
373
+ const score = rules.loopiness * (1 - rules.branching * 0.7) * (1 - rules.connectivity * 0.7);
374
+ return score >= 0.45 ? "circuit" : "net";
375
+ }
376
+ /** Assign a hierarchy level per chained street: spurs are lanes, the longest runs become avenues,
377
+ * and a share of those upgrade to boulevards. */
378
+ function levelForStreets(chains, rules, rng) {
379
+ const order = chains
380
+ .map((c, i) => ({ i, length: c.length, lane: c.lane }))
381
+ .sort((a, b) => b.length - a.length);
382
+ const levels = new Array(chains.length).fill("street");
383
+ const avenueCut = Math.max(1, Math.ceil(order.filter((o) => !o.lane).length * 0.28));
384
+ let ranked = 0;
385
+ for (const o of order) {
386
+ if (o.lane) {
387
+ levels[o.i] = "lane";
388
+ continue;
389
+ }
390
+ if (ranked < avenueCut) {
391
+ levels[o.i] = rng() < rules.boulevards ? "boulevard" : "avenue";
392
+ }
393
+ ranked += 1;
394
+ }
395
+ return levels;
396
+ }
397
+ /**
398
+ * Resolve a full path network from its rules inside a volume of half-extents `hx`×`hz`. Deterministic
399
+ * per `(rules.seed, hx, hz, context)`. Pass a {@link PathNetworkContext} with a ground sampler to turn
400
+ * water gaps into bridges and ridges into tunnels. Coordinates are volume-local; the caller maps to
401
+ * world space.
402
+ *
403
+ * @capability path-network build a deterministic road/track graph (nodes, edges, streets, junctions, bridges, tunnels) from sliders
404
+ */
405
+ export function buildPathNetwork(rules, hx, hz, context = {}) {
406
+ const streams = seededStreams(`pathnet:${rules.seed}:${Math.round(hx)}:${Math.round(hz)}`);
407
+ const mode = pathNetworkMode(rules);
408
+ const raw = mode === "circuit" ? buildCircuit(rules, hx, hz, streams) : buildNet(rules, hx, hz, streams);
409
+ const rawNodes = raw.nodes;
410
+ const rawEdges = raw.edges.filter((e) => e.a !== e.b);
411
+ // --- degrees + adjacency over the atomic graph ---
412
+ const degree = new Array(rawNodes.length).fill(0);
413
+ const adj = rawNodes.map(() => []);
414
+ rawEdges.forEach((e, i) => {
415
+ degree[e.a] += 1;
416
+ degree[e.b] += 1;
417
+ adj[e.a].push({ edge: i, other: e.b });
418
+ adj[e.b].push({ edge: i, other: e.a });
419
+ });
420
+ // --- level per edge: chain length drives the hierarchy, so compute chains first (topology only). ---
421
+ const edgeWander = rawEdges.map((e) =>
422
+ // Clamp wander crests to the footprint so a curve near the rim never bulges outside the volume.
423
+ wanderEdge(rawNodes[e.a], rawNodes[e.b], streams(`edge:${e.a}:${e.b}`), rules).map(([x, z]) => [Math.max(-hx, Math.min(hx, x)), Math.max(-hz, Math.min(hz, z))]));
424
+ const edgeLength = rawEdges.map((_, i) => {
425
+ const pts = edgeWander[i];
426
+ let l = 0;
427
+ for (let s = 1; s < pts.length; s += 1)
428
+ l += Math.hypot(pts[s][0] - pts[s - 1][0], pts[s][1] - pts[s - 1][1]);
429
+ return l;
430
+ });
431
+ // Chain atomic edges through degree-2 nodes into maximal streets (split at junctions/dead-ends).
432
+ const usedEdge = new Array(rawEdges.length).fill(false);
433
+ const isChainThrough = (n) => degree[n] === 2 && !rawEdges.some((e, i) => (e.a === n || e.b === n) && rawEdges[i].lane);
434
+ const chains = [];
435
+ // In a street net, a chain ends where the road bends hard, so streets read as straight runs (and a
436
+ // pure grid stays axis-aligned); a circuit chains its whole ring through every gentle corner.
437
+ const chainSplit = mode === "circuit" ? Math.PI : (55 * Math.PI) / 180;
438
+ const chordTurn = (prev, mid, next) => {
439
+ const ux = rawNodes[mid][0] - rawNodes[prev][0];
440
+ const uz = rawNodes[mid][1] - rawNodes[prev][1];
441
+ const vx = rawNodes[next][0] - rawNodes[mid][0];
442
+ const vz = rawNodes[next][1] - rawNodes[mid][1];
443
+ const lu = Math.hypot(ux, uz);
444
+ const lv = Math.hypot(vx, vz);
445
+ if (lu < 1e-6 || lv < 1e-6)
446
+ return 0;
447
+ return Math.acos(Math.max(-1, Math.min(1, (ux * vx + uz * vz) / (lu * lv))));
448
+ };
449
+ const walkFrom = (startEdge) => {
450
+ if (usedEdge[startEdge])
451
+ return;
452
+ const e0 = rawEdges[startEdge];
453
+ // Grow both directions from this edge through degree-2 pass-through nodes.
454
+ const nodesSeq = [e0.a, e0.b];
455
+ const edgeSeq = [startEdge];
456
+ usedEdge[startEdge] = true;
457
+ const lane = e0.lane;
458
+ // Extend forward off nodesSeq[last], then backward off nodesSeq[0].
459
+ const extend = (dir) => {
460
+ while (true) {
461
+ const tip = dir === 1 ? nodesSeq[nodesSeq.length - 1] : nodesSeq[0];
462
+ if (!isChainThrough(tip))
463
+ break;
464
+ const nextConn = adj[tip].find((c) => !usedEdge[c.edge]);
465
+ if (nextConn === undefined)
466
+ break;
467
+ if (rawEdges[nextConn.edge].lane !== lane)
468
+ break;
469
+ const prevTip = dir === 1 ? nodesSeq[nodesSeq.length - 2] : nodesSeq[1];
470
+ if (chordTurn(prevTip, tip, nextConn.other) > chainSplit)
471
+ break; // road bends hard → end street
472
+ usedEdge[nextConn.edge] = true;
473
+ if (dir === 1) {
474
+ // Closing the loop = reaching the far (unchanging) end, which is index 0 going forward.
475
+ const closed = nextConn.other === nodesSeq[0];
476
+ nodesSeq.push(nextConn.other);
477
+ edgeSeq.push(nextConn.edge);
478
+ if (closed)
479
+ break;
480
+ }
481
+ else {
482
+ // Going backward the far end is the last index, fixed while we prepend.
483
+ const closed = nextConn.other === nodesSeq[nodesSeq.length - 1];
484
+ nodesSeq.unshift(nextConn.other);
485
+ edgeSeq.unshift(nextConn.edge);
486
+ if (closed)
487
+ break;
488
+ }
489
+ }
490
+ };
491
+ extend(1);
492
+ extend(-1);
493
+ let length = 0;
494
+ for (const ei of edgeSeq)
495
+ length += edgeLength[ei];
496
+ const loop = nodesSeq.length > 2 && nodesSeq[0] === nodesSeq[nodesSeq.length - 1];
497
+ chains.push({ nodes: nodesSeq, edges: edgeSeq, length, lane, loop });
498
+ };
499
+ // Prefer starting chains at junctions/dead-ends so through-streets read whole.
500
+ for (let n = 0; n < rawNodes.length; n += 1) {
501
+ if (degree[n] === 2)
502
+ continue;
503
+ for (const c of adj[n])
504
+ walkFrom(c.edge);
505
+ }
506
+ for (let i = 0; i < rawEdges.length; i += 1)
507
+ walkFrom(i); // leftover pure loops
508
+ const levels = levelForStreets(chains, rules, streams("levels"));
509
+ const edgeLevel = new Array(rawEdges.length).fill("street");
510
+ chains.forEach((chain, ci) => {
511
+ for (const ei of chain.edges)
512
+ edgeLevel[ei] = levels[ci];
513
+ });
514
+ // --- assemble atomic edges ---
515
+ const minRad = (rules.minTurnAngle * Math.PI) / 180;
516
+ const maxRad = (rules.maxTurnAngle * Math.PI) / 180;
517
+ const edges = rawEdges.map((e, i) => ({
518
+ id: i,
519
+ a: e.a,
520
+ b: e.b,
521
+ points: edgeWander[i],
522
+ width: rules.width * WIDTH_MULT[edgeLevel[i]],
523
+ level: edgeLevel[i],
524
+ loop: mode === "circuit" && !e.lane,
525
+ }));
526
+ // --- assemble chained streets (smoothed geometry for rendering) ---
527
+ const heightAt = context.heightAt;
528
+ const streets = [];
529
+ chains.forEach((chain, ci) => {
530
+ if (streets.length >= MAX_STREETS)
531
+ return;
532
+ const level = levels[ci];
533
+ const width = rules.width * WIDTH_MULT[level];
534
+ // Concatenate the chain's atomic wander polylines (dropping duplicated shared vertices).
535
+ const pts = [];
536
+ chain.edges.forEach((ei, k) => {
537
+ const e = rawEdges[ei];
538
+ let seg = edgeWander[ei];
539
+ // Orient the segment so it continues the chain's node order.
540
+ const wantStart = chain.nodes[k];
541
+ if (e.b === wantStart && e.a !== wantStart)
542
+ seg = [...seg].reverse();
543
+ for (let s = 0; s < seg.length; s += 1) {
544
+ if (pts.length > 0 && s === 0)
545
+ continue;
546
+ pts.push(seg[s]);
547
+ }
548
+ });
549
+ const smooth = clampTurns(pts, minRad, maxRad);
550
+ const street = {
551
+ id: streets.length,
552
+ nodes: chain.nodes,
553
+ points: smooth,
554
+ width,
555
+ level,
556
+ loop: chain.loop,
557
+ features: [],
558
+ };
559
+ // Dead-end cul-de-sac bulb at a dangling terminal node.
560
+ const endNode = chain.nodes[chain.nodes.length - 1];
561
+ const startNode = chain.nodes[0];
562
+ if (!chain.loop) {
563
+ if (degree[endNode] === 1)
564
+ street.bulb = smooth[smooth.length - 1];
565
+ else if (degree[startNode] === 1)
566
+ street.bulb = smooth[0];
567
+ }
568
+ // Bridge/tunnel features along this street.
569
+ if (heightAt !== undefined) {
570
+ street.features = detectFeatures(smooth, heightAt, context);
571
+ }
572
+ streets.push(street);
573
+ });
574
+ // --- junctions from degree≥3 nodes; arms from incident edge tangents ---
575
+ const junctions = [];
576
+ for (let n = 0; n < rawNodes.length; n += 1) {
577
+ if (degree[n] < 3)
578
+ continue;
579
+ const arms = [];
580
+ let radius = 0;
581
+ let level = "lane";
582
+ for (const c of adj[n]) {
583
+ const e = rawEdges[c.edge];
584
+ const pts = edgeWander[c.edge];
585
+ // Direction leaving node n along this edge.
586
+ const nearN = e.a === n ? pts[Math.min(1, pts.length - 1)] : pts[Math.max(0, pts.length - 2)];
587
+ const dx = nearN[0] - rawNodes[n][0];
588
+ const dz = nearN[1] - rawNodes[n][1];
589
+ arms.push({ angle: Math.atan2(dx, dz), width: edges[c.edge].width });
590
+ radius = Math.max(radius, edges[c.edge].width / 2 + 0.8);
591
+ if (LEVEL_RANK[edgeLevel[c.edge]] > LEVEL_RANK[level])
592
+ level = edgeLevel[c.edge];
593
+ }
594
+ junctions.push({ x: rawNodes[n][0], z: rawNodes[n][1], radius, level, arms });
595
+ }
596
+ // --- dead ends from degree-1 nodes ---
597
+ const deadEnds = [];
598
+ for (let n = 0; n < rawNodes.length; n += 1) {
599
+ if (degree[n] !== 1)
600
+ continue;
601
+ const c = adj[n][0];
602
+ const e = rawEdges[c.edge];
603
+ const pts = edgeWander[c.edge];
604
+ const inner = e.a === n ? pts[Math.min(1, pts.length - 1)] : pts[Math.max(0, pts.length - 2)];
605
+ const dx = rawNodes[n][0] - inner[0];
606
+ const dz = rawNodes[n][1] - inner[1];
607
+ deadEnds.push({ node: n, x: rawNodes[n][0], z: rawNodes[n][1], heading: Math.atan2(dx, dz), width: edges[c.edge].width });
608
+ }
609
+ // --- resolve feature spans into world-of-volume feature centerlines ---
610
+ const bridges = [];
611
+ const tunnels = [];
612
+ for (const street of streets) {
613
+ for (const span of street.features) {
614
+ const slice = street.points.slice(span.from, span.to + 1);
615
+ if (slice.length < 2)
616
+ continue;
617
+ const feature = { kind: span.kind, points: slice, width: street.width, bankHeight: span.bankHeight };
618
+ (span.kind === "bridge" ? bridges : tunnels).push(feature);
619
+ }
620
+ }
621
+ const components = countComponents(rawNodes.length, rawEdges);
622
+ const loops = Math.max(0, rawEdges.length - rawNodes.length + components);
623
+ // Compact node list with final degrees.
624
+ const nodes = rawNodes.map((p, i) => ({ id: i, x: p[0], z: p[1], degree: degree[i] }));
625
+ return { mode, nodes, edges, streets, junctions, deadEnds, bridges, tunnels, loops };
626
+ }
627
+ /** Connected-component count over the atomic graph (for the cycle-rank / loop count). */
628
+ function countComponents(n, edges) {
629
+ const uf = makeUf(n);
630
+ let count = n;
631
+ for (const e of edges)
632
+ if (uf.union(e.a, e.b))
633
+ count -= 1;
634
+ return count;
635
+ }
636
+ /**
637
+ * Walk a street's centerline and mark BRIDGE spans (a run dipping under `minElevation`, banked by
638
+ * land on both sides) and TUNNEL spans (a run buried under a ridge rising `tunnelClearance` above its
639
+ * banks). Both are bounded to a few segment lengths so a road never bridges an ocean or bores a
640
+ * mountain range. Returns index windows into `points`.
641
+ */
642
+ function detectFeatures(points, heightAt, context) {
643
+ const minEl = context.minElevation ?? -2;
644
+ const clearance = context.tunnelClearance ?? 6;
645
+ const wantBridge = context.bridges === true;
646
+ const wantTunnel = context.tunnels === true;
647
+ if (!wantBridge && !wantTunnel)
648
+ return [];
649
+ const spans = [];
650
+ const heights = points.map((p) => heightAt(p[0], p[1]));
651
+ let i = 0;
652
+ while (i < points.length) {
653
+ if (wantBridge && heights[i] < minEl && i > 0 && heights[i - 1] >= minEl) {
654
+ const start = i - 1; // last land vertex before the gap (guaranteed above water)
655
+ let j = i;
656
+ while (j < points.length && heights[j] < minEl)
657
+ j += 1;
658
+ if (j < points.length) {
659
+ const bank = Math.min(heights[start], heights[j]);
660
+ spans.push({ kind: "bridge", from: start, to: j, bankHeight: bank });
661
+ i = j + 1;
662
+ continue;
663
+ }
664
+ break; // gap runs off the end of the street — no far bank, no deck
665
+ }
666
+ if (wantTunnel && i > 0 && i < points.length - 1) {
667
+ const bank = heights[i - 1];
668
+ if (heights[i] > bank + clearance && heights[i] >= minEl) {
669
+ const start = i - 1;
670
+ let j = i;
671
+ while (j < points.length - 1 && heights[j] > bank + clearance)
672
+ j += 1;
673
+ const farBank = heights[j];
674
+ if (farBank <= bank + clearance) {
675
+ spans.push({ kind: "tunnel", from: start, to: j, bankHeight: Math.min(bank, farBank) });
676
+ i = j + 1;
677
+ continue;
678
+ }
679
+ }
680
+ }
681
+ i += 1;
682
+ }
683
+ return spans;
684
+ }