@jgengine/core 0.18.1 → 0.21.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 (404) hide show
  1. package/CHANGELOG.md +504 -7
  2. package/dist/ai/decisionGraph.d.ts +50 -4
  3. package/dist/ai/decisionGraph.js +159 -23
  4. package/dist/ai/flock.d.ts +1 -1
  5. package/dist/ai/flock.js +7 -4
  6. package/dist/ai/groupAssist.d.ts +10 -0
  7. package/dist/ai/groupAssist.js +11 -0
  8. package/dist/ai/pursuit.d.ts +1 -1
  9. package/dist/ai/pursuit.js +1 -1
  10. package/dist/ai/threat.d.ts +8 -0
  11. package/dist/ai/threat.js +10 -0
  12. package/dist/anim/animGraph.d.ts +43 -1
  13. package/dist/anim/animGraph.js +326 -10
  14. package/dist/anim/boneTexture.d.ts +55 -0
  15. package/dist/anim/boneTexture.js +43 -0
  16. package/dist/anim/footPlacement.d.ts +57 -0
  17. package/dist/anim/footPlacement.js +67 -0
  18. package/dist/anim/locomotionGraph.d.ts +18 -1
  19. package/dist/anim/locomotionGraph.js +42 -3
  20. package/dist/audio/audioFalloff.d.ts +6 -0
  21. package/dist/audio/doppler.d.ts +21 -0
  22. package/dist/audio/doppler.js +32 -0
  23. package/dist/audio/engineLayers.d.ts +83 -0
  24. package/dist/audio/engineLayers.js +115 -0
  25. package/dist/audio/voiceAllocator.d.ts +46 -0
  26. package/dist/audio/voiceAllocator.js +76 -0
  27. package/dist/combat/abilityKit.d.ts +20 -0
  28. package/dist/combat/abilityKit.js +41 -4
  29. package/dist/combat/death.d.ts +11 -3
  30. package/dist/combat/death.js +3 -3
  31. package/dist/combat/magazine.d.ts +9 -0
  32. package/dist/combat/magazine.js +24 -2
  33. package/dist/combat/projectiles.d.ts +94 -2
  34. package/dist/combat/projectiles.js +282 -61
  35. package/dist/combat/regenShield.d.ts +14 -0
  36. package/dist/combat/regenShield.js +23 -7
  37. package/dist/combat/shotOrigin.d.ts +6 -0
  38. package/dist/combat/shotOrigin.js +6 -0
  39. package/dist/combat/weaponFeedback.d.ts +36 -0
  40. package/dist/combat/weaponFeedback.js +42 -0
  41. package/dist/combat/weaponHandling.d.ts +161 -0
  42. package/dist/combat/weaponHandling.js +202 -0
  43. package/dist/combat/weaponPresentation.d.ts +115 -0
  44. package/dist/combat/weaponPresentation.js +96 -0
  45. package/dist/crafting/recipeCatalog.d.ts +16 -0
  46. package/dist/crafting/recipeCatalog.js +55 -0
  47. package/dist/devtools/warnOnce.d.ts +8 -0
  48. package/dist/devtools/warnOnce.js +18 -0
  49. package/dist/economy/auctionBook.d.ts +1 -1
  50. package/dist/economy/auctionBook.js +11 -1
  51. package/dist/economy/listingBook.d.ts +1 -1
  52. package/dist/economy/listingBook.js +11 -1
  53. package/dist/economy/resourceLedger.js +3 -3
  54. package/dist/economy/shopStock.js +7 -6
  55. package/dist/economy/techTree.js +7 -6
  56. package/dist/economy/unlockPoints.js +3 -6
  57. package/dist/editor/commandApply.js +89 -16
  58. package/dist/editor/commands.d.ts +38 -1
  59. package/dist/editor/commands.js +320 -3
  60. package/dist/editor/creatorStorage.d.ts +59 -0
  61. package/dist/editor/creatorStorage.js +164 -0
  62. package/dist/editor/document.d.ts +4 -0
  63. package/dist/editor/document.js +119 -18
  64. package/dist/editor/environment.js +8 -0
  65. package/dist/editor/index.d.ts +2 -0
  66. package/dist/editor/liveSync.d.ts +4 -1
  67. package/dist/editor/liveSync.js +26 -10
  68. package/dist/editor/materialAuthoring.d.ts +14 -0
  69. package/dist/editor/materialAuthoring.js +38 -0
  70. package/dist/editor/modelNodeVisibility.d.ts +11 -0
  71. package/dist/editor/modelNodeVisibility.js +29 -0
  72. package/dist/editor/movementCatalog.d.ts +90 -0
  73. package/dist/editor/movementCatalog.js +144 -0
  74. package/dist/editor/simulation.d.ts +60 -0
  75. package/dist/editor/simulation.js +202 -0
  76. package/dist/editor/staticPrefab.d.ts +17 -0
  77. package/dist/editor/staticPrefab.js +51 -0
  78. package/dist/editor/types.d.ts +15 -0
  79. package/dist/faction/reputation.d.ts +6 -0
  80. package/dist/faction/reputation.js +11 -0
  81. package/dist/game/cameraConfig.d.ts +54 -2
  82. package/dist/game/clipRoles.d.ts +2 -1
  83. package/dist/game/clipRoles.js +7 -3
  84. package/dist/game/contentValidation.d.ts +69 -0
  85. package/dist/game/contentValidation.js +118 -0
  86. package/dist/game/controlGate.d.ts +8 -0
  87. package/dist/game/controlGate.js +43 -6
  88. package/dist/game/defineGame.d.ts +32 -3
  89. package/dist/game/dialogueGraph.d.ts +20 -0
  90. package/dist/game/dialogueGraph.js +107 -0
  91. package/dist/game/events.d.ts +11 -0
  92. package/dist/game/keyValueStore.d.ts +17 -4
  93. package/dist/game/keyValueStore.js +35 -23
  94. package/dist/game/lootModifiers.d.ts +17 -0
  95. package/dist/game/lootModifiers.js +26 -0
  96. package/dist/game/lootTable.d.ts +4 -0
  97. package/dist/game/lootTable.js +16 -0
  98. package/dist/game/narrativeAuthoring.d.ts +67 -0
  99. package/dist/game/narrativeAuthoring.js +122 -0
  100. package/dist/game/playableGame.d.ts +134 -35
  101. package/dist/game/quest.d.ts +15 -0
  102. package/dist/game/quest.js +209 -63
  103. package/dist/game/questCatalog.d.ts +50 -0
  104. package/dist/game/questCatalog.js +205 -0
  105. package/dist/game/saveStore.d.ts +6 -5
  106. package/dist/game/saveStore.js +57 -57
  107. package/dist/game/systemRuntime.d.ts +2 -2
  108. package/dist/game/systemRuntime.js +84 -33
  109. package/dist/game/toasts.d.ts +8 -0
  110. package/dist/game/toasts.js +7 -0
  111. package/dist/game/vfxInstance.d.ts +9 -0
  112. package/dist/game/vfxInstance.js +12 -1
  113. package/dist/game/viewports.d.ts +43 -0
  114. package/dist/game/viewports.js +46 -0
  115. package/dist/game/worldItem.d.ts +5 -1
  116. package/dist/game/worldItem.js +0 -1
  117. package/dist/gameplay.d.ts +3 -0
  118. package/dist/gameplay.js +3 -0
  119. package/dist/input/actionBindings.d.ts +15 -1
  120. package/dist/input/actionBindings.js +14 -3
  121. package/dist/input/actionContexts.d.ts +25 -1
  122. package/dist/input/actionContexts.js +82 -25
  123. package/dist/input/axisInput.d.ts +4 -0
  124. package/dist/input/axisInput.js +4 -0
  125. package/dist/input/axisShaper.d.ts +73 -0
  126. package/dist/input/axisShaper.js +105 -0
  127. package/dist/input/gamepadModel.d.ts +36 -2
  128. package/dist/input/gamepadModel.js +69 -30
  129. package/dist/input/haptics.d.ts +50 -0
  130. package/dist/input/haptics.js +74 -0
  131. package/dist/input/inputBuffer.d.ts +40 -0
  132. package/dist/input/inputBuffer.js +59 -0
  133. package/dist/input/lookChannel.d.ts +10 -0
  134. package/dist/input/lookChannel.js +6 -0
  135. package/dist/input/pointer.d.ts +2 -0
  136. package/dist/input/pointer.js +4 -0
  137. package/dist/interaction/promptRegistry.d.ts +5 -4
  138. package/dist/interaction/promptRegistry.js +8 -2
  139. package/dist/inventory/inventoryModel.js +9 -6
  140. package/dist/inventory/shapedGrid.d.ts +48 -9
  141. package/dist/inventory/shapedGrid.js +69 -10
  142. package/dist/item/affix.d.ts +6 -0
  143. package/dist/item/affix.js +6 -0
  144. package/dist/item/durability.js +4 -0
  145. package/dist/item/itemInstanceRegistry.d.ts +32 -1
  146. package/dist/item/itemInstanceRegistry.js +111 -7
  147. package/dist/item/itemgen.d.ts +1 -1
  148. package/dist/item/itemgen.js +1 -1
  149. package/dist/item/modularItem.js +7 -1
  150. package/dist/material/appearanceSignals.d.ts +53 -0
  151. package/dist/material/appearanceSignals.js +47 -0
  152. package/dist/material/hairCards.d.ts +47 -0
  153. package/dist/material/hairCards.js +136 -0
  154. package/dist/material/materialAsset.d.ts +162 -0
  155. package/dist/material/materialAsset.js +382 -0
  156. package/dist/meta/changelog.d.ts +1 -1
  157. package/dist/meta/changelog.js +444 -1
  158. package/dist/movement/characterController.d.ts +5 -2
  159. package/dist/movement/characterController.js +50 -23
  160. package/dist/movement/freeFlight.d.ts +2 -2
  161. package/dist/movement/freeFlight.js +3 -3
  162. package/dist/movement/glideModel.d.ts +3 -0
  163. package/dist/movement/glideModel.js +6 -0
  164. package/dist/movement/grappleSwing.d.ts +2 -0
  165. package/dist/movement/grappleSwing.js +5 -1
  166. package/dist/movement/movementModel.d.ts +63 -1
  167. package/dist/movement/movementModel.js +136 -26
  168. package/dist/movement/movementProbe.d.ts +51 -0
  169. package/dist/movement/movementProbe.js +119 -0
  170. package/dist/movement/playerMovement.d.ts +48 -2
  171. package/dist/movement/playerMovement.js +493 -87
  172. package/dist/movement/poseState.d.ts +2 -0
  173. package/dist/movement/poseState.js +29 -1
  174. package/dist/movement/solidObstacles.d.ts +11 -1
  175. package/dist/movement/solidObstacles.js +48 -3
  176. package/dist/movement/terrainGrade.d.ts +13 -0
  177. package/dist/movement/terrainGrade.js +33 -0
  178. package/dist/movement/voxelController.d.ts +2 -2
  179. package/dist/movement/voxelController.js +10 -10
  180. package/dist/nav/navFromEnvironment.d.ts +9 -3
  181. package/dist/nav/navFromEnvironment.js +18 -12
  182. package/dist/nav/navGrid.d.ts +19 -1
  183. package/dist/nav/navGrid.js +26 -10
  184. package/dist/nav/navMesh.d.ts +43 -2
  185. package/dist/nav/navMesh.js +534 -128
  186. package/dist/nav/solidRoute.d.ts +50 -0
  187. package/dist/nav/solidRoute.js +144 -0
  188. package/dist/physics/aircraftDynamics.d.ts +390 -0
  189. package/dist/physics/aircraftDynamics.js +704 -0
  190. package/dist/physics/ballisticSweep.d.ts +10 -3
  191. package/dist/physics/ballisticSweep.js +53 -29
  192. package/dist/physics/boatDynamics.d.ts +116 -0
  193. package/dist/physics/boatDynamics.js +160 -0
  194. package/dist/physics/drivableVehicle.d.ts +22 -8
  195. package/dist/physics/drivableVehicle.js +4 -2
  196. package/dist/physics/flightDynamics.d.ts +21 -4
  197. package/dist/physics/flightDynamics.js +28 -5
  198. package/dist/physics/forceVolume.d.ts +41 -0
  199. package/dist/physics/forceVolume.js +88 -0
  200. package/dist/physics/handlingProbe.d.ts +249 -0
  201. package/dist/physics/handlingProbe.js +606 -0
  202. package/dist/physics/kinematicVehicle.d.ts +40 -5
  203. package/dist/physics/kinematicVehicle.js +17 -2
  204. package/dist/physics/physicsBackend.d.ts +12 -0
  205. package/dist/physics/physicsBackendConformance.js +34 -0
  206. package/dist/physics/physicsWorldBackend.d.ts +1 -0
  207. package/dist/physics/physicsWorldBackend.js +52 -3
  208. package/dist/physics/spatialGrid.d.ts +1 -1
  209. package/dist/physics/spatialGrid.js +8 -9
  210. package/dist/physics/vehicleBackendLink.d.ts +49 -0
  211. package/dist/physics/vehicleBackendLink.js +102 -0
  212. package/dist/physics/vehicleDynamics.d.ts +375 -0
  213. package/dist/physics/vehicleDynamics.js +737 -0
  214. package/dist/physics/vehicleObstacles.d.ts +3 -2
  215. package/dist/physics/vehicleObstacles.js +29 -5
  216. package/dist/physics/worldColliders.d.ts +1 -1
  217. package/dist/physics/worldColliders.js +35 -3
  218. package/dist/puzzle/cellGrid.d.ts +15 -9
  219. package/dist/puzzle/cellGrid.js +13 -9
  220. package/dist/render/lookPreset.d.ts +9 -2
  221. package/dist/render/lookPreset.js +29 -2
  222. package/dist/render/postProcessing.d.ts +30 -0
  223. package/dist/render/postProcessing.js +1 -1
  224. package/dist/runtime/cameraDirector.d.ts +54 -3
  225. package/dist/runtime/cameraDirector.js +187 -0
  226. package/dist/runtime/commandDispatch.d.ts +14 -0
  227. package/dist/runtime/commandDispatch.js +19 -0
  228. package/dist/runtime/commandInput.d.ts +37 -0
  229. package/dist/runtime/commandInput.js +58 -0
  230. package/dist/runtime/commandRunner.d.ts +27 -0
  231. package/dist/runtime/commandRunner.js +31 -3
  232. package/dist/runtime/context/combat.d.ts +12 -4
  233. package/dist/runtime/context/combat.js +179 -11
  234. package/dist/runtime/context/deathLoot.d.ts +11 -8
  235. package/dist/runtime/context/deathLoot.js +17 -16
  236. package/dist/runtime/context/scene.d.ts +2 -0
  237. package/dist/runtime/context/scene.js +13 -21
  238. package/dist/runtime/descriptors/baseline.js +34 -3
  239. package/dist/runtime/descriptors/features.d.ts +2 -1
  240. package/dist/runtime/descriptors/features.js +18 -0
  241. package/dist/runtime/gameContext.js +86 -13
  242. package/dist/runtime/gameContextTypes.d.ts +33 -3
  243. package/dist/runtime/gameRuntime.d.ts +3 -1
  244. package/dist/runtime/gameRuntime.js +4 -0
  245. package/dist/runtime/headlessRunner.d.ts +3 -1
  246. package/dist/runtime/headlessRunner.js +11 -11
  247. package/dist/runtime/hostedGameRunner.d.ts +24 -1
  248. package/dist/runtime/hostedGameRunner.js +156 -10
  249. package/dist/runtime/hostedWorldSession.d.ts +17 -5
  250. package/dist/runtime/hostedWorldSession.js +154 -16
  251. package/dist/runtime/hostedWorldStore.d.ts +2 -2
  252. package/dist/runtime/inputRecorder.d.ts +1 -1
  253. package/dist/runtime/inputRecorder.js +21 -1
  254. package/dist/runtime/inputSnapshot.d.ts +22 -4
  255. package/dist/runtime/inputSnapshot.js +41 -3
  256. package/dist/runtime/localPlayers.d.ts +73 -0
  257. package/dist/runtime/localPlayers.js +119 -0
  258. package/dist/runtime/runtimeSave.d.ts +10 -6
  259. package/dist/runtime/runtimeSave.js +11 -5
  260. package/dist/runtime/simContext.d.ts +8 -3
  261. package/dist/runtime/simContext.js +7 -3
  262. package/dist/runtime/simSnapshot.js +2 -0
  263. package/dist/runtime/transport.d.ts +9 -0
  264. package/dist/runtime/transport.js +7 -1
  265. package/dist/runtime/worldReplication.d.ts +2 -0
  266. package/dist/runtime/worldReplication.js +1 -1
  267. package/dist/scene/assetCatalog.d.ts +2 -0
  268. package/dist/scene/behaviorRuntime.d.ts +38 -7
  269. package/dist/scene/behaviorRuntime.js +293 -30
  270. package/dist/scene/behaviors.d.ts +35 -2
  271. package/dist/scene/behaviors.js +29 -0
  272. package/dist/scene/entityStore.d.ts +14 -3
  273. package/dist/scene/entityStore.js +45 -9
  274. package/dist/scene/mount.d.ts +9 -0
  275. package/dist/scene/mount.js +35 -0
  276. package/dist/scene/possession.d.ts +2 -0
  277. package/dist/scene/possession.js +21 -1
  278. package/dist/scene/sceneKinds.d.ts +9 -0
  279. package/dist/scene/sceneRaycast.d.ts +7 -0
  280. package/dist/scene/sceneRaycast.js +55 -0
  281. package/dist/scene/spatial.d.ts +20 -1
  282. package/dist/scene/spatial.js +94 -2
  283. package/dist/scene/vehicleSeat.d.ts +41 -3
  284. package/dist/scene/vehicleSeat.js +94 -3
  285. package/dist/sensor/concealment.d.ts +6 -0
  286. package/dist/sensor/concealment.js +8 -0
  287. package/dist/sensor/freezeMonitor.d.ts +6 -0
  288. package/dist/sensor/freezeMonitor.js +8 -0
  289. package/dist/sensor/perception.d.ts +11 -1
  290. package/dist/sensor/perception.js +57 -10
  291. package/dist/sensor/recordingBuffer.d.ts +3 -0
  292. package/dist/sensor/recordingBuffer.js +8 -0
  293. package/dist/session/roundState.d.ts +4 -0
  294. package/dist/session/roundState.js +11 -0
  295. package/dist/settings/frameRateLimit.d.ts +16 -0
  296. package/dist/settings/frameRateLimit.js +37 -0
  297. package/dist/settings/graphicsProfile.d.ts +2 -0
  298. package/dist/settings/graphicsProfile.js +12 -0
  299. package/dist/settings/graphicsSettings.d.ts +40 -0
  300. package/dist/settings/graphicsSettings.js +81 -0
  301. package/dist/settings/settingsModel.d.ts +9 -1
  302. package/dist/settings/settingsModel.js +10 -0
  303. package/dist/stats/accumulatorMeter.d.ts +10 -0
  304. package/dist/stats/accumulatorMeter.js +6 -0
  305. package/dist/stats/eventMeter.d.ts +10 -3
  306. package/dist/stats/eventMeter.js +6 -3
  307. package/dist/store/defineStore.d.ts +3 -1
  308. package/dist/store/defineStore.js +3 -1
  309. package/dist/store/observableKeyedStore.d.ts +1 -1
  310. package/dist/store/observableKeyedStore.js +8 -6
  311. package/dist/time/simClock.d.ts +6 -2
  312. package/dist/time/simClock.js +5 -0
  313. package/dist/vfx/feedbackMixer.d.ts +61 -0
  314. package/dist/vfx/feedbackMixer.js +106 -0
  315. package/dist/vfx/particleDirector.d.ts +58 -6
  316. package/dist/vfx/particleDirector.js +113 -22
  317. package/dist/vfx/particles.d.ts +68 -5
  318. package/dist/vfx/particles.js +284 -32
  319. package/dist/visibility/assetStreaming.d.ts +21 -3
  320. package/dist/visibility/assetStreaming.js +108 -24
  321. package/dist/visibility/settings.d.ts +8 -2
  322. package/dist/visibility/settings.js +3 -0
  323. package/dist/visibility/simulationCulling.d.ts +8 -1
  324. package/dist/visibility/simulationCulling.js +7 -0
  325. package/dist/visibility/spatialIndex.d.ts +21 -0
  326. package/dist/visibility/spatialIndex.js +16 -0
  327. package/dist/visibility/visibilitySystem.js +1 -0
  328. package/dist/work/expedition.d.ts +110 -0
  329. package/dist/work/expedition.js +195 -0
  330. package/dist/work/staffedStation.d.ts +29 -0
  331. package/dist/work/staffedStation.js +27 -0
  332. package/dist/world/authoredAnimation.d.ts +38 -0
  333. package/dist/world/authoredAnimation.js +99 -0
  334. package/dist/world/authoredObjects.d.ts +24 -4
  335. package/dist/world/authoredObjects.js +43 -16
  336. package/dist/world/authoredSimulation.d.ts +74 -0
  337. package/dist/world/authoredSimulation.js +302 -0
  338. package/dist/world/authoredSolids.d.ts +46 -0
  339. package/dist/world/authoredSolids.js +75 -0
  340. package/dist/world/authoredWeather.d.ts +120 -0
  341. package/dist/world/authoredWeather.js +314 -0
  342. package/dist/world/buildingGenerator.js +2 -2
  343. package/dist/world/buildingKit.d.ts +11 -4
  344. package/dist/world/buildingKit.js +21 -8
  345. package/dist/world/buildings.d.ts +34 -2
  346. package/dist/world/buildings.js +11 -1
  347. package/dist/world/cityKind.d.ts +7 -0
  348. package/dist/world/cityKind.js +52 -1
  349. package/dist/world/environmentForces.d.ts +21 -0
  350. package/dist/world/environmentForces.js +42 -0
  351. package/dist/world/features.d.ts +32 -5
  352. package/dist/world/features.js +6 -0
  353. package/dist/world/footprintGrid.d.ts +62 -0
  354. package/dist/world/footprintGrid.js +217 -0
  355. package/dist/world/interiors.d.ts +1 -0
  356. package/dist/world/interiors.js +5 -3
  357. package/dist/world/lod.d.ts +6 -0
  358. package/dist/world/lod.js +8 -0
  359. package/dist/world/pathTerrain.d.ts +1 -1
  360. package/dist/world/terraform.d.ts +9 -1
  361. package/dist/world/terraform.js +53 -18
  362. package/dist/world/walls.d.ts +7 -0
  363. package/dist/world/walls.js +7 -0
  364. package/dist/world/weather.d.ts +17 -0
  365. package/dist/world/weather.js +116 -20
  366. package/dist/world/worldSolids.d.ts +95 -0
  367. package/dist/world/worldSolids.js +351 -0
  368. package/dist/world.d.ts +6 -6
  369. package/dist/world.js +5 -5
  370. package/package.json +1 -1
  371. package/skills/ce-handoff/SKILL.md +2 -2
  372. package/skills/game-design/SKILL.md +2 -2
  373. package/skills/jgengine/SKILL.md +28 -6
  374. package/skills/jgengine/api.md +139 -99
  375. package/skills/jgengine/capabilities.md +28 -0
  376. package/skills/jgengine/recipes/minimal-game.md +10 -2
  377. package/skills/jgengine-combat/SKILL.md +13 -2
  378. package/skills/jgengine-combat/api.md +150 -103
  379. package/skills/jgengine-combat/capabilities.md +29 -1
  380. package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
  381. package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
  382. package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
  383. package/skills/jgengine-combat/reference.md +24 -2
  384. package/skills/jgengine-gameplay/SKILL.md +63 -0
  385. package/skills/jgengine-gameplay/api.md +583 -463
  386. package/skills/jgengine-gameplay/capabilities.md +92 -0
  387. package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
  388. package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
  389. package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
  390. package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
  391. package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
  392. package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
  393. package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
  394. package/skills/jgengine-gameplay/reference-systems.md +123 -0
  395. package/skills/jgengine-world/SKILL.md +25 -4
  396. package/skills/jgengine-world/api.md +1045 -655
  397. package/skills/jgengine-world/capabilities.md +246 -9
  398. package/skills/jgengine-world/recipes/character-animation.md +62 -0
  399. package/skills/jgengine-world/recipes/controls.md +48 -0
  400. package/skills/jgengine-world/recipes/flight-feel.md +76 -0
  401. package/skills/jgengine-world/recipes/npc-senses.md +55 -0
  402. package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
  403. package/skills/jgengine-world/reference.md +180 -4
  404. package/skills/jgengine-world/visibility.md +139 -0
