@jgengine/core 0.9.0 → 0.11.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 (631) hide show
  1. package/CHANGELOG.md +34 -3
  2. package/LICENSE +203 -661
  3. package/README.md +1 -1
  4. package/dist/ai/crowd.d.ts +55 -0
  5. package/dist/ai/crowd.js +114 -0
  6. package/dist/ai/flock.d.ts +5 -2
  7. package/dist/ai/flock.js +5 -2
  8. package/dist/ai/groupAssist.d.ts +1 -0
  9. package/dist/ai/groupAssist.js +1 -0
  10. package/dist/ai/heatSystem.d.ts +76 -0
  11. package/dist/ai/heatSystem.js +97 -0
  12. package/dist/ai/interestScheduler.d.ts +107 -0
  13. package/dist/ai/interestScheduler.js +118 -0
  14. package/dist/ai/jobBoard.d.ts +1 -0
  15. package/dist/ai/jobBoard.js +1 -0
  16. package/dist/ai/laneSelect.d.ts +5 -2
  17. package/dist/ai/laneSelect.js +5 -2
  18. package/dist/ai/mobBrain.d.ts +1 -0
  19. package/dist/ai/mobBrain.js +1 -0
  20. package/dist/ai/spawnDirector.d.ts +19 -1
  21. package/dist/ai/spawnDirector.js +53 -26
  22. package/dist/ai/spawnPoint.d.ts +7 -0
  23. package/dist/ai/spawnPoint.js +7 -0
  24. package/dist/ai/targetAcquisition.d.ts +84 -0
  25. package/dist/ai/targetAcquisition.js +92 -0
  26. package/dist/anim/easing.d.ts +19 -0
  27. package/dist/anim/easing.js +19 -0
  28. package/dist/anim/oscillator.d.ts +12 -0
  29. package/dist/anim/oscillator.js +25 -0
  30. package/dist/area/areaEffectField.d.ts +127 -0
  31. package/dist/area/areaEffectField.js +175 -0
  32. package/dist/area/stackPolicy.d.ts +47 -0
  33. package/dist/area/stackPolicy.js +99 -0
  34. package/dist/audio/audioFalloff.d.ts +5 -2
  35. package/dist/audio/music.d.ts +53 -0
  36. package/dist/audio/music.js +34 -0
  37. package/dist/audio/synth.d.ts +60 -0
  38. package/dist/audio/synth.js +8 -0
  39. package/dist/authoring.d.ts +15 -0
  40. package/dist/authoring.js +15 -0
  41. package/dist/board/laneBoard.d.ts +13 -0
  42. package/dist/board/laneBoard.js +13 -0
  43. package/dist/board/timelineBoard.d.ts +10 -0
  44. package/dist/board/timelineBoard.js +10 -0
  45. package/dist/combat/abilityKit.d.ts +18 -0
  46. package/dist/combat/abilityKit.js +24 -9
  47. package/dist/combat/animationState.d.ts +5 -0
  48. package/dist/combat/animationState.js +5 -0
  49. package/dist/combat/breakMeters.d.ts +10 -0
  50. package/dist/combat/breakMeters.js +10 -0
  51. package/dist/combat/castRunner.d.ts +5 -0
  52. package/dist/combat/castRunner.js +5 -0
  53. package/dist/combat/comboPoints.d.ts +5 -0
  54. package/dist/combat/comboPoints.js +5 -0
  55. package/dist/combat/comboString.d.ts +5 -0
  56. package/dist/combat/comboString.js +5 -0
  57. package/dist/combat/damageInterceptors.d.ts +192 -0
  58. package/dist/combat/damageInterceptors.js +251 -0
  59. package/dist/combat/damageMatchup.d.ts +68 -0
  60. package/dist/combat/damageMatchup.js +42 -0
  61. package/dist/combat/damageResolution.d.ts +63 -0
  62. package/dist/combat/damageResolution.js +50 -0
  63. package/dist/combat/death.d.ts +7 -1
  64. package/dist/combat/death.js +5 -0
  65. package/dist/combat/deathReason.d.ts +14 -0
  66. package/dist/combat/deathReason.js +1 -0
  67. package/dist/combat/defensiveWindow.d.ts +5 -0
  68. package/dist/combat/defensiveWindow.js +5 -0
  69. package/dist/combat/dotField.d.ts +47 -0
  70. package/dist/combat/dotField.js +52 -0
  71. package/dist/combat/downed.d.ts +5 -0
  72. package/dist/combat/downed.js +5 -0
  73. package/dist/combat/effects.d.ts +5 -0
  74. package/dist/combat/effects.js +5 -0
  75. package/dist/combat/encounterSequence.d.ts +258 -0
  76. package/dist/combat/encounterSequence.js +426 -0
  77. package/dist/combat/hitReaction.d.ts +57 -1
  78. package/dist/combat/hitReaction.js +45 -5
  79. package/dist/combat/magazine.d.ts +54 -0
  80. package/dist/combat/magazine.js +96 -0
  81. package/dist/combat/projectiles.d.ts +7 -2
  82. package/dist/combat/projectiles.js +48 -17
  83. package/dist/combat/receivedDamage.d.ts +117 -0
  84. package/dist/combat/receivedDamage.js +92 -0
  85. package/dist/combat/regenShield.d.ts +39 -0
  86. package/dist/combat/regenShield.js +49 -0
  87. package/dist/combat/renderCues.d.ts +51 -0
  88. package/dist/combat/renderCues.js +44 -0
  89. package/dist/combat/resourcePool.d.ts +5 -0
  90. package/dist/combat/resourcePool.js +5 -0
  91. package/dist/combat/rewardAllocation.d.ts +121 -0
  92. package/dist/combat/rewardAllocation.js +251 -0
  93. package/dist/combat/shotOrigin.d.ts +30 -1
  94. package/dist/combat/shotOrigin.js +70 -2
  95. package/dist/combat/statusApplication.d.ts +96 -0
  96. package/dist/combat/statusApplication.js +81 -0
  97. package/dist/combat/telegraph.d.ts +11 -2
  98. package/dist/combat/telegraph.js +11 -2
  99. package/dist/combat/thresholdCrossings.d.ts +85 -0
  100. package/dist/combat/thresholdCrossings.js +116 -0
  101. package/dist/combat.d.ts +34 -0
  102. package/dist/combat.js +34 -0
  103. package/dist/commands/commandRegistry.d.ts +11 -0
  104. package/dist/commands/commandRegistry.js +9 -2
  105. package/dist/crafting/crop.d.ts +18 -0
  106. package/dist/crafting/crop.js +18 -0
  107. package/dist/crafting/production.d.ts +5 -0
  108. package/dist/crafting/production.js +5 -0
  109. package/dist/devtools/rewriteTunables.d.ts +17 -0
  110. package/dist/devtools/rewriteTunables.js +276 -0
  111. package/dist/devtools/saveEndpoint.d.ts +46 -0
  112. package/dist/devtools/saveEndpoint.js +48 -0
  113. package/dist/devtools/transformTunables.d.ts +3 -0
  114. package/dist/devtools/transformTunables.js +3 -0
  115. package/dist/devtools/tunableSchema.d.ts +31 -0
  116. package/dist/devtools/tunableSchema.js +31 -0
  117. package/dist/economy/currency.d.ts +12 -1
  118. package/dist/economy/currency.js +12 -1
  119. package/dist/economy/listingBook.d.ts +91 -0
  120. package/dist/economy/listingBook.js +144 -0
  121. package/dist/economy/resourceLedger.d.ts +222 -0
  122. package/dist/economy/resourceLedger.js +353 -0
  123. package/dist/economy/sharedWallet.d.ts +14 -0
  124. package/dist/economy/sharedWallet.js +14 -0
  125. package/dist/economy/techTree.d.ts +12 -0
  126. package/dist/economy/techTree.js +12 -0
  127. package/dist/economy/wallet.d.ts +26 -2
  128. package/dist/economy/wallet.js +35 -6
  129. package/dist/editor/commands.d.ts +248 -0
  130. package/dist/editor/commands.js +865 -0
  131. package/dist/editor/document.d.ts +186 -0
  132. package/dist/editor/document.js +884 -0
  133. package/dist/editor/grid.d.ts +229 -0
  134. package/dist/editor/grid.js +366 -0
  135. package/dist/editor/gridAdapters.d.ts +80 -0
  136. package/dist/editor/gridAdapters.js +134 -0
  137. package/dist/editor/index.d.ts +97 -0
  138. package/dist/editor/index.js +10 -0
  139. package/dist/editor/liveSync.d.ts +127 -0
  140. package/dist/editor/liveSync.js +249 -0
  141. package/dist/editor/runtimeInspector.d.ts +88 -0
  142. package/dist/editor/runtimeInspector.js +239 -0
  143. package/dist/editor/types.d.ts +175 -0
  144. package/dist/editor/types.js +29 -0
  145. package/dist/faction/reputation.d.ts +5 -0
  146. package/dist/faction/reputation.js +5 -0
  147. package/dist/format/distance.d.ts +17 -0
  148. package/dist/format/distance.js +18 -0
  149. package/dist/format/duration.d.ts +27 -0
  150. package/dist/format/duration.js +45 -0
  151. package/dist/format/speed.d.ts +17 -0
  152. package/dist/format/speed.js +25 -0
  153. package/dist/game/connectedPlayers.d.ts +30 -0
  154. package/dist/game/connectedPlayers.js +21 -0
  155. package/dist/game/cosmetics.d.ts +9 -0
  156. package/dist/game/cosmetics.js +16 -0
  157. package/dist/game/defineGame.d.ts +141 -2
  158. package/dist/game/defineGame.js +39 -1
  159. package/dist/game/defineSystem.d.ts +88 -0
  160. package/dist/game/defineSystem.js +45 -0
  161. package/dist/game/dialogue.d.ts +33 -0
  162. package/dist/game/dialogue.js +21 -0
  163. package/dist/game/events.d.ts +50 -13
  164. package/dist/game/events.js +5 -0
  165. package/dist/game/feed.d.ts +33 -0
  166. package/dist/game/feed.js +35 -2
  167. package/dist/game/gamePhase.d.ts +11 -0
  168. package/dist/game/gamePhase.js +16 -0
  169. package/dist/game/keyValueStore.d.ts +25 -0
  170. package/dist/game/keyValueStore.js +61 -0
  171. package/dist/game/leaderboard.d.ts +5 -0
  172. package/dist/game/leaderboard.js +5 -0
  173. package/dist/game/levelSequence.d.ts +24 -2
  174. package/dist/game/levelSequence.js +51 -2
  175. package/dist/game/loadout.d.ts +5 -0
  176. package/dist/game/loadout.js +5 -0
  177. package/dist/game/lootFilter.d.ts +2 -0
  178. package/dist/game/lootFilter.js +2 -0
  179. package/dist/game/lootPipeline.d.ts +164 -0
  180. package/dist/game/lootPipeline.js +216 -0
  181. package/dist/game/lootTable.d.ts +21 -0
  182. package/dist/game/lootTable.js +15 -4
  183. package/dist/game/modelAnimation.d.ts +8 -0
  184. package/dist/game/modelAnimation.js +18 -0
  185. package/dist/game/objectives.d.ts +43 -0
  186. package/dist/game/objectives.js +36 -0
  187. package/dist/game/paramLayers.d.ts +148 -0
  188. package/dist/game/paramLayers.js +185 -0
  189. package/dist/game/ping.d.ts +5 -0
  190. package/dist/game/ping.js +5 -0
  191. package/dist/game/playableGame.d.ts +160 -12
  192. package/dist/game/quest.d.ts +8 -0
  193. package/dist/game/quest.js +29 -0
  194. package/dist/game/race.d.ts +141 -0
  195. package/dist/game/race.js +169 -0
  196. package/dist/game/recordBook.d.ts +5 -5
  197. package/dist/game/recordBook.js +4 -1
  198. package/dist/game/ruleSelection.d.ts +75 -0
  199. package/dist/game/ruleSelection.js +127 -0
  200. package/dist/game/runDraft.d.ts +5 -0
  201. package/dist/game/runDraft.js +5 -0
  202. package/dist/game/saveStore.d.ts +111 -0
  203. package/dist/game/saveStore.js +312 -0
  204. package/dist/game/snapshotHistory.d.ts +67 -0
  205. package/dist/game/snapshotHistory.js +95 -0
  206. package/dist/game/social.d.ts +38 -1
  207. package/dist/game/social.js +71 -0
  208. package/dist/game/spawnPoints.d.ts +5 -0
  209. package/dist/game/spawnPoints.js +5 -0
  210. package/dist/game/systemRuntime.d.ts +50 -0
  211. package/dist/game/systemRuntime.js +157 -0
  212. package/dist/game/systemSchedule.d.ts +72 -0
  213. package/dist/game/systemSchedule.js +242 -0
  214. package/dist/game/toasts.d.ts +43 -0
  215. package/dist/game/toasts.js +44 -0
  216. package/dist/game/trade.d.ts +10 -0
  217. package/dist/game/trade.js +10 -0
  218. package/dist/game/unlocks.d.ts +8 -0
  219. package/dist/game/unlocks.js +16 -0
  220. package/dist/game/vfxInstance.d.ts +138 -0
  221. package/dist/game/vfxInstance.js +96 -0
  222. package/dist/game/worldItem.d.ts +17 -4
  223. package/dist/game/worldItem.js +17 -4
  224. package/dist/gameplay.d.ts +91 -0
  225. package/dist/gameplay.js +91 -0
  226. package/dist/index.d.ts +5 -7
  227. package/dist/index.js +5 -7
  228. package/dist/input/actionBindings.d.ts +24 -5
  229. package/dist/input/actionBindings.js +23 -5
  230. package/dist/input/axisInput.d.ts +13 -1
  231. package/dist/input/axisInput.js +22 -1
  232. package/dist/input/controlGroups.d.ts +67 -0
  233. package/dist/input/controlGroups.js +41 -0
  234. package/dist/input/touchScheme.d.ts +82 -6
  235. package/dist/input/touchScheme.js +123 -10
  236. package/dist/interaction/contextMenu.d.ts +1 -0
  237. package/dist/interaction/contextMenu.js +1 -0
  238. package/dist/interaction/lockpick.d.ts +61 -0
  239. package/dist/interaction/lockpick.js +168 -0
  240. package/dist/interaction/proximityPrompt.d.ts +5 -1
  241. package/dist/interaction/proximityPrompt.js +5 -3
  242. package/dist/interaction/qte.d.ts +5 -0
  243. package/dist/interaction/qte.js +5 -0
  244. package/dist/interaction/skillCheck.d.ts +5 -0
  245. package/dist/interaction/skillCheck.js +5 -0
  246. package/dist/inventory/inventoryModel.d.ts +12 -0
  247. package/dist/inventory/inventoryModel.js +12 -0
  248. package/dist/inventory/shapedGrid.d.ts +16 -0
  249. package/dist/inventory/shapedGrid.js +16 -0
  250. package/dist/inventory/slotModel.d.ts +10 -0
  251. package/dist/inventory/slotModel.js +10 -0
  252. package/dist/item/durability.d.ts +5 -0
  253. package/dist/item/durability.js +5 -0
  254. package/dist/item/itemIdentity.d.ts +171 -0
  255. package/dist/item/itemIdentity.js +138 -0
  256. package/dist/item/itemInstanceRegistry.d.ts +32 -0
  257. package/dist/item/itemInstanceRegistry.js +34 -0
  258. package/dist/item/itemgen.d.ts +163 -0
  259. package/dist/item/itemgen.js +151 -0
  260. package/dist/item/modularItem.d.ts +5 -0
  261. package/dist/item/modularItem.js +5 -0
  262. package/dist/item/use.d.ts +5 -0
  263. package/dist/item/use.js +5 -0
  264. package/dist/item/useBehavior.d.ts +142 -0
  265. package/dist/item/useBehavior.js +105 -0
  266. package/dist/item/weapon.d.ts +5 -0
  267. package/dist/item/weapon.js +5 -0
  268. package/dist/math/scalar.d.ts +25 -0
  269. package/dist/math/scalar.js +47 -0
  270. package/dist/meta/changelog.d.ts +4 -1
  271. package/dist/meta/changelog.js +36 -1
  272. package/dist/movement/avoidance.d.ts +45 -0
  273. package/dist/movement/avoidance.js +123 -0
  274. package/dist/movement/cameraRig.d.ts +8 -0
  275. package/dist/movement/cameraRig.js +8 -0
  276. package/dist/movement/dash.d.ts +11 -0
  277. package/dist/movement/dash.js +11 -0
  278. package/dist/movement/formation.d.ts +135 -0
  279. package/dist/movement/formation.js +194 -0
  280. package/dist/movement/glideModel.d.ts +5 -0
  281. package/dist/movement/glideModel.js +5 -0
  282. package/dist/movement/grappleSwing.d.ts +5 -0
  283. package/dist/movement/grappleSwing.js +5 -0
  284. package/dist/movement/leaderTrail.d.ts +5 -0
  285. package/dist/movement/leaderTrail.js +5 -0
  286. package/dist/movement/movementModel.d.ts +38 -7
  287. package/dist/movement/movementModel.js +54 -13
  288. package/dist/movement/playerMovement.d.ts +41 -0
  289. package/dist/movement/playerMovement.js +236 -0
  290. package/dist/movement/poseState.d.ts +13 -0
  291. package/dist/movement/poseState.js +16 -0
  292. package/dist/movement/steering.d.ts +8 -0
  293. package/dist/movement/steering.js +18 -0
  294. package/dist/multiplayer/combatSnapshot.d.ts +3 -0
  295. package/dist/multiplayer/combatSnapshot.js +3 -0
  296. package/dist/multiplayer/lagCompensation.d.ts +5 -0
  297. package/dist/multiplayer/lagCompensation.js +5 -0
  298. package/dist/multiplayer/presenceModel.d.ts +9 -1
  299. package/dist/multiplayer/presenceModel.js +9 -1
  300. package/dist/multiplayer/simultaneousCommit.d.ts +3 -0
  301. package/dist/multiplayer/simultaneousCommit.js +3 -0
  302. package/dist/multiplayer.d.ts +11 -0
  303. package/dist/multiplayer.js +11 -0
  304. package/dist/nav/pathFollow.d.ts +57 -0
  305. package/dist/nav/pathFollow.js +109 -0
  306. package/dist/orders/orderKinds.d.ts +156 -0
  307. package/dist/orders/orderKinds.js +197 -0
  308. package/dist/orders/orderQueue.d.ts +208 -0
  309. package/dist/orders/orderQueue.js +211 -0
  310. package/dist/physics/drivableVehicle.d.ts +29 -0
  311. package/dist/physics/drivableVehicle.js +16 -0
  312. package/dist/physics/kinematicVehicle.d.ts +21 -1
  313. package/dist/physics/kinematicVehicle.js +23 -8
  314. package/dist/physics/physicsWorld.d.ts +24 -12
  315. package/dist/physics/physicsWorld.js +11 -5
  316. package/dist/physics/radialImpulse.d.ts +25 -0
  317. package/dist/physics/radialImpulse.js +27 -0
  318. package/dist/procedural.d.ts +3 -0
  319. package/dist/procedural.js +3 -0
  320. package/dist/progression/statGraph.d.ts +116 -0
  321. package/dist/progression/statGraph.js +318 -0
  322. package/dist/puzzle/cellGrid.d.ts +9 -0
  323. package/dist/puzzle/cellGrid.js +9 -0
  324. package/dist/puzzle/fallingPiece.d.ts +9 -0
  325. package/dist/puzzle/fallingPiece.js +9 -0
  326. package/dist/puzzle/nonogram.d.ts +39 -0
  327. package/dist/puzzle/nonogram.js +136 -0
  328. package/dist/random/nameGen.d.ts +5 -0
  329. package/dist/random/nameGen.js +5 -0
  330. package/dist/random/pick.d.ts +8 -0
  331. package/dist/random/pick.js +31 -0
  332. package/dist/random/rng.d.ts +24 -0
  333. package/dist/random/rng.js +30 -8
  334. package/dist/random/seedLink.d.ts +4 -0
  335. package/dist/random/seedLink.js +4 -0
  336. package/dist/relation/keyedValues.d.ts +54 -0
  337. package/dist/relation/keyedValues.js +115 -0
  338. package/dist/relation/thresholds.d.ts +62 -0
  339. package/dist/relation/thresholds.js +62 -0
  340. package/dist/render/color.d.ts +12 -0
  341. package/dist/render/color.js +26 -0
  342. package/dist/render/lookPreset.d.ts +41 -0
  343. package/dist/render/lookPreset.js +29 -0
  344. package/dist/render/postProcessing.d.ts +76 -0
  345. package/dist/render/postProcessing.js +21 -0
  346. package/dist/rules/predicate.d.ts +53 -0
  347. package/dist/rules/predicate.js +64 -0
  348. package/dist/rules/ruleEffects.d.ts +38 -0
  349. package/dist/rules/ruleEffects.js +37 -0
  350. package/dist/rules/triggeredRules.d.ts +160 -0
  351. package/dist/rules/triggeredRules.js +241 -0
  352. package/dist/runtime/adapter.d.ts +58 -0
  353. package/dist/runtime/adapter.js +54 -5
  354. package/dist/runtime/cameraDirector.d.ts +1 -0
  355. package/dist/runtime/cameraDirector.js +1 -0
  356. package/dist/runtime/commandRunner.d.ts +1 -0
  357. package/dist/runtime/commandRunner.js +1 -0
  358. package/dist/runtime/context/combatFx.d.ts +23 -0
  359. package/dist/runtime/context/combatFx.js +161 -0
  360. package/dist/runtime/context/registries.d.ts +13 -0
  361. package/dist/runtime/context/registries.js +84 -0
  362. package/dist/runtime/context/worldItems.d.ts +21 -0
  363. package/dist/runtime/context/worldItems.js +37 -0
  364. package/dist/runtime/descriptors/baseline.d.ts +48 -0
  365. package/dist/runtime/descriptors/baseline.js +134 -0
  366. package/dist/runtime/descriptors/features.d.ts +74 -0
  367. package/dist/runtime/descriptors/features.js +281 -0
  368. package/dist/runtime/gameContext.d.ts +163 -19
  369. package/dist/runtime/gameContext.js +385 -339
  370. package/dist/runtime/gameRuntime.d.ts +1 -0
  371. package/dist/runtime/gameRuntime.js +1 -0
  372. package/dist/runtime/headlessRunner.d.ts +60 -0
  373. package/dist/runtime/headlessRunner.js +44 -0
  374. package/dist/runtime/hostPersistence.d.ts +12 -0
  375. package/dist/runtime/hostPersistence.js +12 -0
  376. package/dist/runtime/hostedGameRunner.d.ts +54 -0
  377. package/dist/runtime/hostedGameRunner.js +85 -0
  378. package/dist/runtime/hostedWorldSession.d.ts +73 -0
  379. package/dist/runtime/hostedWorldSession.js +64 -0
  380. package/dist/runtime/inputSnapshot.d.ts +21 -3
  381. package/dist/runtime/inputSnapshot.js +8 -2
  382. package/dist/runtime/motionIntents.d.ts +12 -1
  383. package/dist/runtime/motionIntents.js +26 -1
  384. package/dist/runtime/perContext.d.ts +15 -0
  385. package/dist/runtime/perContext.js +21 -0
  386. package/dist/runtime/runtimeSave.d.ts +79 -0
  387. package/dist/runtime/runtimeSave.js +105 -0
  388. package/dist/runtime/save.d.ts +4 -0
  389. package/dist/runtime/save.js +4 -0
  390. package/dist/runtime/snapshot.d.ts +7 -0
  391. package/dist/runtime/snapshot.js +7 -0
  392. package/dist/runtime/visibility.d.ts +2 -1
  393. package/dist/runtime/visibility.js +2 -1
  394. package/dist/runtime/worldChannel.d.ts +62 -0
  395. package/dist/runtime/worldChannel.js +114 -0
  396. package/dist/runtime/worldMirror.d.ts +36 -0
  397. package/dist/runtime/worldMirror.js +44 -0
  398. package/dist/runtime/worldProjection.d.ts +47 -0
  399. package/dist/runtime/worldProjection.js +52 -0
  400. package/dist/runtime/worldReplication.d.ts +69 -0
  401. package/dist/runtime/worldReplication.js +217 -0
  402. package/dist/runtime/worldSnapshot.d.ts +44 -0
  403. package/dist/runtime/worldSnapshot.js +30 -0
  404. package/dist/scene/assetCatalog.d.ts +22 -0
  405. package/dist/scene/assetCatalog.js +4 -0
  406. package/dist/scene/assetDiagnostics.d.ts +69 -0
  407. package/dist/scene/assetDiagnostics.js +110 -0
  408. package/dist/scene/assetGenerator.d.ts +65 -0
  409. package/dist/scene/assetGenerator.js +64 -0
  410. package/dist/scene/assetSpace.d.ts +137 -0
  411. package/dist/scene/assetSpace.js +259 -0
  412. package/dist/scene/authoredTriggers.d.ts +169 -0
  413. package/dist/scene/authoredTriggers.js +340 -0
  414. package/dist/scene/behaviorRuntime.d.ts +75 -0
  415. package/dist/scene/behaviorRuntime.js +217 -0
  416. package/dist/scene/behaviors.d.ts +14 -2
  417. package/dist/scene/behaviors.js +7 -2
  418. package/dist/scene/bodyBind.d.ts +60 -0
  419. package/dist/scene/bodyBind.js +51 -0
  420. package/dist/scene/builtinSceneKinds.d.ts +5 -0
  421. package/dist/scene/builtinSceneKinds.js +62 -0
  422. package/dist/scene/colliders.d.ts +18 -0
  423. package/dist/scene/colliders.js +61 -1
  424. package/dist/scene/entityStats.d.ts +13 -0
  425. package/dist/scene/entityStats.js +30 -0
  426. package/dist/scene/entityStore.d.ts +41 -1
  427. package/dist/scene/entityStore.js +75 -0
  428. package/dist/scene/facing.d.ts +18 -0
  429. package/dist/scene/facing.js +26 -0
  430. package/dist/scene/modelSockets.d.ts +33 -0
  431. package/dist/scene/modelSockets.js +29 -0
  432. package/dist/scene/objectQuery.d.ts +2 -0
  433. package/dist/scene/objectQuery.js +2 -0
  434. package/dist/scene/objectStore.d.ts +4 -0
  435. package/dist/scene/objectStore.js +6 -0
  436. package/dist/scene/possession.d.ts +8 -0
  437. package/dist/scene/possession.js +14 -0
  438. package/dist/scene/roster.d.ts +3 -0
  439. package/dist/scene/roster.js +12 -0
  440. package/dist/scene/sceneKinds.d.ts +240 -0
  441. package/dist/scene/sceneKinds.js +214 -0
  442. package/dist/scene/sceneOwnership.d.ts +169 -0
  443. package/dist/scene/sceneOwnership.js +179 -0
  444. package/dist/scene/selectionBookmarks.d.ts +93 -0
  445. package/dist/scene/selectionBookmarks.js +122 -0
  446. package/dist/scene/vehicleSeat.d.ts +68 -0
  447. package/dist/scene/vehicleSeat.js +59 -0
  448. package/dist/sensor/concealment.d.ts +3 -0
  449. package/dist/sensor/concealment.js +3 -0
  450. package/dist/sensor/freezeMonitor.d.ts +1 -0
  451. package/dist/sensor/freezeMonitor.js +1 -0
  452. package/dist/sensor/frustumSensor.d.ts +7 -3
  453. package/dist/sensor/frustumSensor.js +7 -3
  454. package/dist/sensor/hiddenStateProbe.d.ts +5 -2
  455. package/dist/sensor/hiddenStateProbe.js +5 -2
  456. package/dist/sensor/recordingBuffer.d.ts +2 -1
  457. package/dist/sensor/recordingBuffer.js +2 -1
  458. package/dist/sensor/replayLoop.d.ts +6 -2
  459. package/dist/sensor/replayLoop.js +6 -2
  460. package/dist/sensor/revealQuery.d.ts +2 -1
  461. package/dist/sensor/revealQuery.js +2 -1
  462. package/dist/sensor/visionCone.d.ts +10 -3
  463. package/dist/sensor/visionCone.js +10 -3
  464. package/dist/session/contestedChannel.d.ts +1 -0
  465. package/dist/session/contestedChannel.js +1 -0
  466. package/dist/session/extraction.d.ts +1 -0
  467. package/dist/session/extraction.js +1 -0
  468. package/dist/session/roles.d.ts +6 -0
  469. package/dist/session/roles.js +6 -0
  470. package/dist/session/roundState.d.ts +8 -0
  471. package/dist/session/roundState.js +2 -0
  472. package/dist/settings/settingsModel.d.ts +1 -0
  473. package/dist/settings/settingsModel.js +1 -0
  474. package/dist/stats/accumulatorMeter.d.ts +6 -0
  475. package/dist/stats/accumulatorMeter.js +6 -0
  476. package/dist/stats/eventMeter.d.ts +5 -0
  477. package/dist/stats/eventMeter.js +5 -0
  478. package/dist/stats/rollCheck.d.ts +5 -0
  479. package/dist/stats/rollCheck.js +5 -0
  480. package/dist/stats/statModifiers.d.ts +5 -0
  481. package/dist/stats/statModifiers.js +5 -0
  482. package/dist/store/defineKeyedStore.d.ts +38 -0
  483. package/dist/store/defineKeyedStore.js +42 -0
  484. package/dist/store/defineStore.d.ts +33 -0
  485. package/dist/store/defineStore.js +35 -0
  486. package/dist/store/observableKeyedStore.d.ts +9 -0
  487. package/dist/store/observableKeyedStore.js +43 -2
  488. package/dist/survival/decayMeter.d.ts +55 -0
  489. package/dist/survival/decayMeter.js +130 -55
  490. package/dist/survival/moodle.d.ts +2 -0
  491. package/dist/survival/regionHealth.d.ts +6 -0
  492. package/dist/survival/regionHealth.js +5 -0
  493. package/dist/tactics/fallingGrid.d.ts +6 -4
  494. package/dist/tactics/fallingGrid.js +6 -4
  495. package/dist/tactics/predictiveQuery.d.ts +3 -0
  496. package/dist/tactics/predictiveQuery.js +3 -0
  497. package/dist/tactics/snapshot.d.ts +2 -0
  498. package/dist/tactics/snapshot.js +2 -0
  499. package/dist/tactics/surface.d.ts +1 -0
  500. package/dist/tactics/surface.js +1 -0
  501. package/dist/tactics/tacticalGrid.d.ts +1 -0
  502. package/dist/tactics/tacticalGrid.js +1 -0
  503. package/dist/time/beatClock.d.ts +14 -4
  504. package/dist/time/beatClock.js +14 -4
  505. package/dist/time/calendarClock.d.ts +3 -0
  506. package/dist/time/calendarClock.js +3 -0
  507. package/dist/time/idleProgress.d.ts +4 -0
  508. package/dist/time/idleProgress.js +4 -0
  509. package/dist/time/simClock.d.ts +2 -0
  510. package/dist/time/simClock.js +20 -5
  511. package/dist/ui/actionModel.d.ts +110 -0
  512. package/dist/ui/actionModel.js +159 -0
  513. package/dist/ui/gameLayout.d.ts +144 -0
  514. package/dist/ui/gameLayout.js +148 -0
  515. package/dist/ui/hudDocument.d.ts +114 -0
  516. package/dist/ui/hudDocument.js +220 -0
  517. package/dist/ui/hudLayout.d.ts +33 -1
  518. package/dist/ui/hudLayout.js +204 -17
  519. package/dist/ui/orientation.d.ts +39 -0
  520. package/dist/ui/orientation.js +49 -0
  521. package/dist/ui/selectionModel.d.ts +92 -0
  522. package/dist/ui/selectionModel.js +88 -0
  523. package/dist/ui/swingTimer.d.ts +42 -0
  524. package/dist/ui/swingTimer.js +38 -0
  525. package/dist/ui/tooltipModel.d.ts +63 -0
  526. package/dist/ui/tooltipModel.js +82 -0
  527. package/dist/ui.d.ts +15 -0
  528. package/dist/ui.js +15 -0
  529. package/dist/visibility/bounds.d.ts +8 -1
  530. package/dist/visibility/bounds.js +8 -1
  531. package/dist/visibility/frustum.d.ts +12 -3
  532. package/dist/visibility/frustum.js +12 -3
  533. package/dist/visibility/spatialIndex.js +34 -20
  534. package/dist/work/jobQueue.d.ts +201 -0
  535. package/dist/work/jobQueue.js +253 -0
  536. package/dist/work/unitTraining.d.ts +58 -0
  537. package/dist/work/unitTraining.js +52 -0
  538. package/dist/world/authoredObjects.d.ts +58 -0
  539. package/dist/world/authoredObjects.js +69 -0
  540. package/dist/world/buildingGenerator.d.ts +18 -0
  541. package/dist/world/buildingGenerator.js +62 -0
  542. package/dist/world/buildings.d.ts +5 -0
  543. package/dist/world/buildings.js +5 -0
  544. package/dist/world/catenary.d.ts +22 -0
  545. package/dist/world/catenary.js +69 -0
  546. package/dist/world/environmentSummary.d.ts +11 -2
  547. package/dist/world/environmentSummary.js +11 -1
  548. package/dist/world/features.d.ts +244 -7
  549. package/dist/world/features.js +92 -4
  550. package/dist/world/footprintGrid.d.ts +59 -0
  551. package/dist/world/footprintGrid.js +141 -0
  552. package/dist/world/geometry.d.ts +64 -0
  553. package/dist/world/geometry.js +94 -0
  554. package/dist/world/grassKind.d.ts +51 -0
  555. package/dist/world/grassKind.js +101 -0
  556. package/dist/world/pathInstances.d.ts +27 -0
  557. package/dist/world/pathInstances.js +27 -0
  558. package/dist/world/pathTerrain.d.ts +80 -0
  559. package/dist/world/pathTerrain.js +142 -0
  560. package/dist/world/placeAsset.d.ts +81 -0
  561. package/dist/world/placeAsset.js +77 -0
  562. package/dist/world/placement.d.ts +4 -0
  563. package/dist/world/placement.js +4 -0
  564. package/dist/world/placementController.d.ts +6 -0
  565. package/dist/world/placementController.js +6 -0
  566. package/dist/world/polyline.d.ts +34 -0
  567. package/dist/world/polyline.js +81 -0
  568. package/dist/world/roads.d.ts +55 -0
  569. package/dist/world/roads.js +184 -0
  570. package/dist/world/scatterRegion.d.ts +164 -0
  571. package/dist/world/scatterRegion.js +409 -0
  572. package/dist/world/sceneShapes.d.ts +43 -0
  573. package/dist/world/sceneShapes.js +1 -0
  574. package/dist/world/segment.d.ts +27 -0
  575. package/dist/world/segment.js +43 -0
  576. package/dist/world/soilKind.d.ts +48 -0
  577. package/dist/world/soilKind.js +92 -0
  578. package/dist/world/spatialSample.d.ts +241 -0
  579. package/dist/world/spatialSample.js +465 -0
  580. package/dist/world/streets.d.ts +80 -0
  581. package/dist/world/streets.js +144 -0
  582. package/dist/world/terraform.d.ts +213 -1
  583. package/dist/world/terraform.js +655 -20
  584. package/dist/world/terrain.d.ts +138 -12
  585. package/dist/world/terrain.js +346 -28
  586. package/dist/world/terrainGuides.d.ts +182 -0
  587. package/dist/world/terrainGuides.js +343 -0
  588. package/dist/world/vec2.d.ts +81 -0
  589. package/dist/world/vec2.js +128 -0
  590. package/dist/world/vec3.d.ts +49 -0
  591. package/dist/world/vec3.js +76 -0
  592. package/dist/world/vegetation.d.ts +71 -0
  593. package/dist/world/vegetation.js +151 -0
  594. package/dist/world/visibilityField.d.ts +154 -0
  595. package/dist/world/visibilityField.js +271 -0
  596. package/dist/world/volumetricClouds.d.ts +52 -0
  597. package/dist/world/volumetricClouds.js +43 -0
  598. package/dist/world/walls.d.ts +9 -0
  599. package/dist/world/walls.js +9 -0
  600. package/dist/world/waterKind.d.ts +55 -0
  601. package/dist/world/waterKind.js +99 -0
  602. package/dist/world.d.ts +132 -0
  603. package/dist/world.js +132 -0
  604. package/package.json +32 -8
  605. package/skills/jgengine/SKILL.md +51 -0
  606. package/skills/jgengine/api.md +434 -0
  607. package/skills/jgengine/capabilities.md +29 -0
  608. package/skills/jgengine/reference-cartridge.md +72 -0
  609. package/skills/jgengine-combat/SKILL.md +34 -0
  610. package/skills/jgengine-combat/api.md +549 -0
  611. package/skills/jgengine-combat/capabilities.md +169 -0
  612. package/skills/jgengine-combat/reference.md +98 -0
  613. package/skills/jgengine-gameplay/SKILL.md +39 -0
  614. package/skills/jgengine-gameplay/api.md +1794 -0
  615. package/skills/jgengine-gameplay/capabilities.md +378 -0
  616. package/skills/jgengine-gameplay/reference-systems.md +60 -0
  617. package/skills/jgengine-world/SKILL.md +38 -0
  618. package/skills/jgengine-world/api.md +2652 -0
  619. package/skills/jgengine-world/capabilities.md +370 -0
  620. package/skills/jgengine-world/reference.md +222 -0
  621. package/dist/cartridge/runtime.d.ts +0 -15
  622. package/dist/cartridge/runtime.js +0 -556
  623. package/dist/cartridge/spec.d.ts +0 -188
  624. package/dist/cartridge/spec.js +0 -19
  625. package/dist/cartridge/validate.d.ts +0 -2
  626. package/dist/cartridge/validate.js +0 -78
  627. package/dist/store/engineStore.d.ts +0 -22
  628. package/dist/store/engineStore.js +0 -50
  629. package/dist/store/features.d.ts +0 -10
  630. package/dist/store/features.js +0 -23
  631. package/llms.txt +0 -4983
