@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
@@ -120,7 +120,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
120
120
 
121
121
  - `createEncounterState` (function) · `import { createEncounterState } from "@jgengine/core/combat"`
122
122
 
123
- ## event-meter — a heat/streak gauge that builds from repeated hits and cools down over time
123
+ ## event-meter — a heat/streak/alert gauge (wanted level, rage, combo) that builds from repeated events and cools down over time
124
124
 
125
125
  - `createEventMeter` (function) · `import { createEventMeter } from "@jgengine/core/combat"`
126
126
 
@@ -128,6 +128,14 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
128
128
 
129
129
  - `createFireCadence` (function) · `import { createFireCadence } from "@jgengine/core/combat"`
130
130
 
131
+ ## global-cooldown — share one cooldown across every ability slot (MMO global cooldown) with a cooldown group
132
+
133
+ - `createAbilityKit` (function) · `import { createAbilityKit } from "@jgengine/core/combat"`
134
+
135
+ ## hitscan-shot — aim a hitscan shot (shoot a gun): resolve origin and direction from the eye or muzzle for a raycast
136
+
137
+ - `resolveShot` (function) · `import { resolveShot } from "@jgengine/core/combat"`
138
+
131
139
  ## immunity-window — reject damage during a per-target invulnerability window installed by transitions
132
140
 
133
141
  - `createImmunityWindow` (function) · `import { createImmunityWindow } from "@jgengine/core/combat"`
@@ -141,6 +149,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
141
149
 
142
150
  - `createMagazine` (function) · `import { createMagazine } from "@jgengine/core/combat"`
143
151
 
152
+ ## on-death-drops — make a killed enemy drop loot on the ground or into the killer's bag via catalog onDeath
153
+
154
+ - `OnDeathSpec` (interface) · `import { OnDeathSpec } from "@jgengine/core/combat"`
155
+
144
156
  ## parry-window — time a block/parry/i-frame defensive window against incoming hits
145
157
 
146
158
  - `createDefensiveWindow` (function) · `import { createDefensiveWindow } from "@jgengine/core/combat"`
@@ -236,6 +248,22 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
236
248
 
237
249
  - `vfxPresets` (const) · `import { vfxPresets } from "@jgengine/core/combat"`
238
250
 
251
+ ## weapon-feedback — turn weapon shots, recoil kick and impacts into per-tick feedback-mixer signals for camera shake, audio and rumble
252
+
253
+ - `createWeaponFeedbackSignals` (function) · `import { createWeaponFeedbackSignals } from "@jgengine/core/combat/weaponFeedback"`
254
+
255
+ ## weapon-handling — recoil patterns with camera kick and recovery, spread bloom, stance multipliers and ADS as serializable state
256
+
257
+ - `createWeaponHandling` (function) · `import { createWeaponHandling } from "@jgengine/core/combat/weaponHandling"`
258
+
259
+ ## weapon-metrics — measure a weapon's feel as numbers — spread per shot, climb over a magazine, reset time, ADS time, time-to-kill at a range
260
+
261
+ - `measureWeapon` (function) · `import { measureWeapon } from "@jgengine/core/combat/weaponHandling"`
262
+
263
+ ## weapon-presentation — per-weapon viewmodel offset, ADS pose, viewmodel FOV, sway, bob and recoil kick from a weapon handling frame
264
+
265
+ - `createWeaponPresentation` (function) · `import { createWeaponPresentation } from "@jgengine/core/combat/weaponPresentation"`
266
+
239
267
  ## weapon-runtime — compose cadence, magazine, a caller-owned raycast, and portable damage resolution into a fire controller
240
268
 
241
269
  - `createWeaponRuntime` (function) · `import { createWeaponRuntime } from "@jgengine/core/combat"`
@@ -1,8 +1,8 @@
1
1
  # Save and restore closure-backed combat runtimes
2
2
 
3
- Use this recipe when an existing project wants JGengine's convenient magazine
4
- or stat-modifier runtime but keeps its own save system, clock, entities, and
5
- authority. Both runtimes expose detached plain snapshots; no JGengine store or
3
+ Use this recipe when an existing project wants JGengine's combat runtimes but
4
+ keeps its own save system, clock, entities, and authority. The runtimes expose
5
+ detached plain state; no JGengine store or
6
6
  `GameContext` is required.
7
7
 
8
8
  ## Install and focused imports