@@ -0,0 +1,314 @@
1
+ import { createEnvironmentField } from "./envField.js";
2
+ import { windField } from "./wind.js";
3
+ const MAX_ENTRIES = 256;
4
+ const MAX_MAGNITUDE = 1_000_000;
5
+ const MAX_DOMAIN = 1_000_000_000_000;
6
+ const MODES = ["clear", "rain", "snow", "mixed", "dust"];
7
+ const CLEAR = { mode: "clear", intensity: 0 };
8
+ function identifier(value, label) {
9
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > 128)
10
+ throw new Error(`${label} must be a nonempty string of at most 128 characters`);
11
+ }
12
+ function finite(value, label, min = -Infinity, max = Infinity) {
13
+ if (!Number.isFinite(value) || value < min || value > max)
14
+ throw new Error(`${label} must be finite and within ${min}..${max}`);
15
+ }
16
+ function objectKeys(value, allowed, label) {
17
+ if (value === null || typeof value !== "object" || Array.isArray(value))
18
+ throw new Error(`${label} must be an object`);
19
+ for (const key of Object.keys(value))
20
+ if (!allowed.includes(key))
21
+ throw new Error(`${label}.${key}: unknown field`);
22
+ }
23
+ function conditions(value, profile = false) {
24
+ objectKeys(value, ["mode", "intensity", "temperatureOffset", "lightning", ...(profile ? ["id"] : [])], "weather conditions");
25
+ if (!MODES.includes(value.mode))
26
+ throw new Error(`unknown weather mode: ${value.mode}`);
27
+ finite(value.intensity, "weather intensity", 0);
28
+ if (value.intensity > 1)
29
+ throw new Error("weather intensity must be <= 1");
30
+ if (value.temperatureOffset !== undefined)
31
+ finite(value.temperatureOffset, "weather temperatureOffset", -MAX_MAGNITUDE, MAX_MAGNITUDE);
32
+ if (value.lightning !== undefined) {
33
+ finite(value.lightning, "weather lightning", 0);
34
+ if (value.lightning > 1)
35
+ throw new Error("weather lightning must be <= 1");
36
+ }
37
+ }
38
+ function validateWind(value) {
39
+ if (value === undefined)
40
+ return;
41
+ objectKeys(value, ["direction", "speed", "gust", "gustFrequency", "turbulence", "seed"], "wind");
42
+ if (value.direction !== undefined) {
43
+ if (!Array.isArray(value.direction) || value.direction.length !== 2)
44
+ throw new Error("wind direction requires world X,Z");
45
+ for (const axis of value.direction)
46
+ finite(axis, "wind direction", -MAX_MAGNITUDE, MAX_MAGNITUDE);
47
+ }
48
+ for (const key of ["speed", "gust", "gustFrequency", "turbulence"]) {
49
+ if (value[key] !== undefined)
50
+ finite(value[key], `wind.${key}`, 0, key === "gustFrequency" ? 1000 : MAX_MAGNITUDE);
51
+ }
52
+ if (value.seed !== undefined && typeof value.seed !== "string")
53
+ finite(value.seed, "wind seed");
54
+ }
55
+ /** Validate authoring references and work bounds before persisting a weather document.
56
+ * @capability authored-weather-validation Reject invalid weather profiles, schedules, zones and wind before authoring mutations.
57
+ */
58
+ export function validateAuthoredWeather(config) {
59
+ objectKeys(config, ["ambient", "profiles", "initialProfileId", "schedule", "wind", "zones"], "weather");
60
+ for (const list of [config.profiles, config.schedule, config.zones]) {
61
+ if (list !== undefined && !Array.isArray(list))
62
+ throw new Error("weather profiles, schedule and zones must be arrays");
63
+ if (list !== undefined && list.length > MAX_ENTRIES)
64
+ throw new Error(`weather lists are limited to ${MAX_ENTRIES} entries`);
65
+ }
66
+ if (config.ambient !== undefined)
67
+ conditions(config.ambient);
68
+ validateWind(config.wind);
69
+ const ids = new Set();
70
+ for (const profile of config.profiles ?? []) {
71
+ identifier(profile.id, "weather profile id");
72
+ if (!profile.id || ids.has(profile.id))
73
+ throw new Error(`invalid or duplicate weather profile: ${profile.id}`);
74
+ ids.add(profile.id);
75
+ conditions(profile, true);
76
+ }
77
+ if (config.initialProfileId !== undefined)
78
+ identifier(config.initialProfileId, "initial weather profile id");
79
+ if (config.initialProfileId !== undefined && !ids.has(config.initialProfileId))
80
+ throw new Error(`unknown initial weather profile: ${config.initialProfileId}`);
81
+ let previous = -1;
82
+ for (const entry of config.schedule ?? []) {
83
+ objectKeys(entry, ["atSeconds", "profileId", "transitionSeconds"], "weather schedule");
84
+ identifier(entry.profileId, "scheduled weather profile id");
85
+ finite(entry.atSeconds, "weather schedule time", 0, MAX_DOMAIN);
86
+ if (entry.atSeconds <= previous)
87
+ throw new Error("weather schedule times must be strictly increasing");
88
+ previous = entry.atSeconds;
89
+ if (!ids.has(entry.profileId))
90
+ throw new Error(`unknown weather profile: ${entry.profileId}`);
91
+ if (entry.transitionSeconds !== undefined)
92
+ finite(entry.transitionSeconds, "weather transition duration", 0, MAX_DOMAIN);
93
+ }
94
+ ids.clear();
95
+ for (const zone of config.zones ?? []) {
96
+ objectKeys(zone, ["id", "center", "radius", "falloff", "weather", "wind", "radial"], "weather zone");
97
+ identifier(zone.id, "weather zone id");
98
+ if (zone.radial !== undefined && typeof zone.radial !== "boolean")
99
+ throw new Error("weather zone radial must be boolean");
100
+ if (!zone.id || ids.has(zone.id))
101
+ throw new Error(`invalid or duplicate weather zone: ${zone.id}`);
102
+ ids.add(zone.id);
103
+ if (!Array.isArray(zone.center) || zone.center.length !== 2)
104
+ throw new Error("weather zone center requires world X,Z");
105
+ for (const axis of zone.center)
106
+ finite(axis, "weather zone center", -MAX_DOMAIN, MAX_DOMAIN);
107
+ finite(zone.radius, "weather zone radius", Number.MIN_VALUE, MAX_DOMAIN);
108
+ if (zone.falloff !== undefined)
109
+ finite(zone.falloff, "weather zone falloff", 0, MAX_DOMAIN);
110
+ if (zone.weather !== undefined)
111
+ conditions(zone.weather);
112
+ validateWind(zone.wind);
113
+ }
114
+ }
115
+ function amounts(value) {
116
+ return [
117
+ value.mode === "rain" ? value.intensity : value.mode === "mixed" ? value.intensity * 0.5 : 0,
118
+ value.mode === "snow" ? value.intensity : value.mode === "mixed" ? value.intensity * 0.5 : 0,
119
+ value.mode === "dust" ? value.intensity : 0,
120
+ ];
121
+ }
122
+ function modeOf(rain, snow, dust) {
123
+ if (rain > 0 && snow > 0)
124
+ return "mixed";
125
+ if (rain > 0)
126
+ return "rain";
127
+ if (snow > 0)
128
+ return "snow";
129
+ return dust > 0 ? "dust" : "clear";
130
+ }
131
+ function blendOf(value) {
132
+ const [rain, snow, dust] = amounts(value);
133
+ return { rain, snow, dust, temperatureOffset: value.temperatureOffset ?? 0, lightning: value.lightning ?? 0 };
134
+ }
135
+ function blend(a, b, weight) {
136
+ const out = { ...a };
137
+ for (const key of ["rain", "snow", "dust", "temperatureOffset", "lightning"])
138
+ out[key] += (b[key] - a[key]) * weight;
139
+ return out;
140
+ }
141
+ function scheduledWeather(initial, transitions, time) {
142
+ let current = initial;
143
+ for (const entry of transitions) {
144
+ if (time < entry.at)
145
+ break;
146
+ current = blend(entry.from, entry.to, entry.duration === 0 ? 1 : Math.min(1, (time - entry.at) / entry.duration));
147
+ }
148
+ return current;
149
+ }
150
+ function compileWeather(source) {
151
+ validateAuthoredWeather(source);
152
+ const config = JSON.parse(JSON.stringify(source));
153
+ const profiles = new Map((config.profiles ?? []).map((profile) => [profile.id, Object.freeze(blendOf(profile))]));
154
+ const initial = config.initialProfileId === undefined ? Object.freeze(blendOf(config.ambient ?? CLEAR)) : profiles.get(config.initialProfileId);
155
+ const ambientWind = config.wind === undefined ? null : windField(config.wind);
156
+ const zones = (config.zones ?? []).map((zone) => Object.freeze({ zone, field: zone.wind === undefined ? null : windField(zone.wind) }));
157
+ const transitions = [];
158
+ for (const entry of config.schedule ?? []) {
159
+ transitions.push(Object.freeze({ at: entry.atSeconds, duration: entry.transitionSeconds ?? 0, from: Object.freeze(scheduledWeather(initial, transitions, entry.atSeconds)), to: profiles.get(entry.profileId) }));
160
+ }
161
+ return Object.freeze({ initial, profiles, ambientWind, zones: Object.freeze(zones), transitions: Object.freeze(transitions) });
162
+ }
163
+ /** Pure authored weather sampling; the caller's simulation clock owns pause, replay and restore.
164
+ * @capability authored-weather Sample scheduled precipitation and local wind from one authored document and simulation clock.
165
+ */
166
+ export function createAuthoredWeather(source = {}) {
167
+ const compiled = compileWeather(source);
168
+ return {
169
+ sample(x, z, timeSeconds) {
170
+ finite(x, "weather sample x", -MAX_DOMAIN, MAX_DOMAIN);
171
+ finite(z, "weather sample z", -MAX_DOMAIN, MAX_DOMAIN);
172
+ finite(timeSeconds, "weather sample time", 0, MAX_DOMAIN);
173
+ let value = scheduledWeather(compiled.initial, compiled.transitions, timeSeconds);
174
+ const ambient = compiled.ambientWind?.atPoint(x, z, timeSeconds) ?? [0, 0];
175
+ let wx = ambient[0];
176
+ let wz = ambient[1];
177
+ for (const { zone, field } of compiled.zones) {
178
+ const dx = x - zone.center[0];
179
+ const dz = z - zone.center[1];
180
+ const distance = Math.hypot(dx, dz);
181
+ const falloff = zone.falloff ?? 0;
182
+ if (distance > zone.radius + falloff)
183
+ continue;
184
+ const t = distance <= zone.radius ? 1 : 1 - (distance - zone.radius) / falloff;
185
+ const weight = t * t * (3 - 2 * t);
186
+ if (zone.weather !== undefined)
187
+ value = blend(value, blendOf(zone.weather), weight);
188
+ if (field !== null) {
189
+ const vector = zoneWind(field, zone.radial ?? false, dx, dz, distance, timeSeconds);
190
+ wx += vector[0] * weight;
191
+ wz += vector[1] * weight;
192
+ }
193
+ }
194
+ const precipitation = value.rain + value.snow;
195
+ return { ...value, mode: modeOf(value.rain, value.snow, value.dust), intensity: Math.min(1, precipitation + value.dust), timeSeconds, wind: [wx, wz], precipitation };
196
+ },
197
+ };
198
+ }
199
+ function zoneWind(field, radial, dx, dz, distance, time) {
200
+ if (!radial)
201
+ return field.atPoint(dx, dz, time);
202
+ if (distance === 0)
203
+ return [0, 0];
204
+ const speed = field.strengthAt(dx, dz, time);
205
+ return [dx / distance * speed, dz / distance * speed];
206
+ }
207
+ function exposureConfig(config) {
208
+ const out = { maxSurfaces: config.maxSurfaces ?? 1024, wettingRate: config.wettingRate ?? 0.2, dryingRate: config.dryingRate ?? 0.05, snowRate: config.snowRate ?? 0.1, meltRate: config.meltRate ?? 0.02 };
209
+ if (!Number.isInteger(out.maxSurfaces) || out.maxSurfaces < 1 || out.maxSurfaces > 65_536)
210
+ throw new Error("weather exposure maxSurfaces must be an integer in 1..65536");
211
+ for (const key of ["wettingRate", "dryingRate", "snowRate", "meltRate"])
212
+ finite(out[key], `weather exposure ${key}`, 0);
213
+ return out;
214
+ }
215
+ const clamp01 = (value) => Math.max(0, Math.min(1, value));
216
+ /** Bounded surface accumulation shared by gameplay and material adapters, with injected persistence.
217
+ * @capability weather-surface-accumulation Persist and retune bounded surface wetness, snow, temperature and exposure independently of rendering.
218
+ */
219
+ export function createWeatherExposure(config = {}, storage) {
220
+ let tuning = exposureConfig(config);
221
+ const values = new Map();
222
+ const snapshot = () => ({ surfaces: Array.from(values, ([id, value]) => ({ id, value: { ...value } })) });
223
+ const persist = () => storage?.write(snapshot());
224
+ function restore(state) {
225
+ if (state.surfaces.length > tuning.maxSurfaces)
226
+ throw new Error("weather exposure snapshot exceeds capacity");
227
+ const next = new Map();
228
+ for (const entry of state.surfaces) {
229
+ if (!entry.id || next.has(entry.id))
230
+ throw new Error("weather exposure snapshot has invalid surface ids");
231
+ for (const key of ["wetness", "snow", "heat", "exposure"])
232
+ finite(entry.value[key], `weather exposure ${key}`);
233
+ for (const key of ["wetness", "snow", "exposure"])
234
+ if (entry.value[key] < 0 || entry.value[key] > 1)
235
+ throw new Error(`weather exposure ${key} must be in 0..1`);
236
+ next.set(entry.id, { ...entry.value });
237
+ }
238
+ values.clear();
239
+ for (const [id, value] of next)
240
+ values.set(id, value);
241
+ persist();
242
+ }
243
+ const saved = storage?.read();
244
+ if (saved !== undefined)
245
+ restore(saved);
246
+ return {
247
+ step(dt, timeSeconds, surfaces, weather) {
248
+ finite(dt, "weather exposure dt", 0);
249
+ finite(timeSeconds, "weather exposure time", 0);
250
+ if (dt === 0)
251
+ return;
252
+ if (surfaces.length > tuning.maxSurfaces)
253
+ throw new Error("weather exposure step exceeds capacity");
254
+ const ids = new Set();
255
+ let added = 0;
256
+ for (const surface of surfaces) {
257
+ if (!surface.id || ids.has(surface.id))
258
+ throw new Error("weather exposure step has invalid surface ids");
259
+ ids.add(surface.id);
260
+ finite(surface.x, "weather exposure x");
261
+ finite(surface.z, "weather exposure z");
262
+ if (surface.exposure !== undefined)
263
+ finite(surface.exposure, "weather exposure exposure", 0);
264
+ if ((surface.exposure ?? 1) > 1)
265
+ throw new Error("weather exposure exposure must be <= 1");
266
+ if (surface.temperature !== undefined)
267
+ finite(surface.temperature, "weather exposure temperature");
268
+ if (!values.has(surface.id))
269
+ added += 1;
270
+ }
271
+ if (values.size + added > tuning.maxSurfaces)
272
+ throw new Error("weather exposure capacity exhausted; forget unused surfaces");
273
+ for (const surface of surfaces) {
274
+ const sample = weather.sample(surface.x, surface.z, timeSeconds);
275
+ const current = values.get(surface.id) ?? { wetness: 0, snow: 0, heat: 0, exposure: 1 };
276
+ const exposure = surface.exposure ?? 1;
277
+ const heat = surface.temperature ?? sample.temperatureOffset ?? 0;
278
+ const rain = sample.rain * exposure;
279
+ const snowfall = sample.snow * exposure * tuning.snowRate * dt;
280
+ const melt = Math.min(current.snow + snowfall, Math.max(0, heat) * tuning.meltRate * dt);
281
+ values.set(surface.id, {
282
+ wetness: clamp01(current.wetness + (rain * tuning.wettingRate - (1 - rain) * tuning.dryingRate) * dt + melt),
283
+ snow: clamp01(current.snow + snowfall - melt),
284
+ heat,
285
+ exposure,
286
+ });
287
+ }
288
+ persist();
289
+ },
290
+ sample(id) { const value = values.get(id); return value === undefined ? null : { ...value }; },
291
+ forget(id) { if (values.delete(id))
292
+ persist(); },
293
+ snapshot,
294
+ restore,
295
+ retune(next) {
296
+ const resolved = exposureConfig(next);
297
+ if (values.size > resolved.maxSurfaces)
298
+ throw new Error("weather exposure retune capacity is below retained surface count");
299
+ tuning = resolved;
300
+ },
301
+ };
302
+ }
303
+ /** Environment temperature, sunlight and shelter read the same authored precipitation as rendering.
304
+ * @capability weather-environment-field Connect authored precipitation and temperature changes to spatial environment queries.
305
+ */
306
+ export function createWeatherEnvironmentField(weather, config = {}) {
307
+ const base = createEnvironmentField({ ...config, rain: (x, z, time) => weather.sample(x, z, time).rain });
308
+ const temperature = (x, z, time, y) => base.temperature(x, z, time, y) + (weather.sample(x, z, time).temperatureOffset ?? 0);
309
+ return {
310
+ ...base,
311
+ temperature,
312
+ sample(x, z, time, y) { return { ...base.sample(x, z, time, y), temperature: temperature(x, z, time, y) }; },
313
+ };
314
+ }
@@ -7,7 +7,7 @@
7
7
  * @capability building-generator seeded building as a placeable parametric asset