@@ -1,34 +1,42 @@
1
- import { createCardPile } from "../cards/cardPile.js";
1
+ import {} from "../cards/cardPile.js";
2
2
  import { createDeathSystem, deathReasonFromEffect, normalizeOnDeath } from "../combat/death.js";
3
3
  import { createEffectSystem, } from "../combat/effects.js";
4
4
  import { createProjectileSystem } from "../combat/projectiles.js";
5
- import { resolveHitReaction, } from "../combat/hitReaction.js";
6
- import { pointInTelegraph, } from "../combat/telegraph.js";
5
+ import {} from "../combat/hitReaction.js";
6
+ import {} from "../combat/telegraph.js";
7
7
  import { createCommandRegistry, } from "../commands/commandRegistry.js";
8
- import { balance as walletBalance, canAfford as walletCanAfford, charge as walletCharge, chargeAll as walletChargeAll, createEmptyWallet, grant as walletGrant, } from "../economy/wallet.js";
9
- import { createCosmetics } from "../game/cosmetics.js";
8
+ import { balance as walletBalance, charge as walletCharge, createEmptyWallet, grant as walletGrant, isOverdrawn as walletIsOverdrawn, } from "../economy/wallet.js";
9
+ import {} from "../game/cosmetics.js";
10
10
  import { groundFieldFor } from "../world/terrain.js";