@@ -132,18 +132,72 @@ and restores that reserve directly. For a caller-owned `MagazineReserve`, save
132
132
  and restore the caller's reserve first; `restore` returns `false` rather than
133
133
  partially changing loaded/reload state when it cannot reconcile the reserve.
134
134
 
135
+ ## Retune without reconstructing the magazine
136
+
137
+ Equipment or upgrades can change capacity and reload duration in place:
138
+
139
+ ```ts
140
+ if (!magazine.retune({ capacity: 8, reloadMs: 750 })) {
141
+ throw new Error("capacity would strand loaded rounds");
142
+ }
143
+ ```
144
+
145
+ Growing capacity never grants ammo. Retuning preserves loaded rounds, shared
146
+ reserve identity/value, and elapsed reload milliseconds. A shorter duration
147
+ can put `reloadFraction()` at 1; only the next positive `tick` completes the
148
+ reload and draws reserve. Save the current tuning in the caller's equipment
149
+ schema and supply it when recreating the runtime before restoring its snapshot.
150
+
151
+ Shrinking below the loaded count rejects the entire tuning by default. Choose
152
+ `overflow: "return-to-reserve"` to return exactly the excess to a finite reserve
153
+ with `gain`, or `overflow: "discard"` to explicitly destroy those rounds. Returning
154
+ to an infinite reserve or a read/spend-only adapter rejects without changing
155
+ capacity, duration, or ammo.
156
+
157
+ ## Abilities, shields, and meters
158
+
159
+ Ability kits, shields, and meters use the same save boundary:
160
+
161
+ ```ts
162
+ const saved = JSON.stringify({
163
+ abilities: kit.state(),
164
+ shield: shield.state(),
165
+ heat: heat.state(),
166
+ });
167
+ const decoded = JSON.parse(saved);
168
+ if (!kit.restore(decoded.abilities)) throw new Error("ability slot/group ids differ");
169
+ if (!shield.restore(decoded.shield)) throw new Error("external shield pool max differs");
170
+ heat.restore(decoded.heat);
171
+ ```
172
+
173
+ `AbilityKit.state()` contains charges, separate recharge/flash/group timers,
174
+ and retuned cooldowns/costs. `state(slotId, resourceAvailable?)` and `snapshot()`
175
+ remain HUD views. Rebuild with the same slot/group definitions; save any bound
176
+ resource separately. Restore never spends it.
177
+
178
+ `RegenShield.state()` includes mutable bounds, pool value, grace timing, the
179
+ active delay override, and the last external-pool observation. Restore writes
180
+ the pool value, preserving even damage waiting to be observed on the next tick;
181
+ the caller's pool must already have the saved max. Use `refill(amount)` for
182
+ pickups; numeric `restore(amount)` remains a deprecated compatibility alias.
183
+
184
+ `EventMeter.state()` and `AccumulatorMeter.state()` contain value, the hold
185
+ latch, and the decay-delay timer. Rebuild with the same meter configuration.
186
+ For caller-owned visibility policy, `heat.tick(dt, !sourceVisible)` pauses both
187
+ decay and its delay while seen; omitting the second argument advances normally.
188
+
135
189
  ## Ownership
136
190
 
137
191
  The existing project owns save versioning, entity identity, clock value,
138
192
  reserve state, networking/rollback policy, content, and when ticks occur.
139
- JGengine owns the closure convenience methods, snapshot encoding shape,
193
+ JGengine owns the closure convenience methods, state encoding shape,
140
194
  detached copies, and deterministic continuation once the caller supplies the
141
195
  same config and clock.
142
196
 
143
197
  ## Common traps
144
198
 
145
- - Re-supply immutable configuration such as magazine capacity/reload duration
146
- and the stat id set when rebuilding a runtime; snapshots contain mutable
199
+ - Re-supply current tuning such as magazine capacity/reload duration
200
+ and immutable configuration such as the stat id set when rebuilding a runtime; snapshots contain mutable
147
201
  state, not content catalogs.
148
202
  - Restore externally-owned reserve data before calling `Magazine.restore`.
149
203
  - Persist the injected clock alongside expiring modifier snapshots or convert