8
8
  */
9
9
  import { registerAssetGenerator, partsBounds } from "../scene/assetGenerator.js";
10
- import { BUILDING_STYLE_PALETTES, DEFAULT_BUILDING_CONFIG, generateBuilding, resolveBuildingPalette } from "./buildings.js";
10
+ import { BUILDING_STYLE_PALETTES, DEFAULT_BUILDING_CONFIG, buildingSurfaceColor, generateBuilding, resolveBuildingPalette } from "./buildings.js";
11
11
  /** The generator id a catalog entry / placed marker references. */
12
12
  export const BUILDING_GENERATOR_ID = "building";
13
13
  /** The building generator's slider schema — drives the inspector via #809. */
@@ -47,7 +47,7 @@ export function generateBuildingAsset(params, seed) {
47
47
  position: part.position,
48
48
  size: part.scale,
49
49
  rotationY: part.rotationY,
50
- color: palette[part.kind],
50
+ color: buildingSurfaceColor(palette[part.kind]),
51
51
  }));
52
52
  return { parts, bounds: partsBounds(parts) };
53
53
  }
@@ -3,7 +3,8 @@
3
3
  * as an instanced asset kit instead of untextured blocks. Pure serializable data — core resolves
4
4
  * bindings, the renderer loads them.
5
5
  */