11
11
  import { createGameEvents } from "../game/events.js";
12
+ import { createVfxInstanceStore } from "../game/vfxInstance.js";
12
13
  import { createGameFeed } from "../game/feed.js";
13
- import { createLeaderboard } from "../game/leaderboard.js";
14
+ import { setGamePhase } from "../game/gamePhase.js";
15
+ import {} from "../game/leaderboard.js";
14
16
  import { createLoadouts } from "../game/loadout.js";
15
17
  import { createLootRegistry, grantDrops } from "../game/lootTable.js";
16
- import { createQuestJournal } from "../game/quest.js";
17
- import { createWorldItemStore, resolveDeathDrops, DEFAULT_RARITY, WORLD_ITEM_ENTITY_NAME, } from "../game/worldItem.js";
18
- import { createChat } from "../game/chat.js";
18
+ import {} from "../game/dialogue.js";
19
+ import {} from "../game/quest.js";
20
+ import { resolveDeathDrops, DEFAULT_RARITY, } from "../game/worldItem.js";
21
+ import {} from "../game/chat.js";
19
22
  import { createSocial } from "../game/social.js";
20
- import { createTradeSystem } from "../game/trade.js";
21
- import { createUnlocks } from "../game/unlocks.js";
23
+ import {} from "../game/trade.js";
24
+ import {} from "../game/unlocks.js";
22
25
  import { createInventorySet, putItem, } from "../inventory/inventoryModel.js";