@@ -123,14 +123,48 @@ exposes `elapsedMs()`/`restore()` for HUD and snapshots:
123
123
  ```ts
124
124
  const snapshot = {
125
125
  cadenceMs: rifle.cadence.elapsedMs(),
126
- loaded: rifle.magazine?.loaded() ?? null,
126
+ magazine: rifle.magazine?.snapshot() ?? null,
127
127
  };
128
128
  // After load: recreate the runtime from the same config, then:
129
129
  rifle.cadence.restore(snapshot.cadenceMs);
130
+ if (snapshot.magazine !== null && !rifle.magazine?.restore(snapshot.magazine)) {
131
+ throw new Error("magazine reserve configuration differs");
132
+ }
130
133
  ```
131
134
 
132
- Recreate the magazine at its saved `loaded` via `MagazineConfig.loaded`. (Full
133
- magazine snapshot/restore lands with the serializable-runtime-state slice.)
135
+ Restore caller-owned reserve storage first. Recreate with the current equipment
136
+ capacity/reload duration, then restore the full magazine snapshot; see the
137
+ [portable runtime-state recipe](portable-runtime-state.md) for live retuning.
138
+
139
+ ## Scene-backed projectiles and spread
140
+
141
+ When a project already uses `createProjectileSystem`, keep its
142
+ `fireProjectile(input)` → `settleProjectile(shotId)` call path. Pass authoritative
143
+ seeded randomness as `ProjectileSystemDeps.rng`. A nonzero `Aim.spread` (degrees)
144
+ or item `spread` samples an independent uniform solid-angle cone for each pellet
145
+ (the half angle is clamped to 90 degrees);
146
+ `pellets` chooses the count. Each sampled ray resolves its own nearest eligible
147
+ receiver and cover. An explicit `{ origin, direction }` aim remains an exact ray.
148
+ `willHitProjectile` stays a center-ray preview and consumes no randomness.
149
+
150
+ GameContext forwards its authoritative RNG to the projectile system. Standalone
151
+ callers must inject one for reproducible shots; the fallback is `Math.random`.
152
+ Aim fields, explicit ray vectors, and the chosen origin-policy vectors are copied
153
+ at fire time before RNG callbacks, so reusing caller input cannot redirect an
154
+ in-flight shot. The default policy is copied for each shot too. World entities
155
+ and colliders remain queried at settlement time.
156
+ Two RNG draws are captured at fire time per nonzero-spread pellet. Settlement
157
+ preserves the selected eye/muzzle/converge origin, named hitboxes, LOS, and
158
+ blocking colliders. Ballistic paths are unchanged. `maxPellets` defaults to 64
159
+ and clamps to [1, 256] to bound per-shot ray work. Zero-spread pellets each hit
160
+ the nearest receiver; they no longer cycle through receivers down one ray.
161
+
162
+ Scrap Signal is the first adopter: `items/use-handlers.ts` already calls these
163
+ projectile APIs, and its Breacher adds pellet/range/ammo tradeoffs. Its combat
164
+ adapter also needs `Magazine.retune` for live equipment/talent changes without
165
+ reconstructing ammo state. Games remain on published packages until the
166
+ coordinator verifies and unlocks the SDK release; these adoption points do not
167
+ claim native play against unpublished code.
134
168
 
135
169
  ## Ownership
136
170
 