6
- import type { BuildingKitSlot, BuildingPartKind, BuildingVariantCounts, Vec3 } from "./buildings.js";
6
+ import { type MaterialAsset, type MaterialAssignment } from "../material/materialAsset.js";
7
+ import type { BuildingKitSlot, BuildingPartKind, BuildingSurfaceMaterial, BuildingVariantCounts, Vec3 } from "./buildings.js";
7
8
  /**
8
9
  * How a kit model's native bounds are mapped onto the slot box `BuildingPartPlacement.scale`
9
10
  * describes.
@@ -32,6 +33,10 @@ export interface BuildingKitPart {
32
33
  /** Multiplier applied after `fit`. */
33
34
  scale?: Vec3;
34
35
  tint?: string;
36
+ /** Tiled PBR maps applied over the model's own materials; `color` tints, `repeat` sets the tiling. */
37
+ material?: BuildingSurfaceMaterial;
38
+ /** Editor-authored mesh/slot selectors applied after the broad tint and surface. */
39
+ materialAssignments?: readonly MaterialAssignment[];
35
40
  /**
36
41
  * Quarter-turn the model when its long horizontal axis disagrees with the slot's, so a panel
37
42
  * authored running along Z still tiles a bay that runs along X. Default true — modular kits do not
@@ -47,6 +52,8 @@ export interface BuildingKitPart {
47
52
  */