23
26
  import { createItemUse, } from "../item/use.js";
24
27
  import { createWeaponStats } from "../item/weapon.js";
25
28
  import { createPoseState } from "../movement/poseState.js";
29
+ import { createBodyBind } from "../scene/bodyBind.js";
26
30
  import { createPaintLayer } from "../scene/paintLayer.js";
27
31
  import { createEntityStatsApi, seedStatValues, setStatValue, } from "../scene/entityStats.js";
32
+ import { createEntityStore, } from "../scene/entityStore.js";
28
33
  import { createForms } from "../scene/form.js";
34
+ import { scaledEntityColliders, scaledObjectColliders } from "../scene/colliders.js";
29
35
  import { raycastObjects, raycastObjectsAll } from "../scene/objectQuery.js";
30
- import { createObjectStore } from "../scene/objectStore.js";
31
- import { createRoster } from "../scene/roster.js";
36
+ import { createObjectStore, objectVisualScale } from "../scene/objectStore.js";
37
+ import {} from "../scene/roster.js";
38
+ import { createSelectionSet } from "../scene/selection.js";
39
+ import {} from "../game/connectedPlayers.js";
32
40
  import { createPossession } from "../scene/possession.js";
33
41
  import { createSceneRaycast, } from "../scene/sceneRaycast.js";