@@ -0,0 +1,50 @@
1
+ # Recipe — weapon feel (feel target → metrics → handling knobs)
2
+
3
+ **What this wires:** a weapon that handles the way the brief says, from the shot to the camera, sound and rumble. There is no default rifle or shotgun: every weapon is a set of angles in radians and times in seconds that the game picks, checked against numbers it writes down first. A looter shooter's generated guns are the same fields, rolled from the game's own parts tables.
4
+
5
+ ## The seams
6
+
7
+ - **Cadence and ammo.** `createWeaponRuntime` or `weaponFire` + `magazine`, per the [portable weapon-plumbing recipe](portable-weapon-plumbing.md). Each time the cadence fires, call `handling.fire(stance)`.
8
+ - **Handling.** `createWeaponHandling(tuning, { random: ctx.rng })` from `@jgengine/core/combat/weaponHandling`.
9
+ - `recoil`: a `pattern` of `[pitch, yaw]` kicks per shot (or a flat `pitch`), `randomCone` from the injected RNG, `cameraShare` (view punch versus aim kick), `recoverRate`/`recoverDelay`, `adsScale`.
10
+ - `spread`: `base`, `perShot` bloom, `max`, `recoverRate`/`recoverDelay`, and `ads`/`crouching`/`moving`/`airborne` multipliers.
11
+ - `adsTime`.
12
+ - Sample the shot direction inside `fire().spread`. Add `tick(dt, stance).aimPitch/aimYaw` to the aim and `cameraPitch/cameraYaw` to the view only.
13
+ - `snapshot`/`restore` for prediction and replay; `retune` for attachments, parts and upgrades mid-fight.
14
+ - **Presentation.** Return `{ handling: handling.frame(), presentation }` from `camera.weapon(entityId)`, with a `createWeaponPresentation` tuning (`@jgengine/core/combat/weaponPresentation`): `hip`/`ads` viewmodel offsets, `viewmodelFov`, `adsZoom`, `sway`, `bob` and `kick`. The `first` rig poses the viewmodel, adds recoil to the look and zooms with `adsProgress`. The `shoulder` rig takes its ADS framing and recoil from the same frame.
15
+ - **Feedback.** `createWeaponFeedbackSignals()` from `@jgengine/core/combat/weaponFeedback`.
16
+ - Call `signals.shot(handling.fire(stance))` on each shot and `signals.impact(damage)` when a hit lands.
17
+ - Each tick, `mixer.update(dt, signals.read(handling.tick(dt, stance)))` on a `createFeedbackMixer` (`@jgengine/core/vfx/feedbackMixer`) the game declares.
18
+ - `kick`, `shot` and `impact` are one-tick pulses. Route `kick` without smoothing into a trauma target and pass it to `createCameraShake().add(...)` (`@jgengine/core/vfx/cameraShake`), so trauma lands once per shot.
19
+ - Route `impact` with a `release` into rumble targets for `ctx.input.rumble`, rate-limited to about 10 Hz. A threshold `event` on `impact` gives a hit-confirm one-shot.
20
+ - Play the gunshot once per `shot` count. `ads` and `spread` suit continuous targets such as a muffled gun tail or a HUD crosshair gap.
21
+ - **Proof.** `measureWeapon(() => createWeaponHandling(tuning), { interval, burst, damage, pellets, targetHealth, range, targetRadius, damageAt, stance })` returns deterministic numbers:
22
+ - `spreadByShot`, `firstShotSpread` and `tenthShotSpread`.
23
+ - `burstClimb`: set `burst` to the magazine size for climb over a magazine.
24
+ - `resetSeconds` and `adsSeconds`.
25
+ - `shotsToKill` and `timeToKill`: expected damage, where each projectile hits by the share of its cone the target covers at `range`, scaled by the game's `damageAt` falloff.
26
+
27
+ ## Workflow
28
+
29
+ 1. **Write the feel as numbers** on the `WeaponReport` fields. Some examples:
30
+ - "Laser beam when aimed" is `spreadByShot[29] < 0.01` rad with `stance: { ads: true }`.
31
+ - "Controllable" is `burstClimb` under about 3° over the magazine and `resetSeconds < 0.5`.
32
+ - "Close-range only" is `timeToKill` at 5 m near `0` and above 2 s at 30 m.
33
+ - "Snappy sights" is `adsSeconds < 0.2`.
34
+ 2. **Put them in a test** next to the tuning, one `measureWeapon` call per weapon or per rolled part set.
35
+ 3. **Move one knob at a time** using the table below, and rerun after each change.
36
+ 4. **Fire it** in the game (`bun run drive <game> --record <name>`, per `jgengine-verify`). The tests prove the numbers; the recording proves the kick, shake and sound read right.
37
+
38
+ ## Symptom → knob
39
+
40
+ | Symptom | Knob (direction) |
41
+ | --- | --- |
42
+ | Muzzle climbs out of control over a magazine | `recoil.pattern` pitch ↓, `recoverRate` ↑, `recoverDelay` ↓ |
43
+ | Kick feels weak but aim stays true | `cameraShare` ↑: more view punch, same aim |
44
+ | Spray is unlearnable | `randomCone` ↓, keep the drift in `pattern` |
45
+ | Accurate forever while held | `spread.perShot` ↑, `max` ↑ |
46
+ | Bloom never settles between bursts | `spread.recoverRate` ↑, `spread.recoverDelay` ↓ |
47
+ | Hip fire too good versus aiming | `spread.ads` ↓, `recoil.adsScale` ↓ |
48
+ | Sights feel sluggish | `adsTime` ↓ |
49
+ | Shotgun kills at sniper range | `spread.base` ↑ or a steeper `damageAt` falloff |
50
+ | Every shot shakes the same | route `kick` through a `curve` so the shake scales with the kick |
@@ -34,6 +34,8 @@ interceptors). See the [portable damage/effects recipe](recipes/portable-damage-
34
34
 
35
35
  ## Effects and projectiles
36
36
 
37
+ Cold projectile restore requires the receiving owner's pellet limit and radius-aware sweep to support the captured launch. Incompatible or inconsistent pellet, cone-sample and flight counts reject atomically; never truncate saved flights or discard pending settlements.
38
+
37
39
  Effect ids are **game-defined strings**. Magnitudes **drain** stats: positive subtracts down `receive.<effect>.order` (spilling to the next stat in the order), negative restores. Heals pass a negative amount (`via: { amount: -flashHeal }`, typically read from a `weapon.heal` stat).
38
40
 
39
41
  ```ts
@@ -61,15 +63,35 @@ settleProjectile(shotId) // authoritative → { at, hits
61
63
 
62
64
  Raycasts are **object-aware**: the default raycast checks placed scene objects as well as entities, discriminated by `RaycastHit.kind` (`"entity" | "object"` — an `ObjectRaycastHit` also carries `catalogId`). A crate or wall between shooter and target blocks or absorbs the shot instead of every projectile passing through scenery; supply `ProjectileSystemDeps.objects` (`{ list(), halfExtents?(catalogId) }`, matching `ObjectStore.list()` structurally) to opt in, with a `[0.5, 0.5, 0.5]` half-extent default per object.
63
65
 
66
+ `defineGame({ projectileTravel })` accepts `GameProjectileTravelOptions` from
67
+ `@jgengine/core/game/defineGame`: the existing `maxActive`, `maxRetained` and
68
+ `maxTargets` budgets plus `sweep(ctx, from, to, step)`,
69
+ `acceleration(ctx, position, velocity, time, input)` and `targets(ctx)`.
70
+ Each callback receives the live context of that world, including when one
71
+ definition hosts multiple worlds; the authoritative clock remains engine-owned.
72
+ Supply a radius-aware `sweep` for finite-radius travel through authored cover.
73
+ The default cover query supports radius zero and rejects larger radii.
74
+ `targets` selects bounded entity IDs for the default moving-target query;
75
+ omission selects that world's entities, and exceeding `maxTargets` (default
76
+ 2048) throws instead of dropping blockers. A custom sweep owns its complete
77
+ collision policy, including cover and receivers.
78
+ Supplied `acceleration` fully replaces the authored sampler; call
79
+ `ctx.environment.accelerationAt(position, time, windResponse, maxAcceleration,
80
+ forceMask)` explicitly when composing authored forces. Omitting it keeps the
81
+ authored sampler. Providers are unsaved world policy; projectile snapshot and
82
+ restore retain launch and motion state and use the receiving world's providers.
83
+
64
84
  ## Death
65
85
 
66
86
  Resolved **once** by the engine when the last stat in the receive order hits min. No HP polling in `onTick`, ever.
67
87
 
68
88
  - `entity.died` is emitted (before despawn — handlers can still read the victim's stats), then reason-matching `onDeath` entries run.
69
- - `DeathReason = { kind: "player_kill", killerUserId, via? } | { kind: "environment", source } | { kind: "self", source }`. Kills by the local player attribute automatically.
70
- - `onDeath.drops` tables are rolled and **granted to the killer** on player kills (emits `loot.granted`) when `onDeath.dropMode` is `"grant"` (default); `onDeath.command` runs through `ctx.game.commands`.
89
+ - `DeathReason = { kind: "player_kill", killerUserId, via? } | { kind: "environment", source } | { kind: "self", source }`. Local players, hosted members and uniquely owned pawns attribute automatically. `ctx.player.possession.ownerOf(entityId)` resolves recorded ownership without inventing an owner; shared ownership is ambiguous and returns `undefined`.
90
+ - `onDeath.drops` tables are rolled and **granted to the killer** on player kills (emits `loot.granted`) when `onDeath.dropMode` is `"grant"` (default); `onDeath.command` runs through `ctx.game.commands.runAs` with the resolved killer. XP commands, quest kill events and loot therefore use the same actor identity on a remote host.
71
91
  - `onDeath.dropMode: "world"` routes item drops through a scatter impulse into ground `worldItem`s instead of straight to inventory (currency drops still grant directly) — tune the impulse with `onDeath.scatter: { radius, minRadius?, height? }` (defaults from `game/worldItem`'s `DEFAULT_SCATTER`).
92
+ - Non-player kills suppress runtime loot by default. To opt an item table into environmental or unowned deaths, author `onDeath: { dropMode: "world", drops: [{ table: "salvage", when: { reason: "any" } }] }`. Only explicit `"any"` tables spawn in this case; neighboring ordinary tables stay suppressed. Bag grants and currency still require a resolved player killer, including when the table uses `"any"`.
72
93
  - Respawning under the same instance id revives it (it can die again). Same-id respawn must not happen synchronously inside the `entity.died` handler — defer a tick.
94
+ - `ctx.hydrate` and `ctx.restore` also revive restored entities whose terminal receive pools are above their minima. Snapshots captured inside `entity.died` still contain the victim before despawn; their depleted terminal pools retain the duplicate-death guard.
73
95
  - `quest.bind("entity.died")` credits kill objectives from the same event; leaderboards and kill feeds hang off it too.
74
96
 
75
97
  ## Damage interception and threshold transitions
@@ -11,10 +11,47 @@ This skill owns non-combat rules and state: systems, catalogs, inventory/items,
11
11
 
12
12
  Use [capabilities.md](capabilities.md) for intent discovery, [api.md](api.md) for signatures, and [reference-systems.md](reference-systems.md) for scheduling details before hand-rolling a subsystem.
13
13
 
14
+ For multi-cell inventory items, use `createShapedGrid`, `canPlace`, and
15
+ `findShapedPlacement` (`inventory/shapedGrid`). The caller owns item footprints,
16
+ rotation policy, saves, and UI; [shaped inventory placement](recipes/shaped-inventory.md)
17
+ shows bounded first-fit search. A stack-slot `InventoryGrid` does not replace
18
+ footprint rotation or spatial packing.
19
+
14
20
  Existing projects can keep player state and saves behind `LevelingStatAccess`.
15
21
  Follow the [portable XP/leveling recipe](recipes/portable-xp-leveling.md) for
16
22
  custom stat ids, immutable store writes, multiple level events, and JSON resume.
17
23
 
24
+ Persist rolled items with `createItemInstanceRegistry` (`item/itemInstanceRegistry`):
25
+ `state()` detaches definitions, runtime ids, prefix, and allocation sequence;
26
+ `restore(state)` replaces the registry and rejects a different prefix. Keep
27
+ definitions structured-cloneable and JSON-compatible for JSON saves. Save the
28
+ inventory's returned runtime ids alongside this state; do not regenerate rolls
29
+ or infer the next id from the surviving items.
30
+ For large collections, inject caller-owned `ItemInstanceStorage` and use
31
+ `statePages(pageSize)` / `restorePages(pages)` to clone one batch at a time.
32
+ Follow the [paged item storage recipe](recipes/paged-item-storage.md) for
33
+ atomic replacement, allocator reattachment, and export consistency.
34
+
35
+ Generate coherent combinations with `generate` (`item/itemgen`): dependent
36
+ pools and `GenStep.accept` constrain later choices using earlier picks.
37
+ Pins consume no choice RNG and must satisfy the same pool and constraints;
38
+ incompatible pins backtrack earlier unpinned choices or return `unsatisfiable`
39
+ within the configured attempt and pick budgets.
40
+
41
+ Validate generated quest catalogs with `validateQuestCatalog` (`game/questCatalog`).
42
+ Follow the [quest authoring recipe](recipes/quest-authoring.md) for bounded batches,
43
+ canonical facts, external unlock declarations, and precise repair diagnostics.
44
+
45
+ For cross-catalog sources and skill gates, compose `validateContentReferences` and
46
+ `validateContentProgression` (`game/contentValidation`) with `validateRecipeCatalog`
47
+ (`crafting/recipeCatalog`). Follow [content validation](reference-content-validation.md).
48
+ Games declare complete sources before treating blocked routes as errors; costs,
49
+ consumption, exclusive choices, story and balance still need game-owned playtests.
50
+
51
+ For generated chapter dialogue, use the [narrative authoring recipe](recipes/narrative-authoring.md)
52
+ and `npx jgengine recipe narrative-authoring`: freeze reviewed canon, validate
53
+ bounded batches, repair located issues and require explicit semantic approval.
54
+
18
55
  ## Canonical workflow
19
56
 
20
57
  1. Define plain serializable state and stable ids.
@@ -26,6 +63,16 @@ custom stat ids, immutable store writes, multiple level events, and JSON resume.
26
63
 
27
64
  Prefer `defineSystem` and `defineGame({ systems })` for scheduled capabilities. Keep boot/join in the game loop only when it is truly lifecycle glue. System ordering, frequency, and serialization are explicit; avoid a giant per-frame callback.
28
65
 
66
+ For staffed production, use `stationOutputRate` (`work/staffedStation`) to resolve
67
+ caller-owned worker stats and efficiency, then feed the rate to `tickProduction`.
68
+ Keep staffing and production progress in the same serializable store; the
69
+ [staffed production example](reference-systems.md#staffed-production) composes
70
+ the existing scheduler and save/reset boundaries without a separate manager.
71
+
72
+ For whole-world saves, follow [authoritative state](reference-systems.md#authoritative-state); persistence and client replication use separate context methods.
73
+ Follow [truthful device saves](recipes/device-save-status.md) for denied storage,
74
+ quota errors, checkpoint retry, and save status UI.
75
+
29
76
  ## Design rules
30
77
 
31
78
  - Caller data owns nouns, formulas, tables, costs, tiers, and content.
@@ -34,6 +81,15 @@ Prefer `defineSystem` and `defineGame({ systems })` for scheduled capabilities.
34
81
  - Random selection accepts injected RNG and stable ordering.
35
82
  - Large collections use indexes, queues, or bounded reconciliation rather than repeated scans.
36
83
 
84
+ Quest journals index active objectives by kill target and collected item. Bind
85
+ `entity.died` and `inventory.added` once and keep the returned unsubscribe handles;
86
+ credit follows catalog order, and quests activated by a credit callback join the
87
+ next event. Register replacement definitions after editing objective targets,
88
+ items, or party-sharing policy. Save hydration rebuilds the active indexes.
89
+ Shared kills query nearby party members for matching objectives active on any
90
+ user, so the killer need not have that quest; direct kills and collection only
91
+ visit the actor's matching active objectives.
92
+
37
93
  ## Part-composed character motion
38
94
 
39
95
  A character kit-bashed from primitives/`ModelPart`s (no skeleton, no clips) animates procedurally: tag parts with `role` (`leg.l`, `arm.r`, `head`, `tail`, `wing.l`…) and the shell's part-motion rig drives counter-phase limb swing from the entity's live movement speed, idle breathe/sway, hit flinch on `combat.hitReaction`, and a death topple on `entity.died` — no game-side frame loops. Tune with `ModelConfig.partMotion`; curves are pure and deterministic (`@jgengine/core/game/partAnimation`), so crowds de-sync by instance-id phase and replays stay stable. Soft characters (blobs, slimes) opt into volume-conserving squash-and-stretch with `partMotion: { squashAmp }` — footfall squash, jelly idle breathe, flinch pulse — and `deathStyle: "splat"` flattens out on death instead of the default sideways topple. Rigged GLBs keep using `ModelConfig.animation` — roles are only for rig-less compositions.
@@ -55,3 +111,10 @@ A character kit-bashed from primitives/`ModelPart`s (no skeleton, no clips) anim
55
111
  - Reset a profile with `initialPlayerState(runtime, userId)` from the same `onNewPlayer` hook used at join; insolvency never deletes a world.
56
112
  - `ctx.game.chat.send(userId, text)` targets global chat; `recent({ limit })` returns at most 100 messages. The default is 240 characters and one message per author per channel every two seconds. Rejected messages need visible UI feedback.
57
113
  - Hosts reuse `validateChatMessage` and `decideRateWindow`, storing the rate window transactionally with each accepted operation. Preserve chat rate windows when saving/restoring chat state.
114
+
115
+ For timed away missions, compose the pure transitions in `work/expedition` with
116
+ your existing `defineStore` and clock. The [expedition recipe](recipes/expeditions.md)
117
+ keeps bounded offline catch-up, finite supplies, return/death, capacity and RNG
118
+ continuity in one plain saveable state. Authored encounters, hazard/supply policy,
119
+ loot tables, quality ramps and text remain caller-owned; use `timeScaledRarity`
120
+ (`game/lootModifiers`) for elapsed-time odds with pipeline provenance.