48
53
  export interface BuildingKit {
49
54
  id: string;
55
+ /** The caller's existing editor-document material assets; only assigned IDs are loaded. */
56
+ materialAssets?: readonly MaterialAsset[];
50
57
  /** Variants per part kind; `BuildingKitSlot.variant` indexes this list, wrapping. */
51
58
  parts: Readonly<Partial<Record<BuildingPartKind, readonly BuildingKitPart[]>>>;
52
59
  /** Overrides keyed by `BuildingKitSlot.key` (`${facade}.${kind}`); beats `parts` for that facade. */
@@ -61,6 +68,7 @@ export type BuildingKitPartInput = string | BuildingKitPart;
61
68
  /** The authoring shape {@link defineBuildingKit} normalizes into a {@link BuildingKit}. */
62
69
  export interface BuildingKitInput {
63
70
  id?: string;
71
+ materialAssets?: readonly MaterialAsset[];
64
72
  parts: Readonly<Partial<Record<BuildingPartKind, readonly BuildingKitPartInput[]>>>;
65
73
  slots?: Readonly<Record<string, readonly BuildingKitPartInput[]>>;
66
74
  omit?: readonly BuildingPartKind[];
@@ -83,10 +91,9 @@ export type BuildingKitBinding = {
83
91
  };
84
92
  /**
85
93
  * Validates and normalizes a kit description, accepting bare model strings as single-field variants.
86
- * Throws on empty variant lists so a typo fails at authoring time rather than silently falling back
87
- * to untextured boxes.
94
+ * Throws on empty variants or invalid editor material references/selectors before loading models.
88
95
  *
89
- * @capability building-kit bind generated facade part slots to real models instead of untextured blocks
96
+ * @capability building-kit bind generated facade part slots to caller models and editor-authored named materials
90
97
  */
91
98
  export declare function defineBuildingKit(input: BuildingKitInput): BuildingKit;
92
99
  /**
@@ -1,36 +1,49 @@
1
+ /**
2
+ * Binds the part slots `world/buildings` generates to real models, so a procedural building renders
3
+ * as an instanced asset kit instead of untextured blocks. Pure serializable data — core resolves
4
+ * bindings, the renderer loads them.
5
+ */
6
+ import { parseMaterialAssignments, validateMaterialAssignments } from "../material/materialAsset.js";
1
7
  const BOX = { type: "box" };
2
8
  const OMIT = { type: "omit" };
3
9
  const DEFAULT_FIT = "stretch";
4
- function normalizePart(input, where) {
10
+ function normalizePart(input, where, assets) {
5
11
  const part = typeof input === "string" ? { model: input } : input;
6
12
  if (typeof part.model !== "string" || part.model.length === 0) {
7
13
  throw new Error(`Building kit ${where} has a variant with no model reference.`);
8
14
  }
15
+ if (part.materialAssignments !== undefined) {
16
+ const assignments = parseMaterialAssignments(part.materialAssignments);
17
+ const errors = validateMaterialAssignments(assignments, assets, { disallowedTextureRoles: ["height"] }).filter(item => item.severity === "error");
18
+ if (errors.length > 0)
19
+ throw new Error(`Building kit ${where}: ${errors.map(item => `${item.path}: ${item.message}`).join("; ")}`);
20
+ return { ...part, materialAssignments: assignments };
21
+ }
9
22
  return part;
10
23
  }
11
- function normalizeList(list, where) {
24
+ function normalizeList(list, where, assets) {
12
25
  if (list.length === 0) {
13
26
  throw new Error(`Building kit ${where} binds an empty variant list. Bind at least one model, or add the kind to \`omit\` to drop it.`);
14
27
  }
15
- return list.map((entry) => normalizePart(entry, where));
28
+ return list.map((entry) => normalizePart(entry, where, assets));
16
29
  }
17
30
  /**
18
31
  * Validates and normalizes a kit description, accepting bare model strings as single-field variants.
19
- * Throws on empty variant lists so a typo fails at authoring time rather than silently falling back
20
- * to untextured boxes.
32
+ * Throws on empty variants or invalid editor material references/selectors before loading models.
21
33
  *
22
- * @capability building-kit bind generated facade part slots to real models instead of untextured blocks
34
+ * @capability building-kit bind generated facade part slots to caller models and editor-authored named materials
23
35
  */
24
36
  export function defineBuildingKit(input) {
25
37
  const parts = {};
26
38
  for (const [kind, list] of Object.entries(input.parts)) {
27
39
  if (list === undefined)
28
40
  continue;
29
- parts[kind] = normalizeList(list, `"${input.id ?? "kit"}" part "${kind}"`);
41
+ parts[kind] = normalizeList(list, `"${input.id ?? "kit"}" part "${kind}"`, input.materialAssets ?? []);
30
42
  }
31
43
  const kit = {
32
44
  id: input.id ?? "building-kit",
33
45
  parts,
46
+ ...(input.materialAssets === undefined ? {} : { materialAssets: input.materialAssets }),
34
47
  ...(input.omit === undefined ? {} : { omit: input.omit }),
35
48
  ...(input.fit === undefined ? {} : { fit: input.fit }),
36
49
  };
@@ -38,7 +51,7 @@ export function defineBuildingKit(input) {
38
51
  return kit;
39
52
  const slots = {};
40
53
  for (const [key, list] of Object.entries(input.slots)) {
41
- slots[key] = normalizeList(list, `"${kit.id}" slot "${key}"`);
54
+ slots[key] = normalizeList(list, `"${kit.id}" slot "${key}"`, input.materialAssets ?? []);
42
55
  }
43
56
  return { ...kit, slots };
44
57
  }
@@ -103,11 +103,43 @@ export interface BuildingGridConfig {
103
103
  floorRange?: readonly [number, number];
104
104
  }
105
105
  export declare const DEFAULT_BUILDING_CONFIG: BuildingConfig;
106
- export type BuildingPalette = Record<BuildingPartKind, string>;
106
+ /** URLs of the PBR maps a building surface tiles; `buildMaterialCatalog(...).resolve(id)!.maps` fits directly. */
107
+ export interface BuildingSurfaceMaps {
108
+ color?: string;
109
+ normal?: string;
110
+ roughness?: string;
111
+ ao?: string;
112
+ metalness?: string;
113
+ }
114
+ /**
115
+ * A textured building surface: a base colour plus optional tiled PBR maps. With `maps`, `color`
116
+ * multiplies the albedo (leave it light to show the map as authored); without, it is the flat
117
+ * colour the part has always had.
118
+ */
119
+ export interface BuildingSurfaceMaterial {
120
+ /** Base colour; the style's colour for that part kind when unset. */
121
+ color?: string;
122
+ maps?: BuildingSurfaceMaps;
123
+ /**
124
+ * Texture tiles per part slot along [x, y]; a number tiles both. Slots within one building share
125
+ * a size, so one repeat per kind is uniform there; it drifts across buildings with a different
126
+ * `bayWidth`. Default 1.
127
+ */
128
+ repeat?: number | readonly [number, number];
129
+ roughness?: number;
130
+ metalness?: number;
131
+ }
132
+ /** What a building part kind is painted with: a hex colour, or a colour plus tiled PBR maps. */
133
+ export type BuildingSurface = string | BuildingSurfaceMaterial;
134
+ export type BuildingPalette = Record<BuildingPartKind, BuildingSurface>;
107
135
  export type BuildingPaletteOverrides = Partial<BuildingPalette>;
136
+ /** The flat colour of a surface: the hex itself, or a material's `color`, else `fallback`. */
137
+ export declare function buildingSurfaceColor(surface: BuildingSurface | undefined, fallback?: string): string;
138
+ /** Normalises a surface to its material form so a renderer reads one shape. */
139
+ export declare function resolveBuildingSurface(surface: BuildingSurface): BuildingSurfaceMaterial;
108
140
  export type BuildingStyle = "generic" | "capital" | "village" | "desert" | "industrial" | "coastal" | "neon" | "ruin" | "frontier" | "aerial";
109
141
  export declare const DEFAULT_BUILDING_STYLE: BuildingStyle;
110
- export declare const BUILDING_STYLE_PALETTES: Record<BuildingStyle, BuildingPalette>;
142
+ export declare const BUILDING_STYLE_PALETTES: Record<BuildingStyle, Record<BuildingPartKind, string>>;
111
143
  /**
112
144
  * Per-style facade-tone family: the wall colours a district spreads across its buildings so
113
145
  * neighbours differ in hue and value while the block stays one palette. Index 0 is the palette's
@@ -38,6 +38,16 @@ export const DEFAULT_BUILDING_CONFIG = {
38
38
  probabilities: DEFAULT_PROBABILITIES,
39
39
  variants: DEFAULT_VARIANTS,
40
40
  };
41
+ /** The flat colour of a surface: the hex itself, or a material's `color`, else `fallback`. */
42
+ export function buildingSurfaceColor(surface, fallback = "#808080") {
43
+ if (surface === undefined)
44
+ return fallback;
45
+ return typeof surface === "string" ? surface : (surface.color ?? fallback);
46
+ }
47
+ /** Normalises a surface to its material form so a renderer reads one shape. */
48
+ export function resolveBuildingSurface(surface) {
49
+ return typeof surface === "string" ? { color: surface } : surface;
50
+ }
41
51
  export const DEFAULT_BUILDING_STYLE = "generic";
42
52
  export const BUILDING_STYLE_PALETTES = {
43
53
  generic: {
@@ -207,7 +217,7 @@ export function resolveBuildingWallTones(style = DEFAULT_BUILDING_STYLE) {
207
217
  const tones = BUILDING_STYLE_WALL_TONES[style];
208
218
  if (tones !== undefined && tones.length > 0)
209
219
  return tones;
210
- return [resolveBuildingPalette(style).wall];
220
+ return [buildingSurfaceColor(resolveBuildingPalette(style).wall)];
211
221
  }
212
222
  /** @internal */
213
223
  export function resolveBuildingPalette(style = DEFAULT_BUILDING_STYLE, overrides) {
@@ -2,6 +2,7 @@ import { type ParamSchema, type SceneKindObject, type SceneKindResolveContext, t
2
2
  import { type BuildingStyle } from "./buildings.js";
3
3
  import { type CityLotClass, type CityLotPiece, type CityTreeSpecies, type CityZoneBand, type CityZoneProfile } from "./cityContent.js";
4
4
  import { type CircuitCorner, type CircuitGate, type CircuitSealKind } from "./raceCircuit.js";
5
+ import type { WorldSolid } from "./worldSolids.js";
5
6
  import type { RoadPoint } from "./roads.js";
6
7
  import { type StreetFeatureKind } from "./streetGenerator.js";
7
8
  import { type CityBlockKind } from "./cityBlocks.js";
@@ -432,6 +433,12 @@ export interface ResolvedCity {
432
433
  /** The instanced street race through these streets, when `race` is on and a lap could be found. */
433
434
  race?: ResolvedRace;
434
435
  }
436
+ /**
437
+ * One solid per wall and roof piece of every lot, grounded the way the city renderer grounds it:
438
+ * grade at the highest lot corner, foundations down to half a metre under the lowest.
439
+ * @capability city-district editor-authorable procedural city district (streets, zoning, buildings, furniture)
440
+ */
441
+ export declare function citySolids(city: ResolvedCity, sampleHeight?: (x: number, z: number) => number): WorldSolid[];
435
442
  /** Extended resolve context: sibling `cityzone` volumes that override the band/mix locally. */
436
443
  export interface CityResolveContext extends SceneKindResolveContext {
437
444
  zoneOverrides?: readonly SceneKindObject[];
@@ -164,6 +164,7 @@ export const CITY_SCHEMA = {
164
164
  options: [{ value: "auto" }, { value: "gravel" }],
165
165
  },
166
166
  { type: "bool", key: "fabric", label: "buildings & parcels", group: "layout", default: CITY_DEFAULTS.fabric },
167
+ { type: "bool", key: "solid", label: "solid buildings", group: "layout", default: true },
167
168
  { type: "bool", key: "bridges", label: "bridges over water", group: "layout", default: CITY_DEFAULTS.bridges },
168
169
  { type: "bool", key: "tunnels", label: "tunnels through ridges", group: "layout", default: CITY_DEFAULTS.tunnels },
169
170
  {
@@ -638,6 +639,55 @@ export function readCityRules(meta) {
638
639
  seed: params["seed"],
639
640
  };
640
641
  }
642
+ /** Lot pieces that block: walls and roofs. Trim and accents (cornices, awnings) stay passable. */
643
+ const SOLID_PIECE_ROLES = new Set(["wall", "roof"]);
644
+ /**
645
+ * One solid per wall and roof piece of every lot, grounded the way the city renderer grounds it:
646
+ * grade at the highest lot corner, foundations down to half a metre under the lowest.
647
+ * @capability city-district editor-authorable procedural city district (streets, zoning, buildings, furniture)
648
+ */
649
+ export function citySolids(city, sampleHeight = () => 0) {
650
+ const solids = [];
651
+ for (const lot of city.lots) {
652
+ const c = Math.cos(lot.rotationY);
653
+ const s = Math.sin(lot.rotationY);
654
+ const hw = lot.size[0] / 2;
655
+ const hd = lot.size[1] / 2;
656
+ let minY = Infinity;
657
+ let maxY = -Infinity;
658
+ for (const [dx, dz] of [
659
+ [hw, hd],
660
+ [hw, -hd],
661
+ [-hw, hd],
662
+ [-hw, -hd],
663
+ ]) {
664
+ const y = sampleHeight(lot.center[0] + dx * c + dz * s, lot.center[1] - dx * s + dz * c);
665
+ minY = Math.min(minY, y);
666
+ maxY = Math.max(maxY, y);
667
+ }
668
+ const grade = maxY;
669
+ const foundationBase = minY - 0.5;
670
+ for (const piece of lot.pieces) {
671
+ if (!SOLID_PIECE_ROLES.has(piece.role))
672
+ continue;
673
+ const bottom = piece.grounded ? foundationBase : grade + piece.offset[1];
674
+ const top = piece.grounded ? grade + piece.size[1] : grade + piece.offset[1] + piece.size[1];
675
+ if (top - bottom <= 0 || piece.size[0] <= 0 || piece.size[2] <= 0)
676
+ continue;
677
+ const yaw = lot.rotationY + piece.rotationY;
678
+ solids.push({
679
+ center: [
680
+ lot.center[0] + piece.offset[0] * c + piece.offset[2] * s,
681
+ (bottom + top) / 2,
682
+ lot.center[1] - piece.offset[0] * s + piece.offset[2] * c,
683
+ ],
684
+ halfExtents: [piece.size[0] / 2, (top - bottom) / 2, piece.size[2] / 2],
685
+ ...(yaw === 0 ? {} : { rotationY: yaw }),
686
+ });
687
+ }
688
+ }
689
+ return solids;
690
+ }
641
691
  /** Bounded-work caps so a huge volume can never generate unbounded content. */
642
692
  const MAX_STREETS = 320;
643
693
  /** Building budget — the shared scatterable cap so the inspector's "capped at N (budget)" note is truthful. */
@@ -1302,7 +1352,7 @@ export function resolveCityObject(object, context) {
1302
1352
  return Math.hypot(dx, dz) / (2 * d);
1303
1353
  };
1304
1354
  // Child `cityzone` volumes override the band (and optionally the class mix) for lots inside them.
1305
- const overrides = (context?.zoneOverrides ?? []).filter((volume) => volume.kind === CITY_ZONE_KIND);
1355
+ const overrides = (context?.zoneOverrides ?? context?.objects ?? []).filter((volume) => volume.kind === CITY_ZONE_KIND);
1306
1356
  const overrideAt = overrides.length === 0
1307
1357
  ? null
1308
1358
  : (x, z) => {
@@ -1789,6 +1839,7 @@ export function registerCityKind() {
1789
1839
  schema: CITY_SCHEMA,
1790
1840
  coverage: { spec: "city", densityKey: "roadsideOccupancy" },
1791
1841
  resolve: (object, _params, context) => resolveCityObject(object, context),
1842
+ solids: (resolved, _object, params, context) => resolved === null || params["solid"] === false ? [] : citySolids(resolved, context.sampleHeight),
1792
1843
  note: (object) => {
1793
1844
  const resolved = resolveCityObject(object);
1794
1845
  if (resolved === null)
@@ -0,0 +1,21 @@
1
+ import type { GameContext } from "../runtime/gameContextTypes.js";
2
+ import type { MotionIntents } from "../runtime/motionIntents.js";
3
+ /** The game selects affected players, coupling, target bits and its acceleration limit. */
4
+ export interface EnvironmentMotionTarget {
5
+ entityId: string;
6
+ motion: Pick<MotionIntents, "impulse" | "pushHorizontal">;
7
+ /** Wind coupling in 1/second; supported range 0..1e12. */
8
+ windResponse: number;
9
+ /** Maximum combined acceleration in metres/second²; supported range 0..1e12. */
10
+ maxAcceleration: number;
11
+ mask?: number;
12
+ /** Opt into authored shelter exposure scaling for wind and localized forces. */
13
+ sheltered?: boolean;
14
+ }
15
+ /** Inject authoritative spatial forces before existing movement consumes its motion intents.
16
+ * @capability environment-motion apply capped spatial wind and vortex forces to game-selected actors through shared motion intents
17
+ */
18
+ export declare function installEnvironmentMotion(ctx: GameContext, options: {
19
+ targets: () => readonly EnvironmentMotionTarget[];
20
+ maxTargets?: number;
21
+ }): () => void;