34
42
  import { createSpatialApi } from "../scene/spatial.js";
@@ -36,20 +44,32 @@ import { createTargeting } from "../scene/targeting.js";
36
44
  import { createStats } from "../stats/statModifiers.js";
37
45
  import { createChangeSignal, notifyAfter } from "../store/changeSignal.js";
38
46
  import { createObservableKeyedStore } from "../store/observableKeyedStore.js";
47
+ import { applyWorldSnapshot, composeWorldSnapshot, } from "./worldSnapshot.js";
48
+ import { policyProjectsViewers, projectByVisibleIds, projectEntitiesForViewer, projectPerUserForViewer, visibleEntityIds, } from "./worldProjection.js";
49
+ import { createRuntimeSave } from "./runtimeSave.js";
50
+ import { isOffline } from "./adapter.js";
51
+ import { localSaveBackend, memorySaveBackend } from "../game/saveStore.js";
39
52
  import { createSimClock } from "../time/simClock.js";
40
- import { createTurnLoop } from "../turn/turnLoop.js";
53
+ import {} from "../turn/turnLoop.js";
41
54
  import { RaceState } from "../game/race.js";
42
55
  import { createCameraDirector } from "./cameraDirector.js";
43
56
  import { createInputSnapshot } from "./inputSnapshot.js";
44
57
  import { createMotionIntents } from "./motionIntents.js";
58
+ import { baselineDescriptors } from "./descriptors/baseline.js";
59
+ import { featureDescriptors } from "./descriptors/features.js";
60
+ import { createCombatFx } from "./context/combatFx.js";
61
+ import { createContextRegistries } from "./context/registries.js";
62
+ import { createWorldItemContext } from "./context/worldItems.js";
45
63
  export function createGameContext(options) {
46
64
  const { definition, content, player } = options;
47
65
  const now = options.now ?? Date.now;
48
66
  const occluder = options.occluder;
49
67
  const signal = createChangeSignal();
68
+ let actingUserId = null;
69
+ const activeUserId = () => actingUserId ?? player.userId;
50
70
  const time = createSimClock({ config: definition.time, onChange: signal.notify });
51
71
  const ground = groundFieldFor(definition.world);
52
- const entities = definition.scene;
72
+ const entities = createEntityStore();
53
73
  const objects = createObjectStore();
54
74
  entities.subscribe(signal.notify);
55
75
  objects.subscribe(signal.notify);
@@ -101,13 +121,30 @@ export function createGameContext(options) {
101
121
  const override = entityColliders.get(instanceId);
102
122
  if (override !== undefined)
103
123
  return override;
104
- return catalogEntry(instanceId)?.colliders ?? null;
124
+ const entry = catalogEntry(instanceId);
125
+ if (entry?.colliders !== undefined)
126
+ return entry.colliders;
127
+ const scale = entry?.scale;
128
+ if (scale !== undefined && scale !== 1)
129
+ return scaledEntityColliders(scale);
130
+ return null;
131
+ }
132
+ function entityVisualScaleOf(instanceId) {
133
+ return catalogEntry(instanceId)?.scale ?? 1;
105
134
  }
106
135
  function objectCollidersOf(instanceId) {
107
136
  const override = objectColliders.get(instanceId);
108
137
  if (override !== undefined)
109
138
  return override;
110
- return catalogObject(instanceId)?.colliders ?? null;
139
+ const catalog = catalogObject(instanceId);
140
+ if (catalog?.colliders !== undefined)
141
+ return catalog.colliders;
142
+ if (catalog?.halfExtents !== undefined)
143
+ return null;
144
+ const object = objects.get(instanceId);
145
+ if (object === null || object.visual?.scale === undefined)
146
+ return null;
147
+ return scaledObjectColliders(objectVisualScale(object.visual));
111
148
  }
112
149
  const targeting = notifyAfter(createTargeting({
113
150
  candidates: () => [...refreshCandidateIds()],
@@ -168,41 +205,54 @@ export function createGameContext(options) {
168
205
  };
169
206
  const feed = createGameFeed(definition.feed);
170
207
  const lootRegistry = createLootRegistry();
171
- const unlocks = notifyAfter(createUnlocks(), ["grant", "hydrate"], signal.notify);
172
- const rawSocial = createSocial({
173
- events,
174
- now,
175
- emotes: {
176
- entities: { get: (id) => entities.get(id) },
177
- spatial: { inRadius: (center, radius, filter) => spatial.inRadius(center, radius, filter) },
178
- },
179
- });
180
- const social = {
181
- friends: notifyAfter(rawSocial.friends, ["request", "accept", "decline", "remove", "block", "hydrate"], signal.notify),
182
- party: notifyAfter(rawSocial.party, ["invite", "accept", "decline", "kick", "leave", "promote"], signal.notify),
183
- presence: rawSocial.presence,
184
- emotes: rawSocial.emotes,
185
- worldInvites: notifyAfter(rawSocial.worldInvites, ["invite", "accept", "decline"], signal.notify),
208
+ const features = definition.features ?? {};
209
+ const featureRegistry = new Map();
210
+ const featureValue = (key) => featureRegistry.get(key);
211
+ let rawSocial = null;
212
+ const sharedSocial = () => {
213
+ if (rawSocial === null) {
214
+ rawSocial = createSocial({
215
+ events,
216
+ now,
217
+ emotes: {
218
+ entities: { get: (id) => entities.get(id) },
219
+ spatial: { inRadius: (center, radius, filter) => spatial.inRadius(center, radius, filter) },
220
+ },
221
+ });
222
+ }
223
+ return rawSocial;
186
224
  };
187
- const chat = notifyAfter(createChat({
188
- events,
189
- now,
190
- party: rawSocial.party,
191
- proximity: {
192
- entities: { get: (id) => entities.get(id) },
193
- spatial: { inRadius: (center, radius, filter) => spatial.inRadius(center, radius, filter) },
194
- },
195
- blockedBy: (userId) => rawSocial.friends.snapshot(userId).blocked,
196
- }), ["register", "send", "whisper", "hydrate"], signal.notify);
197
- const leaderboard = notifyAfter(createLeaderboard(), ["increment", "hydrate"], signal.notify);
198
- const roster = notifyAfter(createRoster({ now }), ["capture", "release", "setEquipped", "hydrate"], signal.notify);
199
- const playerStats = createStats({});
225
+ const playerStatsByUser = new Map();
226
+ function playerStatsFor(userId) {
227
+ let stats = playerStatsByUser.get(userId);
228
+ if (stats === undefined) {
229
+ stats = createStats({});
230
+ playerStatsByUser.set(userId, stats);
231
+ }
232
+ return stats;
233
+ }
200
234
  const pose = createPoseState((instanceId) => catalogEntry(instanceId)?.movement);
201
235
  const commandRegistry = createCommandRegistry();
236
+ if (definition.lifecycle !== undefined) {
237
+ const lifecycle = definition.lifecycle;
238
+ commandRegistry.define(lifecycle.commands?.start ?? "start", {
239
+ apply(state, input) {
240
+ const next = lifecycle.start(lifecycle.store.read(state), state, input);
241
+ lifecycle.store.write(state, next);
242
+ setGamePhase(state, lifecycle.phaseOf(next));
243
+ },
244
+ });
245
+ commandRegistry.define(lifecycle.commands?.restart ?? "restart", {
246
+ apply(state) {
247
+ const next = lifecycle.restart(lifecycle.store.read(state), state);
248
+ lifecycle.store.write(state, next);
249
+ setGamePhase(state, lifecycle.phaseOf(next));
250
+ },
251
+ });
252
+ }
202
253
  const itemUse = createItemUse((itemId) => content.itemById?.(itemId)?.use);
203
254
  const possession = notifyAfter(createPossession({ entities, events }), ["possess", "own", "disown"], signal.notify);
204
255
  const forms = notifyAfter(createForms({ entities, time, events }), ["shapeshift", "revert"], signal.notify);
205
- const cosmetics = notifyAfter(createCosmetics({ events }), ["apply", "equip", "hydrate"], signal.notify);
206
256
  const paintLayer = notifyAfter(createPaintLayer(), ["paint", "clear"], signal.notify);
207
257
  const inventoryDeclarations = definition.inventories ?? {};
208
258
  const inventoryIds = Object.keys(inventoryDeclarations);
@@ -211,7 +261,15 @@ export function createGameContext(options) {
211
261
  layouts[inventoryId] = { slots: declaration.slots, accepts: declaration.accepts };
212
262
  }
213
263
  const traits = Object.values(inventoryDeclarations).find((declaration) => declaration.traits !== undefined)?.traits ?? { stackLimit: () => Number.POSITIVE_INFINITY };
214
- const inventory = notifyAfter(createInventorySet(layouts, traits), ["put", "take", "move", "replaceState"], signal.notify);
264
+ const inventoryByUser = new Map();
265
+ function inventoryFor(userId) {
266
+ let set = inventoryByUser.get(userId);
267
+ if (set === undefined) {
268
+ set = notifyAfter(createInventorySet(layouts, traits), ["put", "take", "move", "replaceState"], signal.notify);
269
+ inventoryByUser.set(userId, set);
270
+ }
271
+ return set;
272
+ }
215
273
  const wallets = new Map();
216
274
  const walletOf = (userId) => wallets.get(userId) ?? createEmptyWallet();
217
275
  const economy = {
@@ -220,16 +278,18 @@ export function createGameContext(options) {
220
278
  wallets.set(userId, walletGrant(walletOf(userId), currencyId, amount));
221
279
  signal.notify();
222
280
  },
223
- charge(userId, currencyId, amount) {
224
- const result = walletCharge(walletOf(userId), currencyId, amount);
281
+ charge(userId, currencyId, amount, options) {
282
+ const result = walletCharge(walletOf(userId), currencyId, amount, options);
225
283
  if (result.status === "rejected")
226
284
  return { reason: result.reason };
227
285
  wallets.set(userId, result.state);
228
286
  signal.notify();
229
287
  return null;
230
288
  },
289
+ isOverdrawn: (userId, currencyId) => walletIsOverdrawn(walletOf(userId), currencyId),
231
290
  };
232
- function putIntoAnyInventory(itemId, count) {
291
+ function putIntoAnyInventory(userId, itemId, count) {
292
+ const inventory = inventoryFor(userId);
233
293
  for (const inventoryId of inventoryIds) {
234
294
  if (inventory.put(inventoryId, itemId, count).status === "ok")
235
295
  return;
@@ -241,7 +301,7 @@ export function createGameContext(options) {
241
301
  roll: lootRegistry.roll,
242
302
  grantToPlayer(userId, drops, source) {
243
303
  grantDrops(drops, {
244
- putItem: (itemId, count) => putIntoAnyInventory(itemId, count),
304
+ putItem: (itemId, count) => putIntoAnyInventory(userId, itemId, count),
245
305
  grantCurrency: (currencyId, amount) => economy.grant(userId, currencyId, amount),
246
306
  });
247
307
  const event = { userId, drops };
@@ -250,69 +310,15 @@ export function createGameContext(options) {
250
310
  events.emit("loot.granted", event);
251
311
  },
252
312
  };
253
- const trade = createTradeSystem({
254
- resolveTrade: (itemId) => content.itemById?.(itemId)?.trade,
255
- wallet: {
256
- canAfford: (costs) => (walletCanAfford(walletOf(player.userId), costs) ? null : "insufficient-funds"),
257
- charge(costs) {
258
- const result = walletChargeAll(walletOf(player.userId), costs);
259
- if (result.status === "ok") {
260
- wallets.set(player.userId, result.state);
261
- signal.notify();
262
- }
263
- },
264
- grant(gains) {
265
- for (const [currencyId, amount] of Object.entries(gains)) {
266
- economy.grant(player.userId, currencyId, amount);
267
- }
268
- },
269
- },
270
- inventory: {
271
- put(inventoryId, itemId, count) {
272
- if (layouts[inventoryId] === undefined)
273
- return { reason: `unknown inventory "${inventoryId}"` };
274
- const result = inventory.put(inventoryId, itemId, count);
275
- return result.status === "ok" ? null : { reason: result.reason };
276
- },
277
- take(inventoryId, itemId, count) {
278
- if (layouts[inventoryId] === undefined)
279
- return { reason: `unknown inventory "${inventoryId}"` };
280
- const result = inventory.take(inventoryId, itemId, count);
281
- return result.status === "ok" ? null : { reason: result.reason };
282
- },
283
- count: (inventoryId, itemId) => inventory.count(inventoryId, itemId),
284
- },
285
- });
286
313
  function seedUserPool(userId, statId, pool) {
287
314
  const map = ensureInstanceStats(userId);
288
315
  const next = setStatValue(map, statId, pool);
289
316
  map[statId] = next[statId];
290
317
  }
291
- const rawQuest = createQuestJournal({
292
- events,
293
- rewards: {
294
- grantXp(userId, amount) {
295
- const existing = ensureInstanceStats(userId)["xp"];
296
- const current = (existing?.current ?? 0) + amount;
297
- seedUserPool(userId, "xp", { current, max: Math.max(existing?.max ?? 0, current) });
298
- },
299
- grantEconomy: (userId, currencyId, amount) => economy.grant(userId, currencyId, amount),
300
- grantItem(userId, inventoryId, itemId, count) {
301
- if (userId !== player.userId)
302
- return { reason: `unknown user "${userId}"` };
303
- if (layouts[inventoryId] === undefined)
304
- return { reason: `unknown inventory "${inventoryId}"` };
305
- const result = inventory.put(inventoryId, itemId, count);
306
- return result.status === "ok" ? null : { reason: result.reason };
307
- },
308
- grantUnlock: (userId, unlockId) => unlocks.grant(userId, unlockId),
309
- },
310
- hasUnlock: (userId, id) => unlocks.has(userId, id),
311
- });
312
- const quest = notifyAfter(rawQuest, ["accept", "abandon", "progress", "turnIn", "grant", "revoke", "hydrate"], signal.notify);
313
318
  const loadouts = notifyAfter(createLoadouts({
314
319
  inventory: {
315
- begin() {
320
+ begin(userId) {
321
+ const inventory = inventoryFor(userId);
316
322
  const staged = new Map();
317
323
  return {
318
324
  put(inventoryId, itemId, count, slot) {
@@ -335,7 +341,7 @@ export function createGameContext(options) {
335
341
  },
336
342
  stats: { seed: seedUserPool },
337
343
  economy: { grant: economy.grant },
338
- unlocks: { grant: unlocks.grant },
344
+ unlocks: { grant: (userId, unlockId) => featureValue("unlocks")?.grant(userId, unlockId) },
339
345
  }), ["applyLoadout"], signal.notify);
340
346
  function spawnEntity(name, spawnOptions) {
341
347
  const entry = content.entityById?.(name);
@@ -356,6 +362,23 @@ export function createGameContext(options) {
356
362
  entityColliders.delete(instanceId);
357
363
  return existed;
358
364
  }
365
+ function moveEntityTowardCommit(instanceId, target, options) {
366
+ const next = spatial.moveToward(instanceId, target, options);
367
+ if (next === null)
368
+ return null;
369
+ const current = entities.get(instanceId);
370
+ if (current === null)
371
+ return null;
372
+ let rotationY = current.rotationY;
373
+ if (options.face === true) {
374
+ const dx = next[0] - current.position[0];
375
+ const dz = next[2] - current.position[2];
376
+ if (Math.hypot(dx, dz) > 1e-9)
377
+ rotationY = Math.atan2(dx, dz);
378
+ }
379
+ entities.setPose(instanceId, { position: next, rotationY });
380
+ return next;
381
+ }
359
382
  function resetAllToSpawn(filter) {
360
383
  let count = 0;
361
384
  for (const entity of entities.list()) {
@@ -366,37 +389,28 @@ export function createGameContext(options) {
366
389
  }
367
390
  return count;
368
391
  }
369
- const worldItems = notifyAfter(createWorldItemStore({
370
- spawnEntity: (position) => entities.spawn(WORLD_ITEM_ENTITY_NAME, { position, role: "prop" }),
371
- despawnEntity,
372
- resolvePosition: (instanceId) => entities.get(instanceId)?.position,
373
- }), ["spawn", "take", "remove"], signal.notify);
374
- function spawnWorldItem(input) {
375
- const record = worldItems.spawn(input);
376
- events.emit("worldItem.dropped", {
377
- instanceId: record.instanceId,
378
- itemId: record.itemId,
379
- rarity: record.rarity,
380
- count: record.count,
381
- position: [input.position[0], input.position[1], input.position[2]],
382
- ...(record.source !== undefined ? { source: record.source } : {}),
383
- });
384
- return record;
385
- }
386
- function pickupWorldItem(instanceId, userId) {
387
- const record = worldItems.take(instanceId);
388
- if (record === null)
389
- return { status: "rejected", reason: "not-found" };
390
- loot.grantToPlayer(userId, [{ item: record.itemId, count: record.count }], "worldItem.pickup");
391
- events.emit("worldItem.picked_up", {
392
- instanceId,
393
- userId,
394
- itemId: record.itemId,
395
- rarity: record.rarity,
396
- count: record.count,
392
+ const bodyBinds = new Map();
393
+ function bind(key) {
394
+ const existing = bodyBinds.get(key);
395
+ if (existing !== undefined)
396
+ return existing;
397
+ const created = createBodyBind({
398
+ has: (id) => entities.get(id) !== null,
399
+ spawn: spawnEntity,
400
+ despawn: despawnEntity,
401
+ setPose: entities.setPose,
402
+ update: entities.update,
397
403
  });
398
- return { status: "ok", record };
404
+ bodyBinds.set(key, created);
405
+ return created;
399
406
  }
407
+ const { worldItems, spawnWorldItem, pickupWorldItem } = createWorldItemContext({
408
+ entities,
409
+ events,
410
+ despawnEntity,
411
+ grantToPlayer: loot.grantToPlayer,
412
+ signalNotify: signal.notify,
413
+ });
400
414
  const death = createDeathSystem({
401
415
  resolveOnDeath: (instanceId) => catalogEntry(instanceId)?.onDeath,
402
416
  resolveIdentity(instanceId) {
@@ -456,121 +470,16 @@ export function createGameContext(options) {
456
470
  }
457
471
  },
458
472
  }), ["applyEffect"], signal.notify);
459
- function emitFloatText(input) {
460
- const position = input.position ??
461
- (input.instanceId === undefined ? undefined : entities.get(input.instanceId)?.position);
462
- if (position === undefined)
463
- return;
464
- const text = input.text ?? (input.amount === undefined ? "" : String(Math.round(input.amount)));
465
- const event = {
466
- position: [position[0], position[1], position[2]],
467
- text,
468
- kind: input.kind ?? "info",
469
- };
470
- if (input.instanceId !== undefined)
471
- event.instanceId = input.instanceId;
472
- if (input.amount !== undefined)
473
- event.amount = input.amount;
474
- if (input.hitType !== undefined)
475
- event.hitType = input.hitType;
476
- if (input.element !== undefined)
477
- event.element = input.element;
478
- if (input.crit !== undefined)
479
- event.crit = input.crit;
480
- if (input.scale !== undefined)
481
- event.scale = input.scale;
482
- events.emit("entity.floatText", event);
483
- }
484
- let telegraphSeq = 0;
485
- function fireTelegraph(input) {
486
- const id = telegraphSeq++;
487
- const telegraphEvent = {
488
- id,
489
- shape: input.shape,
490
- position: [input.at[0], input.at[1], input.at[2]],
491
- windupMs: input.windupMs,
492
- kind: input.kind ?? "danger",
493
- };
494
- if (input.dir !== undefined)
495
- telegraphEvent.dir = input.dir;
496
- events.emit("combat.telegraph", telegraphEvent);
497
- const cancelVisual = () => events.emit("combat.telegraphCancelled", { id });
498
- const bound = input.effect;
499
- if (bound === undefined)
500
- return cancelVisual;
501
- const config = { shape: input.shape, at: input.at, windupMs: input.windupMs };
502
- if (input.dir !== undefined)
503
- config.dir = input.dir;
504
- const cancelEffect = time.after(input.windupMs / 1000, () => {
505
- const targets = entities.list().filter((entity) => pointInTelegraph(config, entity.position));
506
- for (const target of targets) {
507
- applyEffectAndFloat({
508
- from: input.from,
509
- to: target.id,
510
- effect: bound.effect,
511
- ...(bound.via === undefined ? {} : { via: bound.via }),
512
- });
513
- }
514
- });
515
- return () => {
516
- cancelEffect();
517
- cancelVisual();
518
- };
519
- }
520
- function applyHitReaction(input) {
521
- const attacker = entities.get(input.from);
522
- const target = entities.get(input.to);
523
- if (target === null)
524
- return null;
525
- const attackerPos = attacker?.position ?? target.position;
526
- const reaction = resolveHitReaction(input.config, {
527
- attackerPos,
528
- targetPos: target.position,
529
- ...(input.power === undefined ? {} : { power: input.power }),
530
- });
531
- entities.setPose(input.to, {
532
- position: [
533
- target.position[0] + reaction.impulse[0],
534
- target.position[1] + reaction.impulse[1],
535
- target.position[2] + reaction.impulse[2],
536
- ],
537
- rotationY: target.rotationY,
538
- });
539
- const reactionEvent = {
540
- instanceId: input.to,
541
- position: [target.position[0], target.position[1], target.position[2]],
542
- hitstopMs: reaction.hitstopMs,
543
- };
544
- if (reaction.shake !== null)
545
- reactionEvent.shake = reaction.shake;
546
- events.emit("combat.hitReaction", reactionEvent);
547
- return reaction;
548
- }
549
- function applyEffectAndFloat(input) {
550
- const positionsBefore = new Map();
551
- for (const entity of entities.list())
552
- positionsBefore.set(entity.id, entity.position);
553
- const results = effects.applyEffect(input);
554
- for (const result of results) {
555
- let total = 0;
556
- for (const delta of result.applied)
557
- total += delta.delta;
558
- if (total === 0)
559
- continue;
560
- const position = entities.get(result.instanceId)?.position ?? positionsBefore.get(result.instanceId);
561
- if (position === undefined)
562
- continue;
563
- const magnitude = Math.abs(total);
564
- emitFloatText({
565
- instanceId: result.instanceId,
566
- position: [position[0], position[1], position[2]],
567
- text: String(Math.round(magnitude)),
568
- kind: total < 0 ? "damage" : "heal",
569
- amount: magnitude,
570
- });
571
- }
572
- return results;
573
- }
473
+ const { emitFloatText, emitVfx, fireTelegraph, applyHitReaction, applyEffectAndFloat } = createCombatFx({
474
+ entities,
475
+ events,
476
+ time,
477
+ applyEffect: (input) => effects.applyEffect(input),
478
+ });
479
+ const vfxInstances = createVfxInstanceStore({
480
+ onOp: (op) => events.emit("combat.vfxInstance", op),
481
+ now: () => time.now() * 1000,
482
+ });
574
483
  const floatingEffects = {
575
484
  canReceive: effects.canReceive,
576
485
  preview: effects.preview,
@@ -632,8 +541,15 @@ export function createGameContext(options) {
632
541
  });
633
542
  },
634
543
  }), ["fireProjectile", "settleProjectile"], signal.notify);
544
+ const objectSelection = notifyAfter(createSelectionSet(), ["add", "remove", "toggle", "replace", "clear"], signal.notify);
635
545
  const sceneObjects = {
636
546
  ...objects,
547
+ remove(instanceId) {
548
+ const existed = objects.remove(instanceId);
549
+ if (existed)
550
+ objectSelection.remove(instanceId);
551
+ return existed;
552
+ },
637
553
  catalog: (instanceId) => catalogObject(instanceId) ?? null,
638
554
  raycast: (input) => raycastObjects(objects, input),
639
555
  raycastAll: (input) => raycastObjectsAll(objects, input),
@@ -645,87 +561,132 @@ export function createGameContext(options) {
645
561
  signal.notify();
646
562
  },
647
563
  collidersOf: objectCollidersOf,
564
+ selection: objectSelection,
648
565
  };
649
- const store = notifyAfter(createObservableKeyedStore(), ["set", "delete"], signal.notify);
650
- const cardPiles = new Map();
651
- function pile(id, config) {
652
- const existing = cardPiles.get(id);
653
- if (existing !== undefined)
654
- return existing;
655
- if (config === undefined) {
656
- throw new Error(`cardPile "${id}" has not been created yet; pass a config on first access`);
566
+ const store = notifyAfter(createObservableKeyedStore(), ["set", "delete", "hydrate"], signal.notify);
567
+ const { pile, loop, raceState, cardPiles, turnLoops } = createContextRegistries(signal.notify);
568
+ const camera = notifyAfter(createCameraDirector(), ["follow", "setCinematic", "setChaseTuning"], signal.notify);
569
+ const input = createInputSnapshot();
570
+ const motionByUser = new Map();
571
+ function motionFor(userId) {
572
+ let queue = motionByUser.get(userId);
573
+ if (queue === undefined) {
574
+ queue = createMotionIntents();
575
+ motionByUser.set(userId, queue);
657
576
  }
658
- const created = notifyAfter(createCardPile(config), ["shuffle", "draw", "discard", "exhaust", "move", "reset"], signal.notify);
659
- cardPiles.set(id, created);
660
- return created;
577
+ return queue;
661
578
  }
662
- const turnLoops = new Map();
663
- function loop(id, config) {
664
- const existing = turnLoops.get(id);
665
- if (existing !== undefined)
666
- return existing;
667
- if (config === undefined) {
668
- throw new Error(`turn loop "${id}" has not been created yet; pass a config on first access`);
669
- }
670
- const raw = createTurnLoop(config);
671
- const wrappedCommit = notifyAfter(raw.commit, ["submit", "expected", "commit", "discard", "clear"], signal.notify);
672
- const wrapped = {
673
- ...notifyAfter(raw, [
674
- "setOrder",
675
- "addParticipant",
676
- "removeParticipant",
677
- "advancePhase",
678
- "advanceTurn",
679
- "advanceRound",
680
- "spend",
681
- "gain",
682
- "refill",
683
- "restore",
684
- ], signal.notify),
685
- commit: wrappedCommit,
686
- };
687
- turnLoops.set(id, wrapped);
688
- return wrapped;
579
+ const featureDeps = {
580
+ features,
581
+ signalNotify: signal.notify,
582
+ events,
583
+ now,
584
+ entities,
585
+ spatial,
586
+ store,
587
+ commandRegistry,
588
+ economy,
589
+ content,
590
+ activeUserId,
591
+ walletOf,
592
+ setWallet: (userId, state) => wallets.set(userId, state),
593
+ layouts,
594
+ inventoryFor,
595
+ ensureInstanceStats,
596
+ seedUserPool,
597
+ sharedSocial,
598
+ pile,
599
+ loop,
600
+ cardPiles,
601
+ turnLoops,
602
+ raceState,
603
+ feature: featureValue,
604
+ };
605
+ const featureReplicateModules = [];
606
+ const featureSaveModules = [];
607
+ for (const descriptor of featureDescriptors) {
608
+ if (!descriptor.enabled(features))
609
+ continue;
610
+ const build = descriptor.create(featureDeps);
611
+ featureRegistry.set(descriptor.key, build.value);
612
+ if (build.replicate !== undefined)
613
+ featureReplicateModules.push(build.replicate);
614
+ if (build.save !== undefined)
615
+ featureSaveModules.push(build.save);
689
616
  }
690
- class NotifyingRaceState extends RaceState {
691
- addRacer(racerId, startTime) {
692
- super.addRacer(racerId, startTime);
693
- signal.notify();
694
- }
695
- removeRacer(racerId) {
696
- super.removeRacer(racerId);
697
- signal.notify();
698
- }
699
- reset() {
700
- super.reset();
701
- signal.notify();
617
+ const baselineDeps = {
618
+ signalNotify: signal.notify,
619
+ entities,
620
+ statsByInstance,
621
+ store,
622
+ feed,
623
+ inventoryIds,
624
+ inventoryByUser,
625
+ inventoryFor,
626
+ wallets,
627
+ time,
628
+ pose,
629
+ possession,
630
+ motionByUser,
631
+ motionFor,
632
+ };
633
+ const baselineBuilds = baselineDescriptors.map((descriptor) => descriptor.create(baselineDeps));
634
+ const snapshotModules = [
635
+ ...baselineBuilds.flatMap((build) => (build.replicate === undefined ? [] : [build.replicate])),
636
+ ...featureReplicateModules,
637
+ ];
638
+ const replication = options.replication;
639
+ const projectsViewers = policyProjectsViewers(replication);
640
+ const aoiRadius = replication?.aoiRadius;
641
+ const projectorFor = (key) => {
642
+ if (aoiRadius !== undefined && key === "entities") {
643
+ return (data, viewer) => projectEntitiesForViewer(data, viewer, aoiRadius);
702
644
  }
703
- eliminate(racerId) {
704
- super.eliminate(racerId);
705
- signal.notify();
645
+ if (aoiRadius !== undefined && key === "stats") {
646
+ return (data, viewer, world) => projectByVisibleIds(data, visibleEntityIds((world["entities"] ?? []), viewer, aoiRadius));
706
647
  }
707
- update(now, positions) {
708
- const raceEvents = super.update(now, positions);
709
- if (raceEvents.length > 0)
710
- signal.notify();
711
- return raceEvents;
648
+ if (replication?.privatePerUser === true && key === "inventory") {
649
+ return (data, viewer) => projectPerUserForViewer(data, viewer);
712
650
  }
651
+ return undefined;
652
+ };
653
+ const replicationModules = snapshotModules.map((module) => {
654
+ const project = projectorFor(module.key);
655
+ return {
656
+ ...module,
657
+ version: () => signal.version(),
658
+ ...(project === undefined ? {} : { project }),
659
+ };
660
+ });
661
+ const replicationVersion = () => replicationModules.reduce((sum, module) => sum + (module.version?.() ?? 0), 0);
662
+ /**
663
+ * The whole-world *save* set is a superset of the *replication* set (`snapshotModules`): it adds the
664
+ * always-on baseline subsystems (economy, time, pose, possession, motion) plus every opted-in
665
+ * feature's `save` module (unlocks, roster, quest, cosmetics, cards, turn) — the persistent state a
666
+ * single-player save must restore but a host does not replicate to clients. Coverage is owned per
667
+ * subsystem, not hand-listed against a drifting feature manifest: each feature descriptor emits its
668
+ * own `save`, so a new persistent feature can't silently fall out of the save. Keeping the two sets
669
+ * distinct leaves `ctx.snapshot()`/`ctx.hydrate()` — the host→client payload — byte-identical for
670
+ * every multiplayer game, while `ctx.game.save` still persists everything.
671
+ * Mutable so game systems can register save modules at install time via `ctx.game.registerSave`.
672
+ */
673
+ const saveModules = [
674
+ ...snapshotModules,
675
+ ...baselineBuilds.flatMap((build) => (build.save === undefined ? [] : [build.save])),
676
+ ...featureSaveModules,
677
+ ];
678
+ function registerSave(module) {
679
+ saveModules.push(module);
713
680
  }
714
- const raceStates = new Map();
715
- function raceState(id, config) {
716
- const existing = raceStates.get(id);
717
- if (existing !== undefined)
718
- return existing;
719
- if (config === undefined) {
720
- throw new Error(`race "${id}" has not been created yet; pass a config on first access`);
721
- }
722
- const created = new NotifyingRaceState(config);
723
- raceStates.set(id, created);
724
- return created;
681
+ function registerReplicate(module) {
682
+ const project = projectorFor(module.key);
683
+ replicationModules.push({
684
+ ...module,
685
+ version: () => signal.version(),
686
+ ...(project === undefined ? {} : { project }),
687
+ });
688
+ saveModules.push(module);
725
689
  }
726
- const camera = notifyAfter(createCameraDirector(), ["follow", "setCinematic", "setChaseTuning"], signal.notify);
727
- const input = createInputSnapshot();
728
- const motion = createMotionIntents();
729
690
  const ctx = {
730
691
  scene: {
731
692
  object: sceneObjects,
@@ -737,11 +698,16 @@ export function createGameContext(options) {
737
698
  setPoseConstraint: entities.setPoseConstraint,
738
699
  get: entities.get,
739
700
  list: entities.list,
701
+ ids: entities.ids,
702
+ subscribeMembership: entities.subscribeMembership,
740
703
  spawnPoseOf: entities.spawnPoseOf,
741
704
  resetToSpawn: entities.resetToSpawn,
742
705
  resetAllToSpawn,
706
+ blackboard: entities.blackboard,
743
707
  stats: entityStats,
744
708
  floatText: emitFloatText,
709
+ vfx: emitVfx,
710
+ vfxInstance: vfxInstances,
745
711
  telegraph: fireTelegraph,
746
712
  hitReaction: applyHitReaction,
747
713
  setTarget: targeting.setTarget,
@@ -758,6 +724,7 @@ export function createGameContext(options) {
758
724
  hasLineOfSight: spatial.hasLineOfSight,
759
725
  queryArc: spatial.queryArc,
760
726
  moveToward: spatial.moveToward,
727
+ moveTowardCommit: moveEntityTowardCommit,
761
728
  invalidateSpatial: spatial.invalidate,
762
729
  setColliders(instanceId, colliders) {
763
730
  if (colliders === null)
@@ -767,8 +734,10 @@ export function createGameContext(options) {
767
734
  signal.notify();
768
735
  },
769
736
  collidersOf: entityCollidersOf,
737
+ visualScaleOf: entityVisualScaleOf,
770
738
  form: forms,
771
739
  paint: paintLayer,
740
+ bind,
772
741
  },
773
742
  worldItem: {
774
743
  spawn: spawnWorldItem,
@@ -795,8 +764,27 @@ export function createGameContext(options) {
795
764
  signal.notify();
796
765
  return result;
797
766
  },
767
+ runAs(actorUserId, name, input) {
768
+ const previous = actingUserId;
769
+ actingUserId = actorUserId;
770
+ try {
771
+ const result = commandRegistry.run(ctx, name, input);
772
+ signal.notify();
773
+ return result;
774
+ }
775
+ finally {
776
+ actingUserId = previous;
777
+ }
778
+ },
779
+ actor: () => actingUserId,
798
780
  },
799
781
  events,
782
+ playEntityAnimation: (instanceId, event) => events.emit("entity.animation", { instanceId, event }),
783
+ audio: {
784
+ play: (sound, at) => events.emit("audio.play", at === undefined ? { sound } : { sound, at }),
785
+ music: (theme, transpose) => events.emit("audio.music", transpose === undefined ? { theme } : { theme, transpose }),
786
+ resume: () => events.emit("audio.resume", {}),
787
+ },
800
788
  feed: {
801
789
  bind: (action) => feed.bind(action, events),
802
790
  push(action, entry) {
@@ -812,30 +800,45 @@ export function createGameContext(options) {
812
800
  },
813
801
  },
814
802
  loot,
815
- trade,
816
- quest,
817
- social,
818
- chat,
819
- unlocks,
803
+ trade: featureValue("trade"),
804
+ quest: featureValue("quest"),
805
+ social: featureValue("social"),
806
+ chat: featureValue("chat"),
807
+ unlocks: featureValue("unlocks"),
820
808
  economy,
821
- leaderboard,
822
- roster,
809
+ leaderboard: featureValue("leaderboard"),
810
+ roster: featureValue("roster"),
823
811
  store,
824
- cards: { pile },
825
- turn: { loop },
826
- race: { state: raceState },
812
+ dialogue: featureValue("dialogue"),
813
+ cards: featureValue("cards"),
814
+ turn: featureValue("turn"),
815
+ race: featureValue("race"),
816
+ players: featureValue("players"),
817
+ registerSave,
818
+ registerReplicate,
827
819
  },
828
820
  player: {
829
- userId: player.userId,
821
+ get userId() {
822
+ return activeUserId();
823
+ },
830
824
  isNew: player.isNew,
831
- inventory,
832
- stats: playerStats,
825
+ get inventory() {
826
+ return inventoryFor(activeUserId());
827
+ },
828
+ inventoryFor,
829
+ get stats() {
830
+ return playerStatsFor(activeUserId());
831
+ },
832
+ statsFor: playerStatsFor,
833
833
  loadout: loadouts,
834
834
  applyLoadout: loadouts.applyLoadout,
835
835
  movement: pose,
836
836
  possession,
837
- cosmetics,
838
- motion,
837
+ cosmetics: featureValue("cosmetics"),
838
+ get motion() {
839
+ return motionFor(activeUserId());
840
+ },
841
+ motionFor,
839
842
  },
840
843
  item: {
841
844
  use: {
@@ -855,6 +858,49 @@ export function createGameContext(options) {
855
858
  input,
856
859
  subscribe: signal.subscribe,
857
860
  version: signal.version,
861
+ touch: signal.notify,
862
+ snapshot: (viewer) => composeWorldSnapshot(replicationModules, viewer),
863
+ hydrate(snapshot) {
864
+ applyWorldSnapshot(replicationModules, snapshot);
865
+ signal.notify();
866
+ },
867
+ replicationVersion,
868
+ replicatesPerViewer: () => projectsViewers,
858
869
  };
870
+ const saveOptions = resolveSaveOptions(definition, options);
871
+ if (saveOptions !== undefined) {
872
+ const saveTarget = {
873
+ snapshot: () => composeWorldSnapshot(saveModules),
874
+ hydrate: (snapshot) => {
875
+ applyWorldSnapshot(saveModules, snapshot);
876
+ signal.notify();
877
+ },
878
+ subscribe: signal.subscribe,
879
+ };
880
+ ctx.game.save = createRuntimeSave({ target: saveTarget, ...saveOptions });
881
+ }
859
882
  return ctx;
860
883
  }
884
+ function saveKey(name) {
885
+ const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
886
+ return `jgengine:save:${slug === "" ? "game" : slug}`;
887
+ }
888
+ /** Explicit `options.save` wins (the shell can inject any backend, e.g. cloud); otherwise `defineGame({ persist })` auto-wires a localStorage save for offline/single-player worlds only. */
889
+ function resolveSaveOptions(definition, options) {
890
+ if (options.save !== undefined)
891
+ return options.save;
892
+ const persist = definition.persist;
893
+ if (persist === undefined || persist === false)
894
+ return undefined;
895
+ if (!isOffline(definition.multiplayer))
896
+ return undefined;
897
+ const config = persist === true ? {} : persist;
898
+ return {
899
+ backend: config.storage === "memory" ? memorySaveBackend() : localSaveBackend(),
900
+ mode: config.mode,
901
+ key: saveKey(definition.name),
902
+ version: config.version,
903
+ autosaveMs: config.autosaveMs,
904
+ now: options.now,
905
+ };
906
+ }