@jgengine/core 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (727) hide show
  1. package/CHANGELOG.md +39 -2
  2. package/LICENSE +203 -661
  3. package/README.md +1 -1
  4. package/dist/ai/crowd.d.ts +55 -0
  5. package/dist/ai/crowd.js +114 -0
  6. package/dist/ai/flock.d.ts +5 -2
  7. package/dist/ai/flock.js +5 -2
  8. package/dist/ai/groupAssist.d.ts +1 -0
  9. package/dist/ai/groupAssist.js +1 -0
  10. package/dist/ai/heatSystem.d.ts +76 -0
  11. package/dist/ai/heatSystem.js +97 -0
  12. package/dist/ai/interestScheduler.d.ts +107 -0
  13. package/dist/ai/interestScheduler.js +118 -0
  14. package/dist/ai/jobBoard.d.ts +1 -0
  15. package/dist/ai/jobBoard.js +1 -0
  16. package/dist/ai/laneSelect.d.ts +5 -2
  17. package/dist/ai/laneSelect.js +5 -2
  18. package/dist/ai/mobBrain.d.ts +1 -0
  19. package/dist/ai/mobBrain.js +1 -0
  20. package/dist/ai/populationDirector.d.ts +103 -0
  21. package/dist/ai/populationDirector.js +190 -0
  22. package/dist/ai/pursuit.d.ts +56 -0
  23. package/dist/ai/pursuit.js +53 -0
  24. package/dist/ai/spawnDirector.d.ts +7 -2
  25. package/dist/ai/spawnDirector.js +18 -15
  26. package/dist/ai/spawnPoint.d.ts +3 -1
  27. package/dist/ai/spawnPoint.js +3 -1
  28. package/dist/ai/targetAcquisition.d.ts +84 -0
  29. package/dist/ai/targetAcquisition.js +92 -0
  30. package/dist/anim/easing.d.ts +19 -0
  31. package/dist/anim/easing.js +19 -0
  32. package/dist/anim/oscillator.d.ts +12 -0
  33. package/dist/anim/oscillator.js +25 -0
  34. package/dist/area/areaEffectField.d.ts +127 -0
  35. package/dist/area/areaEffectField.js +175 -0
  36. package/dist/area/stackPolicy.d.ts +47 -0
  37. package/dist/area/stackPolicy.js +99 -0
  38. package/dist/authoring.d.ts +2 -1
  39. package/dist/authoring.js +2 -1
  40. package/dist/board/laneBoard.d.ts +13 -0
  41. package/dist/board/laneBoard.js +13 -0
  42. package/dist/board/timelineBoard.d.ts +10 -0
  43. package/dist/board/timelineBoard.js +10 -0
  44. package/dist/combat/abilityKit.d.ts +18 -0
  45. package/dist/combat/abilityKit.js +24 -9
  46. package/dist/combat/animationState.d.ts +5 -0
  47. package/dist/combat/animationState.js +5 -0
  48. package/dist/combat/breakMeters.d.ts +10 -0
  49. package/dist/combat/breakMeters.js +10 -0
  50. package/dist/combat/castRunner.d.ts +5 -0
  51. package/dist/combat/castRunner.js +5 -0
  52. package/dist/combat/comboPoints.d.ts +5 -0
  53. package/dist/combat/comboPoints.js +5 -0
  54. package/dist/combat/comboString.d.ts +5 -0
  55. package/dist/combat/comboString.js +5 -0
  56. package/dist/combat/damageInterceptors.d.ts +192 -0
  57. package/dist/combat/damageInterceptors.js +251 -0
  58. package/dist/combat/damageMatchup.d.ts +68 -0
  59. package/dist/combat/damageMatchup.js +42 -0
  60. package/dist/combat/damageResolution.d.ts +63 -0
  61. package/dist/combat/damageResolution.js +50 -0
  62. package/dist/combat/death.d.ts +7 -1
  63. package/dist/combat/death.js +5 -0
  64. package/dist/combat/deathReason.d.ts +14 -0
  65. package/dist/combat/deathReason.js +1 -0
  66. package/dist/combat/defensiveWindow.d.ts +5 -0
  67. package/dist/combat/defensiveWindow.js +5 -0
  68. package/dist/combat/dotField.d.ts +5 -1
  69. package/dist/combat/dotField.js +5 -1
  70. package/dist/combat/downed.d.ts +5 -0
  71. package/dist/combat/downed.js +5 -0
  72. package/dist/combat/effects.d.ts +5 -0
  73. package/dist/combat/effects.js +5 -0
  74. package/dist/combat/encounterSequence.d.ts +258 -0
  75. package/dist/combat/encounterSequence.js +426 -0
  76. package/dist/combat/hitReaction.d.ts +57 -1
  77. package/dist/combat/hitReaction.js +45 -5
  78. package/dist/combat/magazine.d.ts +54 -0
  79. package/dist/combat/magazine.js +96 -0
  80. package/dist/combat/projectiles.d.ts +5 -0
  81. package/dist/combat/projectiles.js +32 -14
  82. package/dist/combat/receivedDamage.d.ts +117 -0
  83. package/dist/combat/receivedDamage.js +92 -0
  84. package/dist/combat/regenShield.d.ts +5 -1
  85. package/dist/combat/regenShield.js +5 -1
  86. package/dist/combat/renderCues.d.ts +51 -0
  87. package/dist/combat/renderCues.js +44 -0
  88. package/dist/combat/resourcePool.d.ts +5 -0
  89. package/dist/combat/resourcePool.js +5 -0
  90. package/dist/combat/rewardAllocation.d.ts +121 -0
  91. package/dist/combat/rewardAllocation.js +251 -0
  92. package/dist/combat/shotOrigin.d.ts +18 -1
  93. package/dist/combat/shotOrigin.js +39 -0
  94. package/dist/combat/statusApplication.d.ts +96 -0
  95. package/dist/combat/statusApplication.js +81 -0
  96. package/dist/combat/telegraph.d.ts +11 -2
  97. package/dist/combat/telegraph.js +11 -2
  98. package/dist/combat/thresholdCrossings.d.ts +85 -0
  99. package/dist/combat/thresholdCrossings.js +116 -0
  100. package/dist/combat.d.ts +35 -0
  101. package/dist/combat.js +35 -0
  102. package/dist/commands/commandRegistry.d.ts +11 -0
  103. package/dist/commands/commandRegistry.js +9 -2
  104. package/dist/crafting/crop.d.ts +18 -0
  105. package/dist/crafting/crop.js +18 -0
  106. package/dist/crafting/production.d.ts +5 -0
  107. package/dist/crafting/production.js +5 -0
  108. package/dist/devtools/controls.d.ts +38 -0
  109. package/dist/devtools/controls.js +181 -0
  110. package/dist/devtools/devtools.d.ts +5 -197
  111. package/dist/devtools/devtools.js +38 -774
  112. package/dist/devtools/discover.d.ts +32 -0
  113. package/dist/devtools/discover.js +240 -0
  114. package/dist/devtools/fallbackSeams.d.ts +50 -0
  115. package/dist/devtools/fallbackSeams.js +111 -0
  116. package/dist/devtools/frame.d.ts +15 -0
  117. package/dist/devtools/frame.js +247 -0
  118. package/dist/devtools/logs.d.ts +15 -0
  119. package/dist/devtools/logs.js +86 -0
  120. package/dist/devtools/overrides.d.ts +16 -0
  121. package/dist/devtools/overrides.js +66 -0
  122. package/dist/devtools/rewriteTunables.d.ts +17 -0
  123. package/dist/devtools/rewriteTunables.js +276 -0
  124. package/dist/devtools/saveEndpoint.d.ts +46 -0
  125. package/dist/devtools/saveEndpoint.js +48 -0
  126. package/dist/devtools/transformTunables.d.ts +3 -0
  127. package/dist/devtools/transformTunables.js +3 -0
  128. package/dist/devtools/tunableSchema.d.ts +31 -0
  129. package/dist/devtools/tunableSchema.js +31 -0
  130. package/dist/devtools/types.d.ts +212 -0
  131. package/dist/devtools/types.js +1 -0
  132. package/dist/economy/auctionBook.d.ts +122 -0
  133. package/dist/economy/auctionBook.js +213 -0
  134. package/dist/economy/currency.d.ts +12 -1
  135. package/dist/economy/currency.js +12 -1
  136. package/dist/economy/listingBook.d.ts +91 -0
  137. package/dist/economy/listingBook.js +144 -0
  138. package/dist/economy/priceHistory.d.ts +44 -0
  139. package/dist/economy/priceHistory.js +89 -0
  140. package/dist/economy/resourceLedger.d.ts +222 -0
  141. package/dist/economy/resourceLedger.js +353 -0
  142. package/dist/economy/sharedWallet.d.ts +14 -0
  143. package/dist/economy/sharedWallet.js +14 -0
  144. package/dist/economy/techTree.d.ts +12 -0
  145. package/dist/economy/techTree.js +12 -0
  146. package/dist/economy/unlockPoints.d.ts +92 -0
  147. package/dist/economy/unlockPoints.js +103 -0
  148. package/dist/economy/wallet.d.ts +26 -2
  149. package/dist/economy/wallet.js +35 -6
  150. package/dist/editor/commandApply.d.ts +5 -0
  151. package/dist/editor/commandApply.js +755 -0
  152. package/dist/editor/commands.d.ts +235 -7
  153. package/dist/editor/commands.js +82 -124
  154. package/dist/editor/directives.d.ts +94 -0
  155. package/dist/editor/directives.js +206 -0
  156. package/dist/editor/document.d.ts +194 -13
  157. package/dist/editor/document.js +1166 -19
  158. package/dist/editor/entityCatalog.d.ts +42 -0
  159. package/dist/editor/entityCatalog.js +101 -0
  160. package/dist/editor/environment.d.ts +74 -0
  161. package/dist/editor/environment.js +169 -0
  162. package/dist/editor/grid.d.ts +229 -0
  163. package/dist/editor/grid.js +366 -0
  164. package/dist/editor/gridAdapters.d.ts +80 -0
  165. package/dist/editor/gridAdapters.js +134 -0
  166. package/dist/editor/index.d.ts +98 -6
  167. package/dist/editor/index.js +13 -2
  168. package/dist/editor/liveSync.d.ts +127 -0
  169. package/dist/editor/liveSync.js +249 -0
  170. package/dist/editor/minimap.d.ts +31 -0
  171. package/dist/editor/minimap.js +73 -0
  172. package/dist/editor/runtimeInspector.d.ts +88 -0
  173. package/dist/editor/runtimeInspector.js +239 -0
  174. package/dist/editor/streamer.d.ts +66 -0
  175. package/dist/editor/streamer.js +68 -0
  176. package/dist/editor/types.d.ts +250 -1
  177. package/dist/editor/world.d.ts +136 -0
  178. package/dist/editor/world.js +303 -0
  179. package/dist/faction/reputation.d.ts +5 -0
  180. package/dist/faction/reputation.js +5 -0
  181. package/dist/format/distance.d.ts +17 -0
  182. package/dist/format/distance.js +18 -0
  183. package/dist/format/duration.d.ts +27 -0
  184. package/dist/format/duration.js +45 -0
  185. package/dist/format/speed.d.ts +17 -0
  186. package/dist/format/speed.js +25 -0
  187. package/dist/game/breeding.d.ts +161 -0
  188. package/dist/game/breeding.js +181 -0
  189. package/dist/game/cameraConfig.d.ts +451 -0
  190. package/dist/game/cameraConfig.js +1 -0
  191. package/dist/game/connectedPlayers.d.ts +6 -4
  192. package/dist/game/connectedPlayers.js +2 -2
  193. package/dist/game/cosmetics.d.ts +9 -0
  194. package/dist/game/cosmetics.js +16 -0
  195. package/dist/game/defineGame.d.ts +110 -5
  196. package/dist/game/defineGame.js +49 -4
  197. package/dist/game/defineSystem.d.ts +90 -0
  198. package/dist/game/defineSystem.js +47 -0
  199. package/dist/game/dialogue.d.ts +33 -0
  200. package/dist/game/dialogue.js +21 -0
  201. package/dist/game/events.d.ts +63 -13
  202. package/dist/game/events.js +5 -0
  203. package/dist/game/feed.d.ts +33 -0
  204. package/dist/game/feed.js +35 -2
  205. package/dist/game/gamePhase.d.ts +11 -0
  206. package/dist/game/gamePhase.js +16 -0
  207. package/dist/game/leaderboard.d.ts +5 -0
  208. package/dist/game/leaderboard.js +5 -0
  209. package/dist/game/loadout.d.ts +5 -0
  210. package/dist/game/loadout.js +5 -0
  211. package/dist/game/lootFilter.d.ts +2 -0
  212. package/dist/game/lootFilter.js +2 -0
  213. package/dist/game/lootPipeline.d.ts +164 -0
  214. package/dist/game/lootPipeline.js +216 -0
  215. package/dist/game/lootTable.d.ts +19 -2
  216. package/dist/game/lootTable.js +15 -5
  217. package/dist/game/objectives.d.ts +43 -0
  218. package/dist/game/objectives.js +36 -0
  219. package/dist/game/paramLayers.d.ts +148 -0
  220. package/dist/game/paramLayers.js +185 -0
  221. package/dist/game/ping.d.ts +5 -0
  222. package/dist/game/ping.js +5 -0
  223. package/dist/game/playableGame.d.ts +116 -432
  224. package/dist/game/playableGame.js +1 -1
  225. package/dist/game/progression.d.ts +35 -0
  226. package/dist/game/progression.js +19 -0
  227. package/dist/game/quest.d.ts +8 -0
  228. package/dist/game/quest.js +30 -3
  229. package/dist/game/race.d.ts +141 -0
  230. package/dist/game/race.js +169 -0
  231. package/dist/game/recordBook.d.ts +3 -1
  232. package/dist/game/recordBook.js +4 -1
  233. package/dist/game/ruleSelection.d.ts +75 -0
  234. package/dist/game/ruleSelection.js +127 -0
  235. package/dist/game/runDraft.d.ts +5 -0
  236. package/dist/game/runDraft.js +5 -0
  237. package/dist/game/saveStore.d.ts +111 -0
  238. package/dist/game/saveStore.js +312 -0
  239. package/dist/game/snapshotHistory.d.ts +67 -0
  240. package/dist/game/snapshotHistory.js +95 -0
  241. package/dist/game/social.d.ts +38 -1
  242. package/dist/game/social.js +71 -0
  243. package/dist/game/spawnPoints.d.ts +5 -0
  244. package/dist/game/spawnPoints.js +5 -0
  245. package/dist/game/systemRuntime.d.ts +50 -0
  246. package/dist/game/systemRuntime.js +157 -0
  247. package/dist/game/systemSchedule.d.ts +72 -0
  248. package/dist/game/systemSchedule.js +242 -0
  249. package/dist/game/toasts.d.ts +43 -0
  250. package/dist/game/toasts.js +44 -0
  251. package/dist/game/trade.d.ts +10 -0
  252. package/dist/game/trade.js +10 -0
  253. package/dist/game/tribe.d.ts +184 -0
  254. package/dist/game/tribe.js +432 -0
  255. package/dist/game/unlocks.d.ts +8 -0
  256. package/dist/game/unlocks.js +16 -0
  257. package/dist/game/vfxInstance.d.ts +138 -0
  258. package/dist/game/vfxInstance.js +96 -0
  259. package/dist/game/worldItem.d.ts +17 -4
  260. package/dist/game/worldItem.js +17 -4
  261. package/dist/gameplay.d.ts +93 -0
  262. package/dist/gameplay.js +93 -0
  263. package/dist/index.d.ts +5 -14
  264. package/dist/index.js +5 -14
  265. package/dist/input/actionBindings.d.ts +23 -6
  266. package/dist/input/actionBindings.js +23 -6
  267. package/dist/input/axisInput.d.ts +7 -2
  268. package/dist/input/axisInput.js +7 -2
  269. package/dist/input/controlGroups.d.ts +67 -0
  270. package/dist/input/controlGroups.js +41 -0
  271. package/dist/input/touchScheme.d.ts +82 -6
  272. package/dist/input/touchScheme.js +123 -10
  273. package/dist/interaction/lockpick.d.ts +61 -0
  274. package/dist/interaction/lockpick.js +168 -0
  275. package/dist/interaction/proximityPrompt.d.ts +2 -0
  276. package/dist/interaction/proximityPrompt.js +2 -0
  277. package/dist/interaction/qte.d.ts +5 -0
  278. package/dist/interaction/qte.js +5 -0
  279. package/dist/interaction/skillCheck.d.ts +5 -0
  280. package/dist/interaction/skillCheck.js +5 -0
  281. package/dist/inventory/encumbrance.d.ts +69 -0
  282. package/dist/inventory/encumbrance.js +68 -0
  283. package/dist/inventory/inventoryModel.d.ts +12 -0
  284. package/dist/inventory/inventoryModel.js +12 -0
  285. package/dist/inventory/shapedGrid.d.ts +16 -0
  286. package/dist/inventory/shapedGrid.js +16 -0
  287. package/dist/inventory/slotModel.d.ts +10 -0
  288. package/dist/inventory/slotModel.js +10 -0
  289. package/dist/item/durability.d.ts +5 -0
  290. package/dist/item/durability.js +6 -3
  291. package/dist/item/itemIdentity.d.ts +171 -0
  292. package/dist/item/itemIdentity.js +138 -0
  293. package/dist/item/itemInstanceRegistry.d.ts +32 -0
  294. package/dist/item/itemInstanceRegistry.js +34 -0
  295. package/dist/item/itemgen.d.ts +163 -0
  296. package/dist/item/itemgen.js +151 -0
  297. package/dist/item/modularItem.d.ts +5 -0
  298. package/dist/item/modularItem.js +5 -0
  299. package/dist/item/use.d.ts +5 -0
  300. package/dist/item/use.js +5 -0
  301. package/dist/item/useBehavior.d.ts +142 -0
  302. package/dist/item/useBehavior.js +105 -0
  303. package/dist/item/weapon.d.ts +5 -0
  304. package/dist/item/weapon.js +5 -0
  305. package/dist/math/scalar.d.ts +25 -0
  306. package/dist/math/scalar.js +47 -0
  307. package/dist/meta/changelog.d.ts +4 -1
  308. package/dist/meta/changelog.js +39 -2
  309. package/dist/movement/avoidance.d.ts +45 -0
  310. package/dist/movement/avoidance.js +123 -0
  311. package/dist/movement/cameraRig.d.ts +8 -0
  312. package/dist/movement/cameraRig.js +8 -0
  313. package/dist/movement/dash.d.ts +11 -0
  314. package/dist/movement/dash.js +11 -0
  315. package/dist/movement/formation.d.ts +135 -0
  316. package/dist/movement/formation.js +194 -0
  317. package/dist/movement/glideModel.d.ts +5 -0
  318. package/dist/movement/glideModel.js +5 -0
  319. package/dist/movement/grappleSwing.d.ts +5 -0
  320. package/dist/movement/grappleSwing.js +5 -0
  321. package/dist/movement/leaderTrail.d.ts +5 -0
  322. package/dist/movement/leaderTrail.js +5 -0
  323. package/dist/movement/movementModel.d.ts +50 -9
  324. package/dist/movement/movementModel.js +75 -29
  325. package/dist/movement/playerMovement.js +167 -12
  326. package/dist/movement/poseState.d.ts +13 -0
  327. package/dist/movement/poseState.js +16 -0
  328. package/dist/movement/steering.d.ts +8 -0
  329. package/dist/movement/steering.js +18 -0
  330. package/dist/multiplayer/combatSnapshot.d.ts +3 -0
  331. package/dist/multiplayer/combatSnapshot.js +16 -8
  332. package/dist/multiplayer/lagCompensation.d.ts +5 -0
  333. package/dist/multiplayer/lagCompensation.js +5 -0
  334. package/dist/multiplayer/presenceModel.d.ts +11 -1
  335. package/dist/multiplayer/presenceModel.js +17 -1
  336. package/dist/multiplayer/simultaneousCommit.d.ts +3 -0
  337. package/dist/multiplayer/simultaneousCommit.js +3 -0
  338. package/dist/multiplayer.d.ts +11 -0
  339. package/dist/multiplayer.js +11 -0
  340. package/dist/nav/pathFollow.d.ts +57 -0
  341. package/dist/nav/pathFollow.js +109 -0
  342. package/dist/orders/orderKinds.d.ts +156 -0
  343. package/dist/orders/orderKinds.js +197 -0
  344. package/dist/orders/orderQueue.d.ts +208 -0
  345. package/dist/orders/orderQueue.js +211 -0
  346. package/dist/physics/drivableVehicle.d.ts +31 -0
  347. package/dist/physics/drivableVehicle.js +16 -0
  348. package/dist/physics/flightDynamics.d.ts +82 -0
  349. package/dist/physics/flightDynamics.js +165 -0
  350. package/dist/physics/gravityField.d.ts +25 -0
  351. package/dist/physics/gravityField.js +41 -0
  352. package/dist/physics/kinematicVehicle.d.ts +108 -1
  353. package/dist/physics/kinematicVehicle.js +212 -15
  354. package/dist/physics/physicsWorld.d.ts +50 -12
  355. package/dist/physics/physicsWorld.js +120 -122
  356. package/dist/physics/radialImpulse.d.ts +25 -0
  357. package/dist/physics/radialImpulse.js +27 -0
  358. package/dist/physics/vehicleObstacles.d.ts +44 -0
  359. package/dist/physics/vehicleObstacles.js +115 -0
  360. package/dist/procedural.d.ts +3 -0
  361. package/dist/procedural.js +3 -0
  362. package/dist/progression/statGraph.d.ts +116 -0
  363. package/dist/progression/statGraph.js +318 -0
  364. package/dist/puzzle/cellGrid.d.ts +9 -0
  365. package/dist/puzzle/cellGrid.js +9 -0
  366. package/dist/puzzle/fallingPiece.d.ts +9 -0
  367. package/dist/puzzle/fallingPiece.js +9 -0
  368. package/dist/puzzle/nonogram.d.ts +39 -0
  369. package/dist/puzzle/nonogram.js +136 -0
  370. package/dist/random/nameGen.d.ts +5 -0
  371. package/dist/random/nameGen.js +5 -0
  372. package/dist/random/pick.d.ts +8 -0
  373. package/dist/random/pick.js +31 -0
  374. package/dist/random/rng.d.ts +26 -1
  375. package/dist/random/rng.js +32 -9
  376. package/dist/random/seedLink.d.ts +4 -0
  377. package/dist/random/seedLink.js +4 -0
  378. package/dist/relation/keyedValues.d.ts +54 -0
  379. package/dist/relation/keyedValues.js +115 -0
  380. package/dist/relation/thresholds.d.ts +62 -0
  381. package/dist/relation/thresholds.js +62 -0
  382. package/dist/render/color.d.ts +12 -0
  383. package/dist/render/color.js +26 -0
  384. package/dist/render/lookPreset.d.ts +41 -0
  385. package/dist/render/lookPreset.js +29 -0
  386. package/dist/render/postProcessing.d.ts +20 -0
  387. package/dist/render/postProcessing.js +21 -1
  388. package/dist/rules/predicate.d.ts +53 -0
  389. package/dist/rules/predicate.js +64 -0
  390. package/dist/rules/ruleEffects.d.ts +38 -0
  391. package/dist/rules/ruleEffects.js +37 -0
  392. package/dist/rules/triggeredRules.d.ts +160 -0
  393. package/dist/rules/triggeredRules.js +241 -0
  394. package/dist/runtime/adapter.d.ts +55 -3
  395. package/dist/runtime/adapter.js +57 -1
  396. package/dist/runtime/cameraDirector.d.ts +1 -0
  397. package/dist/runtime/cameraDirector.js +1 -0
  398. package/dist/runtime/commandRunner.d.ts +1 -0
  399. package/dist/runtime/commandRunner.js +1 -0
  400. package/dist/runtime/context/combat.d.ts +51 -0
  401. package/dist/runtime/context/combat.js +130 -0
  402. package/dist/runtime/context/combatFx.d.ts +23 -0
  403. package/dist/runtime/context/combatFx.js +161 -0
  404. package/dist/runtime/context/deathLoot.d.ts +30 -0
  405. package/dist/runtime/context/deathLoot.js +39 -0
  406. package/dist/runtime/context/player.d.ts +48 -0
  407. package/dist/runtime/context/player.js +125 -0
  408. package/dist/runtime/context/registries.d.ts +13 -0
  409. package/dist/runtime/context/registries.js +84 -0
  410. package/dist/runtime/context/scene.d.ts +59 -0
  411. package/dist/runtime/context/scene.js +291 -0
  412. package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
  413. package/dist/runtime/context/snapshotCodecs.js +26 -0
  414. package/dist/runtime/context/worldItems.d.ts +21 -0
  415. package/dist/runtime/context/worldItems.js +37 -0
  416. package/dist/runtime/descriptors/baseline.d.ts +48 -0
  417. package/dist/runtime/descriptors/baseline.js +157 -0
  418. package/dist/runtime/descriptors/features.d.ts +79 -0
  419. package/dist/runtime/descriptors/features.js +299 -0
  420. package/dist/runtime/gameContext.d.ts +2 -327
  421. package/dist/runtime/gameContext.js +273 -778
  422. package/dist/runtime/gameContextTypes.d.ts +467 -0
  423. package/dist/runtime/gameContextTypes.js +1 -0
  424. package/dist/runtime/gameRuntime.d.ts +1 -0
  425. package/dist/runtime/gameRuntime.js +1 -0
  426. package/dist/runtime/headlessRunner.d.ts +62 -0
  427. package/dist/runtime/headlessRunner.js +45 -0
  428. package/dist/runtime/hostPersistence.d.ts +12 -0
  429. package/dist/runtime/hostPersistence.js +12 -0
  430. package/dist/runtime/hostPolicy.d.ts +51 -0
  431. package/dist/runtime/hostPolicy.js +68 -0
  432. package/dist/runtime/hostedGameRunner.d.ts +11 -4
  433. package/dist/runtime/hostedGameRunner.js +27 -3
  434. package/dist/runtime/hostedWorldSession.d.ts +15 -5
  435. package/dist/runtime/hostedWorldSession.js +12 -4
  436. package/dist/runtime/inputSnapshot.d.ts +8 -3
  437. package/dist/runtime/inputSnapshot.js +6 -2
  438. package/dist/runtime/motionIntents.d.ts +11 -2
  439. package/dist/runtime/motionIntents.js +16 -2
  440. package/dist/runtime/perContext.d.ts +15 -0
  441. package/dist/runtime/perContext.js +21 -0
  442. package/dist/runtime/runtimeSave.d.ts +79 -0
  443. package/dist/runtime/runtimeSave.js +105 -0
  444. package/dist/runtime/save.d.ts +4 -0
  445. package/dist/runtime/save.js +4 -0
  446. package/dist/runtime/snapshot.d.ts +7 -0
  447. package/dist/runtime/snapshot.js +7 -0
  448. package/dist/runtime/visibility.d.ts +2 -1
  449. package/dist/runtime/visibility.js +2 -1
  450. package/dist/runtime/worldChannel.d.ts +7 -3
  451. package/dist/runtime/worldChannel.js +51 -6
  452. package/dist/runtime/worldMirror.d.ts +14 -2
  453. package/dist/runtime/worldMirror.js +15 -2
  454. package/dist/runtime/worldProjection.d.ts +47 -0
  455. package/dist/runtime/worldProjection.js +52 -0
  456. package/dist/runtime/worldReplication.d.ts +30 -7
  457. package/dist/runtime/worldReplication.js +41 -9
  458. package/dist/runtime/worldSnapshot.d.ts +42 -3
  459. package/dist/runtime/worldSnapshot.js +34 -7
  460. package/dist/scene/assetCatalog.d.ts +26 -0
  461. package/dist/scene/assetCatalog.js +4 -0
  462. package/dist/scene/assetDiagnostics.d.ts +69 -0
  463. package/dist/scene/assetDiagnostics.js +110 -0
  464. package/dist/scene/assetGenerator.d.ts +65 -0
  465. package/dist/scene/assetGenerator.js +64 -0
  466. package/dist/scene/assetSpace.d.ts +137 -0
  467. package/dist/scene/assetSpace.js +257 -0
  468. package/dist/scene/authoredTriggers.d.ts +219 -0
  469. package/dist/scene/authoredTriggers.js +454 -0
  470. package/dist/scene/behaviorRuntime.d.ts +75 -0
  471. package/dist/scene/behaviorRuntime.js +217 -0
  472. package/dist/scene/behaviors.d.ts +14 -2
  473. package/dist/scene/behaviors.js +7 -2
  474. package/dist/scene/bodyBind.d.ts +60 -0
  475. package/dist/scene/bodyBind.js +51 -0
  476. package/dist/scene/builtinSceneKinds.d.ts +5 -0
  477. package/dist/scene/builtinSceneKinds.js +67 -0
  478. package/dist/scene/colliders.d.ts +71 -1
  479. package/dist/scene/colliders.js +145 -1
  480. package/dist/scene/collisionMesh.d.ts +94 -0
  481. package/dist/scene/collisionMesh.js +699 -0
  482. package/dist/scene/companion.d.ts +147 -0
  483. package/dist/scene/companion.js +193 -0
  484. package/dist/scene/entityStats.d.ts +18 -11
  485. package/dist/scene/entityStats.js +21 -16
  486. package/dist/scene/entityStore.d.ts +12 -1
  487. package/dist/scene/entityStore.js +15 -0
  488. package/dist/scene/facing.d.ts +18 -0
  489. package/dist/scene/facing.js +26 -0
  490. package/dist/scene/modelSockets.d.ts +33 -0
  491. package/dist/scene/modelSockets.js +29 -0
  492. package/dist/scene/objectQuery.d.ts +2 -0
  493. package/dist/scene/objectQuery.js +2 -0
  494. package/dist/scene/objectStore.d.ts +4 -0
  495. package/dist/scene/objectStore.js +6 -0
  496. package/dist/scene/possession.d.ts +8 -0
  497. package/dist/scene/possession.js +14 -0
  498. package/dist/scene/roster.d.ts +3 -0
  499. package/dist/scene/roster.js +12 -0
  500. package/dist/scene/sceneKinds.d.ts +256 -0
  501. package/dist/scene/sceneKinds.js +218 -0
  502. package/dist/scene/sceneOwnership.d.ts +169 -0
  503. package/dist/scene/sceneOwnership.js +179 -0
  504. package/dist/scene/sceneRaycast.js +15 -0
  505. package/dist/scene/selectionBookmarks.d.ts +93 -0
  506. package/dist/scene/selectionBookmarks.js +122 -0
  507. package/dist/scene/vehicleSeat.d.ts +68 -0
  508. package/dist/scene/vehicleSeat.js +59 -0
  509. package/dist/sensor/concealment.d.ts +3 -0
  510. package/dist/sensor/concealment.js +3 -0
  511. package/dist/sensor/freezeMonitor.d.ts +1 -0
  512. package/dist/sensor/freezeMonitor.js +1 -0
  513. package/dist/sensor/frustumSensor.d.ts +7 -3
  514. package/dist/sensor/frustumSensor.js +7 -3
  515. package/dist/sensor/hiddenStateProbe.d.ts +5 -2
  516. package/dist/sensor/hiddenStateProbe.js +5 -2
  517. package/dist/sensor/recordingBuffer.d.ts +2 -1
  518. package/dist/sensor/recordingBuffer.js +2 -1
  519. package/dist/sensor/replayLoop.d.ts +6 -2
  520. package/dist/sensor/replayLoop.js +6 -2
  521. package/dist/sensor/revealQuery.d.ts +2 -1
  522. package/dist/sensor/revealQuery.js +2 -1
  523. package/dist/sensor/visionCone.d.ts +10 -3
  524. package/dist/sensor/visionCone.js +10 -3
  525. package/dist/session/contestedChannel.d.ts +1 -0
  526. package/dist/session/contestedChannel.js +1 -0
  527. package/dist/session/extraction.d.ts +1 -0
  528. package/dist/session/extraction.js +1 -0
  529. package/dist/session/roles.d.ts +6 -0
  530. package/dist/session/roles.js +6 -0
  531. package/dist/session/roundState.d.ts +8 -0
  532. package/dist/session/roundState.js +2 -0
  533. package/dist/settings/settingsModel.d.ts +1 -0
  534. package/dist/settings/settingsModel.js +1 -0
  535. package/dist/stats/accumulatorMeter.d.ts +6 -0
  536. package/dist/stats/accumulatorMeter.js +6 -0
  537. package/dist/stats/eventMeter.d.ts +5 -0
  538. package/dist/stats/eventMeter.js +5 -0
  539. package/dist/stats/rollCheck.d.ts +5 -0
  540. package/dist/stats/rollCheck.js +5 -0
  541. package/dist/stats/spawnLevelStats.d.ts +110 -0
  542. package/dist/stats/spawnLevelStats.js +140 -0
  543. package/dist/stats/statModifiers.d.ts +5 -0
  544. package/dist/stats/statModifiers.js +5 -0
  545. package/dist/stats/statPool.d.ts +70 -0
  546. package/dist/stats/statPool.js +59 -0
  547. package/dist/store/changeSignal.d.ts +3 -0
  548. package/dist/store/changeSignal.js +2 -0
  549. package/dist/store/defineKeyedStore.d.ts +38 -0
  550. package/dist/store/defineKeyedStore.js +42 -0
  551. package/dist/store/defineStore.d.ts +33 -0
  552. package/dist/store/defineStore.js +35 -0
  553. package/dist/store/observableKeyedStore.d.ts +7 -0
  554. package/dist/store/observableKeyedStore.js +35 -2
  555. package/dist/survival/decayMeter.d.ts +55 -0
  556. package/dist/survival/decayMeter.js +130 -57
  557. package/dist/survival/moodle.d.ts +2 -0
  558. package/dist/survival/regionHealth.d.ts +6 -0
  559. package/dist/survival/regionHealth.js +6 -3
  560. package/dist/tactics/fallingGrid.d.ts +4 -2
  561. package/dist/tactics/fallingGrid.js +4 -2
  562. package/dist/tactics/predictiveQuery.d.ts +3 -0
  563. package/dist/tactics/predictiveQuery.js +3 -0
  564. package/dist/tactics/snapshot.d.ts +2 -0
  565. package/dist/tactics/snapshot.js +2 -0
  566. package/dist/tactics/surface.d.ts +1 -0
  567. package/dist/tactics/surface.js +1 -0
  568. package/dist/tactics/tacticalGrid.d.ts +1 -0
  569. package/dist/tactics/tacticalGrid.js +1 -0
  570. package/dist/time/beatClock.d.ts +14 -4
  571. package/dist/time/beatClock.js +14 -4
  572. package/dist/time/calendarClock.d.ts +3 -0
  573. package/dist/time/calendarClock.js +3 -0
  574. package/dist/time/idleProgress.d.ts +4 -0
  575. package/dist/time/idleProgress.js +4 -0
  576. package/dist/time/simClock.d.ts +2 -0
  577. package/dist/time/simClock.js +20 -5
  578. package/dist/ui/actionModel.d.ts +110 -0
  579. package/dist/ui/actionModel.js +159 -0
  580. package/dist/ui/gameLayout.d.ts +30 -11
  581. package/dist/ui/gameLayout.js +30 -11
  582. package/dist/ui/hudDocument.d.ts +114 -0
  583. package/dist/ui/hudDocument.js +220 -0
  584. package/dist/ui/hudLayout.d.ts +38 -1
  585. package/dist/ui/hudLayout.js +220 -17
  586. package/dist/ui/selectionModel.d.ts +92 -0
  587. package/dist/ui/selectionModel.js +88 -0
  588. package/dist/ui/tooltipModel.d.ts +63 -0
  589. package/dist/ui/tooltipModel.js +82 -0
  590. package/dist/ui.d.ts +15 -0
  591. package/dist/ui.js +15 -0
  592. package/dist/visibility/bounds.d.ts +8 -1
  593. package/dist/visibility/bounds.js +8 -1
  594. package/dist/visibility/frustum.d.ts +12 -3
  595. package/dist/visibility/frustum.js +12 -3
  596. package/dist/work/jobQueue.d.ts +201 -0
  597. package/dist/work/jobQueue.js +253 -0
  598. package/dist/work/unitTraining.d.ts +58 -0
  599. package/dist/work/unitTraining.js +52 -0
  600. package/dist/world/authoredEntities.d.ts +29 -0
  601. package/dist/world/authoredEntities.js +29 -0
  602. package/dist/world/authoredObjects.d.ts +72 -0
  603. package/dist/world/authoredObjects.js +81 -0
  604. package/dist/world/authoredSpawn.d.ts +39 -0
  605. package/dist/world/authoredSpawn.js +37 -0
  606. package/dist/world/buildSockets.d.ts +152 -0
  607. package/dist/world/buildSockets.js +188 -0
  608. package/dist/world/buildingGenerator.d.ts +18 -0
  609. package/dist/world/buildingGenerator.js +62 -0
  610. package/dist/world/buildings.d.ts +5 -0
  611. package/dist/world/buildings.js +5 -0
  612. package/dist/world/catenary.d.ts +22 -0
  613. package/dist/world/catenary.js +69 -0
  614. package/dist/world/cityBlocks.d.ts +152 -0
  615. package/dist/world/cityBlocks.js +670 -0
  616. package/dist/world/cityContent.d.ts +77 -0
  617. package/dist/world/cityContent.js +261 -0
  618. package/dist/world/cityGeometry.d.ts +114 -0
  619. package/dist/world/cityGeometry.js +573 -0
  620. package/dist/world/cityKind.d.ts +314 -0
  621. package/dist/world/cityKind.js +1578 -0
  622. package/dist/world/features.d.ts +133 -10
  623. package/dist/world/features.js +66 -6
  624. package/dist/world/footprintGrid.d.ts +59 -0
  625. package/dist/world/footprintGrid.js +141 -0
  626. package/dist/world/geometry.d.ts +64 -0
  627. package/dist/world/geometry.js +94 -0
  628. package/dist/world/grassKind.d.ts +51 -0
  629. package/dist/world/grassKind.js +101 -0
  630. package/dist/world/markers.d.ts +50 -4
  631. package/dist/world/markers.js +49 -0
  632. package/dist/world/massing.js +1 -1
  633. package/dist/world/minimapBake.d.ts +78 -0
  634. package/dist/world/minimapBake.js +208 -0
  635. package/dist/world/minimapTrack.d.ts +17 -0
  636. package/dist/world/minimapTrack.js +22 -0
  637. package/dist/world/pathInstances.d.ts +27 -0
  638. package/dist/world/pathInstances.js +27 -0
  639. package/dist/world/pathNetwork.d.ts +159 -0
  640. package/dist/world/pathNetwork.js +684 -0
  641. package/dist/world/pathTerrain.d.ts +80 -0
  642. package/dist/world/pathTerrain.js +142 -0
  643. package/dist/world/place.d.ts +166 -0
  644. package/dist/world/place.js +96 -0
  645. package/dist/world/placeAsset.d.ts +81 -0
  646. package/dist/world/placeAsset.js +79 -0
  647. package/dist/world/placement.d.ts +4 -0
  648. package/dist/world/placement.js +4 -0
  649. package/dist/world/placementController.d.ts +6 -0
  650. package/dist/world/placementController.js +6 -0
  651. package/dist/world/poleLineKind.d.ts +40 -0
  652. package/dist/world/poleLineKind.js +97 -0
  653. package/dist/world/polyline.d.ts +34 -0
  654. package/dist/world/polyline.js +88 -0
  655. package/dist/world/resourceNode.d.ts +109 -0
  656. package/dist/world/resourceNode.js +140 -0
  657. package/dist/world/roads.d.ts +8 -0
  658. package/dist/world/roads.js +38 -0
  659. package/dist/world/scatter.js +2 -20
  660. package/dist/world/scatterRegion.d.ts +164 -0
  661. package/dist/world/scatterRegion.js +409 -0
  662. package/dist/world/sceneShapes.d.ts +43 -0
  663. package/dist/world/sceneShapes.js +1 -0
  664. package/dist/world/segment.d.ts +27 -0
  665. package/dist/world/segment.js +43 -0
  666. package/dist/world/soilKind.d.ts +48 -0
  667. package/dist/world/soilKind.js +92 -0
  668. package/dist/world/spatialSample.d.ts +241 -0
  669. package/dist/world/spatialSample.js +465 -0
  670. package/dist/world/streets.d.ts +29 -0
  671. package/dist/world/streets.js +55 -0
  672. package/dist/world/terraform.d.ts +213 -1
  673. package/dist/world/terraform.js +627 -22
  674. package/dist/world/terrain.d.ts +164 -20
  675. package/dist/world/terrain.js +325 -41
  676. package/dist/world/terrainGuides.d.ts +182 -0
  677. package/dist/world/terrainGuides.js +343 -0
  678. package/dist/world/vec2.d.ts +81 -0
  679. package/dist/world/vec2.js +128 -0
  680. package/dist/world/vec3.d.ts +49 -0
  681. package/dist/world/vec3.js +76 -0
  682. package/dist/world/vegetation.d.ts +71 -0
  683. package/dist/world/vegetation.js +151 -0
  684. package/dist/world/visibilityField.d.ts +154 -0
  685. package/dist/world/visibilityField.js +271 -0
  686. package/dist/world/volumetricClouds.d.ts +52 -0
  687. package/dist/world/volumetricClouds.js +43 -0
  688. package/dist/world/walls.d.ts +9 -0
  689. package/dist/world/walls.js +9 -0
  690. package/dist/world/waterKind.d.ts +55 -0
  691. package/dist/world/waterKind.js +99 -0
  692. package/dist/world.d.ts +141 -0
  693. package/dist/world.js +141 -0
  694. package/package.json +32 -8
  695. package/skills/game-design/SKILL.md +28 -0
  696. package/skills/game-design/agents/openai.yaml +4 -0
  697. package/skills/game-design/references/application-playbook.md +170 -0
  698. package/skills/game-design/references/field-guide.md +288 -0
  699. package/skills/jgengine/SKILL.md +54 -0
  700. package/skills/jgengine/api.md +527 -0
  701. package/skills/jgengine/capabilities.md +131 -0
  702. package/skills/jgengine/recipes/minimal-game.md +88 -0
  703. package/skills/jgengine-combat/SKILL.md +38 -0
  704. package/skills/jgengine-combat/api.md +585 -0
  705. package/skills/jgengine-combat/capabilities.md +213 -0
  706. package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
  707. package/skills/jgengine-combat/reference.md +108 -0
  708. package/skills/jgengine-gameplay/SKILL.md +39 -0
  709. package/skills/jgengine-gameplay/api.md +1920 -0
  710. package/skills/jgengine-gameplay/capabilities.md +454 -0
  711. package/skills/jgengine-gameplay/reference-systems.md +56 -0
  712. package/skills/jgengine-world/SKILL.md +51 -0
  713. package/skills/jgengine-world/api.md +3056 -0
  714. package/skills/jgengine-world/capabilities.md +476 -0
  715. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  716. package/skills/jgengine-world/reference.md +261 -0
  717. package/dist/cartridge/runtime.d.ts +0 -15
  718. package/dist/cartridge/runtime.js +0 -556
  719. package/dist/cartridge/spec.d.ts +0 -188
  720. package/dist/cartridge/spec.js +0 -19
  721. package/dist/cartridge/validate.d.ts +0 -2
  722. package/dist/cartridge/validate.js +0 -78
  723. package/dist/store/engineStore.d.ts +0 -22
  724. package/dist/store/engineStore.js +0 -50
  725. package/dist/store/features.d.ts +0 -10
  726. package/dist/store/features.js +0 -23
  727. package/llms.txt +0 -5389
@@ -1,50 +1,32 @@
1
- import { createCardPile } from "../cards/cardPile.js";
2
- import { createDeathSystem, deathReasonFromEffect, normalizeOnDeath } from "../combat/death.js";
3
- import { createEffectSystem, } from "../combat/effects.js";
4
- import { createProjectileSystem } from "../combat/projectiles.js";
5
- import { resolveHitReaction, } from "../combat/hitReaction.js";
6
- import { pointInTelegraph, } from "../combat/telegraph.js";
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";
1
+ import { createCommandRegistry } from "../commands/commandRegistry.js";
2
+ import {} from "../game/cosmetics.js";
10
3
  import { groundFieldFor } from "../world/terrain.js";
11
4
  import { createGameEvents } from "../game/events.js";
12
5
  import { createGameFeed } from "../game/feed.js";
13
- import { createLeaderboard } from "../game/leaderboard.js";
14
- import { createLoadouts } from "../game/loadout.js";
6
+ import { setGamePhase } from "../game/gamePhase.js";
15
7
  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";
19
8
  import { createSocial } from "../game/social.js";
20
- import { createTradeSystem } from "../game/trade.js";
21
- import { createUnlocks } from "../game/unlocks.js";
22
- import { createInventorySet, putItem, } from "../inventory/inventoryModel.js";
23
- import { createItemUse, } from "../item/use.js";
9
+ import { createItemUse } from "../item/use.js";
24
10
  import { createWeaponStats } from "../item/weapon.js";
25
- import { createPoseState } from "../movement/poseState.js";
26
- import { createPaintLayer } from "../scene/paintLayer.js";
27
- import { createEntityStatsApi, hydrateEntityStats, seedStatValues, setStatValue, snapshotEntityStats, } from "../scene/entityStats.js";
28
- import { createForms } from "../scene/form.js";
29
- import { scaledObjectColliders } from "../scene/colliders.js";
30
- import { raycastObjects, raycastObjectsAll } from "../scene/objectQuery.js";
31
- import { createObjectStore, objectVisualScale } from "../scene/objectStore.js";
32
- import { createRoster } from "../scene/roster.js";
33
- import { createConnectedPlayers } from "../game/connectedPlayers.js";
34
- import { createPossession } from "../scene/possession.js";
35
- import { createSceneRaycast, } from "../scene/sceneRaycast.js";
36
- import { createSpatialApi } from "../scene/spatial.js";
37
- import { createTargeting } from "../scene/targeting.js";
38
- import { createStats } from "../stats/statModifiers.js";
11
+ import {} from "../scene/entityStats.js";
12
+ import {} from "../scene/entityStore.js";
39
13
  import { createChangeSignal, notifyAfter } from "../store/changeSignal.js";
40
14
  import { createObservableKeyedStore } from "../store/observableKeyedStore.js";
41
15
  import { applyWorldSnapshot, composeWorldSnapshot, } from "./worldSnapshot.js";
16
+ import { policyProjectsViewers, projectByVisibleIds, projectEntitiesForViewer, projectPerUserForViewer, visibleEntityIds, } from "./worldProjection.js";
17
+ import { createRuntimeSave } from "./runtimeSave.js";
18
+ import { isOffline } from "./adapter.js";
19
+ import { localSaveBackend, memorySaveBackend } from "../game/saveStore.js";
42
20
  import { createSimClock } from "../time/simClock.js";
43
- import { createTurnLoop } from "../turn/turnLoop.js";
44
- import { RaceState } from "../game/race.js";
45
21
  import { createCameraDirector } from "./cameraDirector.js";
46
22
  import { createInputSnapshot } from "./inputSnapshot.js";
47
- import { createMotionIntents } from "./motionIntents.js";
23
+ import { baselineDescriptors } from "./descriptors/baseline.js";
24
+ import { featureDescriptors } from "./descriptors/features.js";
25
+ import { createCombatSubsystem } from "./context/combat.js";
26
+ import { createContextRegistries } from "./context/registries.js";
27
+ import { createPlayerSubsystem } from "./context/player.js";
28
+ import { createSceneSubsystem } from "./context/scene.js";
29
+ import { createWorldItemContext } from "./context/worldItems.js";
48
30
  export function createGameContext(options) {
49
31
  const { definition, content, player } = options;
50
32
  const now = options.now ?? Date.now;
@@ -54,122 +36,6 @@ export function createGameContext(options) {
54
36
  const activeUserId = () => actingUserId ?? player.userId;
55
37
  const time = createSimClock({ config: definition.time, onChange: signal.notify });
56
38
  const ground = groundFieldFor(definition.world);
57
- const entities = definition.scene;
58
- const objects = createObjectStore();
59
- entities.subscribe(signal.notify);
60
- objects.subscribe(signal.notify);
61
- const statsByInstance = new Map();
62
- const entityStats = notifyAfter(createEntityStatsApi((instanceId) => statsByInstance.get(instanceId)), ["set", "delta"], signal.notify);
63
- function ensureInstanceStats(instanceId) {
64
- let map = statsByInstance.get(instanceId);
65
- if (map === undefined) {
66
- map = {};
67
- statsByInstance.set(instanceId, map);
68
- }
69
- return map;
70
- }
71
- function catalogEntry(instanceId) {
72
- const entity = entities.get(instanceId);
73
- return entity === null ? undefined : content.entityById?.(entity.name);
74
- }
75
- function catalogObject(instanceId) {
76
- const object = objects.get(instanceId);
77
- return object === null ? undefined : content.objectById?.(object.catalogId);
78
- }
79
- let spatialGeneration = 0;
80
- const candidateIds = [];
81
- let candidateIdsDirty = true;
82
- function refreshCandidateIds() {
83
- if (!candidateIdsDirty)
84
- return candidateIds;
85
- candidateIds.length = 0;
86
- for (const entity of entities.list())
87
- candidateIds.push(entity.id);
88
- candidateIdsDirty = false;
89
- return candidateIds;
90
- }
91
- const spatial = createSpatialApi({
92
- resolvePosition: (instanceId) => entities.get(instanceId)?.position,
93
- candidates: refreshCandidateIds,
94
- grid: { cellSize: 8 },
95
- getVersion: () => spatialGeneration,
96
- ...(occluder !== undefined ? { occluder } : {}),
97
- });
98
- entities.subscribe(() => {
99
- candidateIdsDirty = true;
100
- spatialGeneration += 1;
101
- spatial.invalidate();
102
- });
103
- const entityColliders = new Map();
104
- const objectColliders = new Map();
105
- function entityCollidersOf(instanceId) {
106
- const override = entityColliders.get(instanceId);
107
- if (override !== undefined)
108
- return override;
109
- return catalogEntry(instanceId)?.colliders ?? null;
110
- }
111
- function objectCollidersOf(instanceId) {
112
- const override = objectColliders.get(instanceId);
113
- if (override !== undefined)
114
- return override;
115
- const catalog = catalogObject(instanceId);
116
- if (catalog?.colliders !== undefined)
117
- return catalog.colliders;
118
- if (catalog?.halfExtents !== undefined)
119
- return null;
120
- const object = objects.get(instanceId);
121
- if (object === null || object.visual?.scale === undefined)
122
- return null;
123
- return scaledObjectColliders(objectVisualScale(object.visual));
124
- }
125
- const targeting = notifyAfter(createTargeting({
126
- candidates: () => [...refreshCandidateIds()],
127
- classify(_fromId, toId) {
128
- const role = catalogEntry(toId)?.role;
129
- return role === "enemy" || role === "hostile" ? "hostile" : "friendly";
130
- },
131
- distance: (fromId, toId) => spatial.distance(fromId, toId),
132
- }), ["setTarget", "cycleTarget"], signal.notify);
133
- const combatSpatial = {
134
- inRadius: (center, radius) => spatial.inRadius(center, radius),
135
- hasLineOfSight: (from, to) => {
136
- if (typeof from === "string")
137
- return spatial.hasLineOfSight(from, to);
138
- const toPos = entities.get(to)?.position;
139
- if (toPos === undefined)
140
- return false;
141
- if (occluder === undefined)
142
- return true;
143
- return !occluder(from, toPos);
144
- },
145
- positionOf: (instanceId) => entities.get(instanceId)?.position,
146
- };
147
- const sceneRaycast = createSceneRaycast({
148
- entities: {
149
- list: () => entities.list().map((entity) => ({
150
- id: entity.id,
151
- position: entity.position,
152
- rotationY: entity.rotationY,
153
- name: entity.name,
154
- })),
155
- collidersOf: entityCollidersOf,
156
- inRadius: (center, radius) => spatial.inRadius(center, radius),
157
- get: (id) => {
158
- const entity = entities.get(id);
159
- if (entity === null)
160
- return null;
161
- return { id: entity.id, position: entity.position, rotationY: entity.rotationY, name: entity.name };
162
- },
163
- },
164
- objects: {
165
- list: () => objects.list(),
166
- inBox: (min, max) => objects.inBox(min, max),
167
- collidersOf: objectCollidersOf,
168
- halfExtentsOf: (catalogId) => content.objectById?.(catalogId)?.halfExtents ?? null,
169
- },
170
- terrain: ground,
171
- });
172
- const weapon = createWeaponStats((itemId) => content.itemById?.(itemId));
173
39
  const rawEvents = createGameEvents();
174
40
  const events = {
175
41
  on: rawEvents.on,
@@ -179,105 +45,75 @@ export function createGameContext(options) {
179
45
  signal.notify();
180
46
  },
181
47
  };
48
+ // --- Scene installer (entities, objects, colliders, spatial, raycast, targeting) ---
49
+ const scene = createSceneSubsystem({
50
+ content,
51
+ signalNotify: signal.notify,
52
+ ground,
53
+ events,
54
+ time,
55
+ ...(occluder !== undefined ? { occluder } : {}),
56
+ ...(options.models !== undefined ? { models: options.models } : {}),
57
+ });
58
+ const { entities, objects, statsByInstance, entityStats, ensureInstanceStats, catalogEntry, spatial, combatSpatial, sceneRaycast, targeting, entityColliders, entityCollidersOf, objectCollidersOf, entityVisualScaleOf, forms, paintLayer, spawnEntity, despawnEntity, moveEntityTowardCommit, resetAllToSpawn, bind, sceneObjects, } = scene;
59
+ // --- Shared game services (weapon, feed, features bag, commands, item use) ---
60
+ const weapon = createWeaponStats((itemId) => content.itemById?.(itemId));
182
61
  const feed = createGameFeed(definition.feed);
183
62
  const lootRegistry = createLootRegistry();
184
- const unlocks = notifyAfter(createUnlocks(), ["grant", "hydrate"], signal.notify);
63
+ /**
64
+ * Single feature-activation path (G9): `definition.features` is already the final map after
65
+ * `defineGame` OR-merged explicit boolean sugar (`features: { quest: true }`) with system-implied
66
+ * flags via `mergeSystemFeatures`. Descriptor install below is the only place that decides
67
+ * "feature on" — never a second enable check against systems.
68
+ */
185
69
  const features = definition.features ?? {};
186
- const rawSocial = features.social || features.chat
187
- ? createSocial({
188
- events,
189
- now,
190
- emotes: {
191
- entities: { get: (id) => entities.get(id) },
192
- spatial: { inRadius: (center, radius, filter) => spatial.inRadius(center, radius, filter) },
70
+ const featureRegistry = new Map();
71
+ const featureValue = (key) => featureRegistry.get(key);
72
+ let rawSocial = null;
73
+ const sharedSocial = () => {
74
+ if (rawSocial === null) {
75
+ rawSocial = createSocial({
76
+ events,
77
+ now,
78
+ emotes: {
79
+ entities: { get: (id) => entities.get(id) },
80
+ spatial: { inRadius: (center, radius, filter) => spatial.inRadius(center, radius, filter) },
81
+ },
82
+ });
83
+ }
84
+ return rawSocial;
85
+ };
86
+ const commandRegistry = createCommandRegistry();
87
+ if (definition.lifecycle !== undefined) {
88
+ const lifecycle = definition.lifecycle;
89
+ commandRegistry.define(lifecycle.commands?.start ?? "start", {
90
+ apply(state, input) {
91
+ const next = lifecycle.start(lifecycle.store.read(state), state, input);
92
+ lifecycle.store.write(state, next);
93
+ setGamePhase(state, lifecycle.phaseOf(next));
193
94
  },
194
- })
195
- : null;
196
- let social;
197
- if (features.social && rawSocial !== null) {
198
- social = {
199
- friends: notifyAfter(rawSocial.friends, ["request", "accept", "decline", "remove", "block", "hydrate"], signal.notify),
200
- party: notifyAfter(rawSocial.party, ["invite", "accept", "decline", "kick", "leave", "promote"], signal.notify),
201
- presence: rawSocial.presence,
202
- emotes: rawSocial.emotes,
203
- worldInvites: notifyAfter(rawSocial.worldInvites, ["invite", "accept", "decline"], signal.notify),
204
- };
205
- }
206
- let chat;
207
- if (features.chat && rawSocial !== null) {
208
- chat = notifyAfter(createChat({
209
- events,
210
- now,
211
- party: rawSocial.party,
212
- proximity: {
213
- entities: { get: (id) => entities.get(id) },
214
- spatial: { inRadius: (center, radius, filter) => spatial.inRadius(center, radius, filter) },
95
+ });
96
+ commandRegistry.define(lifecycle.commands?.restart ?? "restart", {
97
+ apply(state) {
98
+ const next = lifecycle.restart(lifecycle.store.read(state), state);
99
+ lifecycle.store.write(state, next);
100
+ setGamePhase(state, lifecycle.phaseOf(next));
215
101
  },
216
- blockedBy: (userId) => rawSocial.friends.snapshot(userId).blocked,
217
- }), ["register", "send", "whisper", "hydrate"], signal.notify);
218
- }
219
- const leaderboard = features.leaderboard
220
- ? notifyAfter(createLeaderboard(), ["increment", "hydrate"], signal.notify)
221
- : undefined;
222
- const roster = features.roster
223
- ? notifyAfter(createRoster({ now }), ["capture", "release", "setEquipped", "hydrate"], signal.notify)
224
- : undefined;
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;
102
+ });
233
103
  }
234
- const pose = createPoseState((instanceId) => catalogEntry(instanceId)?.movement);
235
- const commandRegistry = createCommandRegistry();
236
104
  const itemUse = createItemUse((itemId) => content.itemById?.(itemId)?.use);
237
- const possession = notifyAfter(createPossession({ entities, events }), ["possess", "own", "disown"], signal.notify);
238
- const forms = notifyAfter(createForms({ entities, time, events }), ["shapeshift", "revert"], signal.notify);
239
- const cosmetics = notifyAfter(createCosmetics({ events }), ["apply", "equip", "hydrate"], signal.notify);
240
- const paintLayer = notifyAfter(createPaintLayer(), ["paint", "clear"], signal.notify);
241
- const inventoryDeclarations = definition.inventories ?? {};
242
- const inventoryIds = Object.keys(inventoryDeclarations);
243
- const layouts = {};
244
- for (const [inventoryId, declaration] of Object.entries(inventoryDeclarations)) {
245
- layouts[inventoryId] = { slots: declaration.slots, accepts: declaration.accepts };
246
- }
247
- const traits = Object.values(inventoryDeclarations).find((declaration) => declaration.traits !== undefined)?.traits ?? { stackLimit: () => Number.POSITIVE_INFINITY };
248
- const inventoryByUser = new Map();
249
- function inventoryFor(userId) {
250
- let set = inventoryByUser.get(userId);
251
- if (set === undefined) {
252
- set = notifyAfter(createInventorySet(layouts, traits), ["put", "take", "move", "replaceState"], signal.notify);
253
- inventoryByUser.set(userId, set);
254
- }
255
- return set;
256
- }
257
- const wallets = new Map();
258
- const walletOf = (userId) => wallets.get(userId) ?? createEmptyWallet();
259
- const economy = {
260
- balance: (userId, currencyId) => walletBalance(walletOf(userId), currencyId),
261
- grant(userId, currencyId, amount) {
262
- wallets.set(userId, walletGrant(walletOf(userId), currencyId, amount));
263
- signal.notify();
264
- },
265
- charge(userId, currencyId, amount) {
266
- const result = walletCharge(walletOf(userId), currencyId, amount);
267
- if (result.status === "rejected")
268
- return { reason: result.reason };
269
- wallets.set(userId, result.state);
270
- signal.notify();
271
- return null;
272
- },
273
- };
274
- function putIntoAnyInventory(userId, itemId, count) {
275
- const inventory = inventoryFor(userId);
276
- for (const inventoryId of inventoryIds) {
277
- if (inventory.put(inventoryId, itemId, count).status === "ok")
278
- return;
279
- }
280
- }
105
+ // --- Player installer (inventory, wallets, pose, motion, loadout) ---
106
+ const playerSys = createPlayerSubsystem({
107
+ signalNotify: signal.notify,
108
+ inventoryDeclarations: definition.inventories ?? {},
109
+ entities,
110
+ events,
111
+ catalogEntry,
112
+ ensureInstanceStats,
113
+ feature: featureValue,
114
+ });
115
+ const { inventoryIds, inventoryByUser, inventoryFor, layouts, wallets, walletOf, economy, putIntoAnyInventory, playerStatsFor, pose, possession, motionByUser, motionFor, loadouts, seedUserPool, } = playerSys;
116
+ scene.setOnAfterDespawn((instanceId) => pose.clear(instanceId));
281
117
  const loot = {
282
118
  register: lootRegistry.register,
283
119
  has: lootRegistry.has,
@@ -293,540 +129,158 @@ export function createGameContext(options) {
293
129
  events.emit("loot.granted", event);
294
130
  },
295
131
  };
296
- const trade = createTradeSystem({
297
- resolveTrade: (itemId) => content.itemById?.(itemId)?.trade,
298
- wallet: {
299
- canAfford: (costs) => (walletCanAfford(walletOf(activeUserId()), costs) ? null : "insufficient-funds"),
300
- charge(costs) {
301
- const result = walletChargeAll(walletOf(activeUserId()), costs);
302
- if (result.status === "ok") {
303
- wallets.set(activeUserId(), result.state);
304
- signal.notify();
305
- }
306
- },
307
- grant(gains) {
308
- for (const [currencyId, amount] of Object.entries(gains)) {
309
- economy.grant(activeUserId(), currencyId, amount);
310
- }
311
- },
312
- },
313
- inventory: {
314
- put(inventoryId, itemId, count) {
315
- if (layouts[inventoryId] === undefined)
316
- return { reason: `unknown inventory "${inventoryId}"` };
317
- const result = inventoryFor(activeUserId()).put(inventoryId, itemId, count);
318
- return result.status === "ok" ? null : { reason: result.reason };
319
- },
320
- take(inventoryId, itemId, count) {
321
- if (layouts[inventoryId] === undefined)
322
- return { reason: `unknown inventory "${inventoryId}"` };
323
- const result = inventoryFor(activeUserId()).take(inventoryId, itemId, count);
324
- return result.status === "ok" ? null : { reason: result.reason };
325
- },
326
- count: (inventoryId, itemId) => inventoryFor(activeUserId()).count(inventoryId, itemId),
327
- },
328
- });
329
- function seedUserPool(userId, statId, pool) {
330
- const map = ensureInstanceStats(userId);
331
- const next = setStatValue(map, statId, pool);
332
- map[statId] = next[statId];
333
- }
334
- const rawQuest = createQuestJournal({
132
+ const { worldItems, spawnWorldItem, pickupWorldItem } = createWorldItemContext({
133
+ entities,
335
134
  events,
336
- rewards: {
337
- grantXp(userId, amount) {
338
- const existing = ensureInstanceStats(userId)["xp"];
339
- const current = (existing?.current ?? 0) + amount;
340
- seedUserPool(userId, "xp", { current, max: Math.max(existing?.max ?? 0, current) });
341
- },
342
- grantEconomy: (userId, currencyId, amount) => economy.grant(userId, currencyId, amount),
343
- grantItem(userId, inventoryId, itemId, count) {
344
- if (layouts[inventoryId] === undefined)
345
- return { reason: `unknown inventory "${inventoryId}"` };
346
- const result = inventoryFor(userId).put(inventoryId, itemId, count);
347
- return result.status === "ok" ? null : { reason: result.reason };
348
- },
349
- grantUnlock: (userId, unlockId) => unlocks.grant(userId, unlockId),
350
- },
351
- hasUnlock: (userId, id) => unlocks.has(userId, id),
352
- });
353
- const quest = notifyAfter(rawQuest, ["accept", "abandon", "progress", "turnIn", "grant", "revoke", "hydrate"], signal.notify);
354
- const loadouts = notifyAfter(createLoadouts({
355
- inventory: {
356
- begin(userId) {
357
- const inventory = inventoryFor(userId);
358
- const staged = new Map();
359
- return {
360
- put(inventoryId, itemId, count, slot) {
361
- const layout = layouts[inventoryId];
362
- if (layout === undefined)
363
- return { reason: `unknown inventory "${inventoryId}"` };
364
- const state = staged.get(inventoryId) ?? inventory.state(inventoryId);
365
- const result = putItem(state, layout, traits, itemId, count, slot === undefined ? undefined : { slot });
366
- if (result.status === "rejected")
367
- return { reason: result.reason };
368
- staged.set(inventoryId, result.state);
369
- return null;
370
- },
371
- commit() {
372
- for (const [inventoryId, state] of staged)
373
- inventory.replaceState(inventoryId, state);
374
- },
375
- };
376
- },
377
- },
378
- stats: { seed: seedUserPool },
379
- economy: { grant: economy.grant },
380
- unlocks: { grant: unlocks.grant },
381
- }), ["applyLoadout"], signal.notify);
382
- function spawnEntity(name, spawnOptions) {
383
- const entry = content.entityById?.(name);
384
- const walkSpeed = spawnOptions?.movement?.walkSpeed ?? entry?.movement?.walkSpeed;
385
- const options = walkSpeed === undefined
386
- ? spawnOptions
387
- : { ...spawnOptions, movement: { ...spawnOptions?.movement, walkSpeed } };
388
- const instanceId = entities.spawn(name, options);
389
- death.revive(instanceId);
390
- statsByInstance.set(instanceId, entry?.stats === undefined ? {} : seedStatValues(entry.stats));
391
- return instanceId;
392
- }
393
- function despawnEntity(instanceId) {
394
- const existed = entities.despawn(instanceId);
395
- statsByInstance.delete(instanceId);
396
- targeting.clearAll(instanceId);
397
- pose.clear(instanceId);
398
- entityColliders.delete(instanceId);
399
- return existed;
400
- }
401
- function resetAllToSpawn(filter) {
402
- let count = 0;
403
- for (const entity of entities.list()) {
404
- if (filter !== undefined && !filter(entity))
405
- continue;
406
- if (entities.resetToSpawn(entity.id))
407
- count += 1;
408
- }
409
- return count;
410
- }
411
- const worldItems = notifyAfter(createWorldItemStore({
412
- spawnEntity: (position) => entities.spawn(WORLD_ITEM_ENTITY_NAME, { position, role: "prop" }),
413
135
  despawnEntity,
414
- resolvePosition: (instanceId) => entities.get(instanceId)?.position,
415
- }), ["spawn", "take", "remove"], signal.notify);
416
- function spawnWorldItem(input) {
417
- const record = worldItems.spawn(input);
418
- events.emit("worldItem.dropped", {
419
- instanceId: record.instanceId,
420
- itemId: record.itemId,
421
- rarity: record.rarity,
422
- count: record.count,
423
- position: [input.position[0], input.position[1], input.position[2]],
424
- ...(record.source !== undefined ? { source: record.source } : {}),
425
- });
426
- return record;
427
- }
428
- function pickupWorldItem(instanceId, userId) {
429
- const record = worldItems.take(instanceId);
430
- if (record === null)
431
- return { status: "rejected", reason: "not-found" };
432
- loot.grantToPlayer(userId, [{ item: record.itemId, count: record.count }], "worldItem.pickup");
433
- events.emit("worldItem.picked_up", {
434
- instanceId,
435
- userId,
436
- itemId: record.itemId,
437
- rarity: record.rarity,
438
- count: record.count,
439
- });
440
- return { status: "ok", record };
441
- }
442
- const death = createDeathSystem({
443
- resolveOnDeath: (instanceId) => catalogEntry(instanceId)?.onDeath,
444
- resolveIdentity(instanceId) {
445
- const entity = entities.get(instanceId);
446
- if (entity === null)
447
- return null;
448
- return {
449
- catalogId: entity.name,
450
- position: [entity.position[0], entity.position[1], entity.position[2]],
451
- };
452
- },
453
- loot: { roll: (tableId) => (lootRegistry.has(tableId) ? lootRegistry.roll(tableId) : []) },
136
+ grantToPlayer: loot.grantToPlayer,
137
+ signalNotify: signal.notify,
138
+ });
139
+ // --- Combat installer (effects, projectiles, death, combat FX) ---
140
+ // Command runner closes over `ctx` once assembled (same late-bind pattern as before).
141
+ let ctxRef = null;
142
+ const combat = createCombatSubsystem({
143
+ content,
144
+ signalNotify: signal.notify,
145
+ now,
454
146
  events,
147
+ time,
148
+ entities,
149
+ objects,
150
+ combatSpatial,
151
+ sceneRaycast,
152
+ entityCollidersOf,
153
+ objectCollidersOf,
154
+ catalogEntry,
155
+ statsByInstance,
156
+ weapon,
157
+ loot,
158
+ lootRegistry,
159
+ spawnWorldItem,
160
+ despawnEntity,
455
161
  runCommand(name, args) {
456
- commandRegistry.run(ctx, name, args);
457
- },
458
- despawn(instanceId) {
459
- despawnEntity(instanceId);
162
+ if (ctxRef === null)
163
+ return;
164
+ commandRegistry.run(ctxRef, name, args);
460
165
  },
166
+ localUserId: player.userId,
167
+ ...(definition.physics !== undefined ? { physics: definition.physics } : {}),
461
168
  });
462
- const effects = notifyAfter(createEffectSystem({
463
- resolveReceive: (instanceId) => catalogEntry(instanceId)?.receive,
464
- resolveStats: (instanceId) => statsByInstance.get(instanceId),
465
- getStat: weapon.getStat,
466
- spatial: combatSpatial,
467
- onLethal(instanceId, lethalCtx) {
468
- const dyingEntity = entities.get(instanceId);
469
- const catalogId = dyingEntity?.name;
470
- const position = dyingEntity?.position;
471
- const normalizedOnDeath = normalizeOnDeath(catalogEntry(instanceId)?.onDeath);
472
- const reason = deathReasonFromEffect({
473
- ...lethalCtx,
474
- userIdOf: (id) => (id === player.userId ? player.userId : undefined),
475
- });
476
- const resolution = death.resolveDeath(instanceId, reason);
477
- if (resolution.status === "resolved" &&
478
- resolution.drops.length > 0 &&
479
- reason.kind === "player_kill" &&
480
- reason.killerUserId === player.userId) {
481
- if (normalizedOnDeath.dropMode === "world" && position !== undefined) {
482
- const resolved = resolveDeathDrops(resolution.drops, {
483
- mode: "world",
484
- origin: position,
485
- resolveRarity: (itemId) => content.itemById?.(itemId)?.rarity ?? DEFAULT_RARITY,
486
- resolveBaseType: (itemId) => content.itemById?.(itemId)?.baseType ?? itemId,
487
- scatter: normalizedOnDeath.scatter,
488
- ...(catalogId !== undefined ? { source: catalogId } : {}),
489
- });
490
- for (const spawn of resolved.worldSpawns)
491
- spawnWorldItem(spawn);
492
- if (resolved.grants.length > 0)
493
- loot.grantToPlayer(player.userId, resolved.grants, catalogId);
494
- }
495
- else {
496
- loot.grantToPlayer(player.userId, resolution.drops, catalogId);
497
- }
498
- }
499
- },
500
- }), ["applyEffect"], signal.notify);
501
- function emitFloatText(input) {
502
- const position = input.position ??
503
- (input.instanceId === undefined ? undefined : entities.get(input.instanceId)?.position);
504
- if (position === undefined)
505
- return;
506
- const text = input.text ?? (input.amount === undefined ? "" : String(Math.round(input.amount)));
507
- const event = {
508
- position: [position[0], position[1], position[2]],
509
- text,
510
- kind: input.kind ?? "info",
511
- };
512
- if (input.instanceId !== undefined)
513
- event.instanceId = input.instanceId;
514
- if (input.amount !== undefined)
515
- event.amount = input.amount;
516
- if (input.hitType !== undefined)
517
- event.hitType = input.hitType;
518
- if (input.element !== undefined)
519
- event.element = input.element;
520
- if (input.crit !== undefined)
521
- event.crit = input.crit;
522
- if (input.scale !== undefined)
523
- event.scale = input.scale;
524
- events.emit("entity.floatText", event);
525
- }
526
- let telegraphSeq = 0;
527
- function fireTelegraph(input) {
528
- const id = telegraphSeq++;
529
- const telegraphEvent = {
530
- id,
531
- shape: input.shape,
532
- position: [input.at[0], input.at[1], input.at[2]],
533
- windupMs: input.windupMs,
534
- kind: input.kind ?? "danger",
535
- };
536
- if (input.dir !== undefined)
537
- telegraphEvent.dir = input.dir;
538
- events.emit("combat.telegraph", telegraphEvent);
539
- const cancelVisual = () => events.emit("combat.telegraphCancelled", { id });
540
- const bound = input.effect;
541
- if (bound === undefined)
542
- return cancelVisual;
543
- const config = { shape: input.shape, at: input.at, windupMs: input.windupMs };
544
- if (input.dir !== undefined)
545
- config.dir = input.dir;
546
- const cancelEffect = time.after(input.windupMs / 1000, () => {
547
- const targets = entities.list().filter((entity) => pointInTelegraph(config, entity.position));
548
- for (const target of targets) {
549
- applyEffectAndFloat({
550
- from: input.from,
551
- to: target.id,
552
- effect: bound.effect,
553
- ...(bound.via === undefined ? {} : { via: bound.via }),
554
- });
555
- }
556
- });
557
- return () => {
558
- cancelEffect();
559
- cancelVisual();
560
- };
561
- }
562
- function applyHitReaction(input) {
563
- const attacker = entities.get(input.from);
564
- const target = entities.get(input.to);
565
- if (target === null)
566
- return null;
567
- const attackerPos = attacker?.position ?? target.position;
568
- const reaction = resolveHitReaction(input.config, {
569
- attackerPos,
570
- targetPos: target.position,
571
- ...(input.power === undefined ? {} : { power: input.power }),
572
- });
573
- entities.setPose(input.to, {
574
- position: [
575
- target.position[0] + reaction.impulse[0],
576
- target.position[1] + reaction.impulse[1],
577
- target.position[2] + reaction.impulse[2],
578
- ],
579
- rotationY: target.rotationY,
580
- });
581
- const reactionEvent = {
582
- instanceId: input.to,
583
- position: [target.position[0], target.position[1], target.position[2]],
584
- hitstopMs: reaction.hitstopMs,
585
- };
586
- if (reaction.shake !== null)
587
- reactionEvent.shake = reaction.shake;
588
- events.emit("combat.hitReaction", reactionEvent);
589
- return reaction;
590
- }
591
- function applyEffectAndFloat(input) {
592
- const positionsBefore = new Map();
593
- for (const entity of entities.list())
594
- positionsBefore.set(entity.id, entity.position);
595
- const results = effects.applyEffect(input);
596
- for (const result of results) {
597
- let total = 0;
598
- for (const delta of result.applied)
599
- total += delta.delta;
600
- if (total === 0)
601
- continue;
602
- const position = entities.get(result.instanceId)?.position ?? positionsBefore.get(result.instanceId);
603
- if (position === undefined)
604
- continue;
605
- const magnitude = Math.abs(total);
606
- emitFloatText({
607
- instanceId: result.instanceId,
608
- position: [position[0], position[1], position[2]],
609
- text: String(Math.round(magnitude)),
610
- kind: total < 0 ? "damage" : "heal",
611
- amount: magnitude,
612
- });
613
- }
614
- return results;
615
- }
616
- const floatingEffects = {
617
- canReceive: effects.canReceive,
618
- preview: effects.preview,
619
- applyEffect: applyEffectAndFloat,
620
- };
621
- const projectileObstacles = definition.physics?.projectileObstacles === true;
622
- const projectileSceneRaycast = {
623
- raycast(input) {
624
- return sceneRaycast.raycast({
625
- ...input,
626
- filter: {
627
- entities: true,
628
- objects: projectileObstacles,
629
- terrain: false,
630
- walls: projectileObstacles,
631
- ...input.filter,
632
- },
633
- });
634
- },
635
- raycastAll(input) {
636
- return sceneRaycast.raycastAll({
637
- ...input,
638
- filter: {
639
- entities: true,
640
- objects: projectileObstacles,
641
- terrain: false,
642
- walls: projectileObstacles,
643
- ...input.filter,
644
- },
645
- });
646
- },
647
- };
648
- const projectiles = notifyAfter(createProjectileSystem({
649
- effects: floatingEffects,
650
- spatial: combatSpatial,
651
- getStat: weapon.getStat,
652
- sceneRaycast: projectileSceneRaycast,
653
- objects: projectileObstacles
654
- ? {
655
- list: () => objects.list(),
656
- inBox: (min, max) => objects.inBox(min, max),
657
- halfExtents: (catalogId) => {
658
- const half = content.objectById?.(catalogId)?.halfExtents;
659
- return half === undefined ? null : [half[0], half[1], half[2]];
660
- },
661
- collidersOf: objectCollidersOf,
662
- }
663
- : undefined,
664
- entityCollidersOf,
665
- rotationYOf: (instanceId) => entities.get(instanceId)?.rotationY,
169
+ scene.setOnAfterSpawn((instanceId) => combat.death.revive(instanceId));
170
+ const { projectiles, combatFx, vfxInstances, effects } = combat;
171
+ const store = notifyAfter(createObservableKeyedStore(), ["set", "delete", "hydrate"], signal.notify);
172
+ const { pile, loop, raceState, cardPiles, turnLoops } = createContextRegistries(signal.notify);
173
+ const camera = notifyAfter(createCameraDirector(), ["follow", "setCinematic", "setChaseTuning"], signal.notify);
174
+ const input = createInputSnapshot();
175
+ // --- Descriptor install (single feature enable path) ---
176
+ const featureDeps = {
177
+ features,
178
+ signalNotify: signal.notify,
179
+ events,
666
180
  now,
667
- onSettle(report) {
668
- events.emit("projectile.settled", {
669
- from: report.from,
670
- origin: [report.origin[0], report.origin[1], report.origin[2]],
671
- at: [report.at[0], report.at[1], report.at[2]],
672
- effect: report.effect,
673
- hit: report.hit,
674
- });
675
- },
676
- }), ["fireProjectile", "settleProjectile"], signal.notify);
677
- const sceneObjects = {
678
- ...objects,
679
- catalog: (instanceId) => catalogObject(instanceId) ?? null,
680
- raycast: (input) => raycastObjects(objects, input),
681
- raycastAll: (input) => raycastObjectsAll(objects, input),
682
- setColliders(instanceId, colliders) {
683
- if (colliders === null)
684
- objectColliders.delete(instanceId);
685
- else
686
- objectColliders.set(instanceId, colliders);
687
- signal.notify();
688
- },
689
- collidersOf: objectCollidersOf,
181
+ entities,
182
+ spatial,
183
+ store,
184
+ commandRegistry,
185
+ economy,
186
+ content,
187
+ activeUserId,
188
+ walletOf,
189
+ setWallet: (userId, state) => wallets.set(userId, state),
190
+ layouts,
191
+ inventoryFor,
192
+ ensureInstanceStats,
193
+ seedUserPool,
194
+ sharedSocial,
195
+ pile,
196
+ loop,
197
+ cardPiles,
198
+ turnLoops,
199
+ raceState,
200
+ feature: featureValue,
690
201
  };
691
- const store = notifyAfter(createObservableKeyedStore(), ["set", "delete", "hydrate"], signal.notify);
692
- const players = features.players
693
- ? notifyAfter(createConnectedPlayers(), ["join", "leave"], signal.notify)
694
- : undefined;
695
- const cardPiles = new Map();
696
- function pile(id, config) {
697
- const existing = cardPiles.get(id);
698
- if (existing !== undefined)
699
- return existing;
700
- if (config === undefined) {
701
- throw new Error(`cardPile "${id}" has not been created yet; pass a config on first access`);
702
- }
703
- const created = notifyAfter(createCardPile(config), ["shuffle", "draw", "discard", "exhaust", "move", "reset"], signal.notify);
704
- cardPiles.set(id, created);
705
- return created;
202
+ const featureReplicateModules = [];
203
+ const featureSaveModules = [];
204
+ for (const descriptor of featureDescriptors) {
205
+ if (!descriptor.enabled(features))
206
+ continue;
207
+ const build = descriptor.create(featureDeps);
208
+ featureRegistry.set(descriptor.key, build.value);
209
+ if (build.replicate !== undefined)
210
+ featureReplicateModules.push(build.replicate);
211
+ if (build.save !== undefined)
212
+ featureSaveModules.push(build.save);
706
213
  }
707
- const turnLoops = new Map();
708
- function loop(id, config) {
709
- const existing = turnLoops.get(id);
710
- if (existing !== undefined)
711
- return existing;
712
- if (config === undefined) {
713
- throw new Error(`turn loop "${id}" has not been created yet; pass a config on first access`);
714
- }
715
- const raw = createTurnLoop(config);
716
- const wrappedCommit = notifyAfter(raw.commit, ["submit", "expected", "commit", "discard", "clear"], signal.notify);
717
- const wrapped = {
718
- ...notifyAfter(raw, [
719
- "setOrder",
720
- "addParticipant",
721
- "removeParticipant",
722
- "advancePhase",
723
- "advanceTurn",
724
- "advanceRound",
725
- "spend",
726
- "gain",
727
- "refill",
728
- "restore",
729
- ], signal.notify),
730
- commit: wrappedCommit,
731
- };
732
- turnLoops.set(id, wrapped);
733
- return wrapped;
214
+ const gameFeatureBlock = {};
215
+ for (const descriptor of featureDescriptors) {
216
+ if (descriptor.key === "cosmetics")
217
+ continue;
218
+ gameFeatureBlock[descriptor.key] = featureRegistry.get(descriptor.key);
734
219
  }
735
- class NotifyingRaceState extends RaceState {
736
- addRacer(racerId, startTime) {
737
- super.addRacer(racerId, startTime);
738
- signal.notify();
739
- }
740
- removeRacer(racerId) {
741
- super.removeRacer(racerId);
742
- signal.notify();
743
- }
744
- reset() {
745
- super.reset();
746
- signal.notify();
747
- }
748
- eliminate(racerId) {
749
- super.eliminate(racerId);
750
- signal.notify();
751
- }
752
- update(now, positions) {
753
- const raceEvents = super.update(now, positions);
754
- if (raceEvents.length > 0)
755
- signal.notify();
756
- return raceEvents;
220
+ const baselineDeps = {
221
+ signalNotify: signal.notify,
222
+ entities,
223
+ statsByInstance,
224
+ store,
225
+ feed,
226
+ inventoryIds,
227
+ inventoryByUser,
228
+ inventoryFor,
229
+ wallets,
230
+ time,
231
+ pose,
232
+ possession,
233
+ motionByUser,
234
+ motionFor,
235
+ };
236
+ const baselineBuilds = baselineDescriptors.map((descriptor) => descriptor.create(baselineDeps));
237
+ const snapshotModules = [
238
+ ...baselineBuilds.flatMap((build) => (build.replicate === undefined ? [] : [build.replicate])),
239
+ ...featureReplicateModules,
240
+ ];
241
+ const replication = options.replication;
242
+ const projectsViewers = policyProjectsViewers(replication);
243
+ const aoiRadius = replication?.aoiRadius;
244
+ const typedProject = (project) => (data, viewer, world) => project(data, viewer, world);
245
+ const projectorFor = (key) => {
246
+ if (aoiRadius !== undefined && key === "entities") {
247
+ return typedProject((data, viewer) => projectEntitiesForViewer(data, viewer, aoiRadius));
757
248
  }
758
- }
759
- const raceStates = new Map();
760
- function raceState(id, config) {
761
- const existing = raceStates.get(id);
762
- if (existing !== undefined)
763
- return existing;
764
- if (config === undefined) {
765
- throw new Error(`race "${id}" has not been created yet; pass a config on first access`);
249
+ if (aoiRadius !== undefined && key === "stats") {
250
+ return typedProject((data, viewer, world) => projectByVisibleIds(data, visibleEntityIds((world["entities"] ?? []), viewer, aoiRadius)));
766
251
  }
767
- const created = new NotifyingRaceState(config);
768
- raceStates.set(id, created);
769
- return created;
770
- }
771
- const camera = notifyAfter(createCameraDirector(), ["follow", "setCinematic", "setChaseTuning"], signal.notify);
772
- const input = createInputSnapshot();
773
- const motionByUser = new Map();
774
- function motionFor(userId) {
775
- let queue = motionByUser.get(userId);
776
- if (queue === undefined) {
777
- queue = createMotionIntents();
778
- motionByUser.set(userId, queue);
252
+ if (replication?.privatePerUser === true && key === "inventory") {
253
+ return typedProject((data, viewer) => projectPerUserForViewer(data, viewer));
779
254
  }
780
- return queue;
781
- }
782
- const snapshotModules = [
783
- { key: "entities", snapshot: () => entities.snapshot(), hydrate: (data) => entities.hydrate(data) },
784
- {
785
- key: "stats",
786
- snapshot: () => snapshotEntityStats(statsByInstance),
787
- hydrate: (data) => hydrateEntityStats(statsByInstance, data),
788
- },
789
- {
790
- key: "store",
791
- snapshot: () => store.snapshot(),
792
- hydrate: (data) => store.hydrate(data),
793
- },
794
- { key: "feed", snapshot: () => feed.snapshot(), hydrate: (data) => feed.hydrate(data) },
795
- {
796
- key: "inventory",
797
- snapshot: () => {
798
- const byUser = {};
799
- for (const [userId, set] of inventoryByUser) {
800
- const states = {};
801
- for (const inventoryId of inventoryIds)
802
- states[inventoryId] = set.state(inventoryId);
803
- byUser[userId] = states;
804
- }
805
- return byUser;
806
- },
807
- hydrate: (data) => {
808
- for (const [userId, states] of Object.entries(data)) {
809
- const set = inventoryFor(userId);
810
- for (const [inventoryId, state] of Object.entries(states))
811
- set.replaceState(inventoryId, state);
812
- }
813
- },
814
- },
255
+ return undefined;
256
+ };
257
+ const replicationModules = snapshotModules.map((module) => {
258
+ const project = projectorFor(module.key);
259
+ return {
260
+ ...module,
261
+ version: () => signal.version(),
262
+ ...(project === undefined ? {} : { project }),
263
+ };
264
+ });
265
+ const replicationVersion = () => replicationModules.reduce((sum, module) => sum + (module.version?.() ?? 0), 0);
266
+ const saveModules = [
267
+ ...snapshotModules,
268
+ ...baselineBuilds.flatMap((build) => (build.save === undefined ? [] : [build.save])),
269
+ ...featureSaveModules,
815
270
  ];
816
- if (leaderboard) {
817
- snapshotModules.push({
818
- key: "leaderboard",
819
- snapshot: () => leaderboard.snapshot(),
820
- hydrate: (data) => leaderboard.hydrate(data),
821
- });
271
+ function registerSave(module) {
272
+ saveModules.push(module);
822
273
  }
823
- if (chat) {
824
- snapshotModules.push({
825
- key: "chat",
826
- snapshot: () => chat.snapshot(),
827
- hydrate: (data) => chat.hydrate(data),
274
+ function registerReplicate(module) {
275
+ const project = projectorFor(module.key);
276
+ replicationModules.push({
277
+ ...module,
278
+ version: () => signal.version(),
279
+ ...(project === undefined ? {} : { project }),
828
280
  });
281
+ saveModules.push(module);
829
282
  }
283
+ // --- Assemble public GameContext ---
830
284
  const ctx = {
831
285
  scene: {
832
286
  object: sceneObjects,
@@ -838,20 +292,24 @@ export function createGameContext(options) {
838
292
  setPoseConstraint: entities.setPoseConstraint,
839
293
  get: entities.get,
840
294
  list: entities.list,
295
+ ids: entities.ids,
296
+ subscribeMembership: entities.subscribeMembership,
841
297
  spawnPoseOf: entities.spawnPoseOf,
842
298
  resetToSpawn: entities.resetToSpawn,
843
299
  resetAllToSpawn,
844
300
  blackboard: entities.blackboard,
845
301
  stats: entityStats,
846
- floatText: emitFloatText,
847
- telegraph: fireTelegraph,
848
- hitReaction: applyHitReaction,
302
+ floatText: combatFx.emitFloatText,
303
+ vfx: combatFx.emitVfx,
304
+ vfxInstance: vfxInstances,
305
+ telegraph: combatFx.fireTelegraph,
306
+ hitReaction: combatFx.applyHitReaction,
849
307
  setTarget: targeting.setTarget,
850
308
  getTarget: targeting.getTarget,
851
309
  cycleTarget: targeting.cycleTarget,
852
310
  canReceive: effects.canReceive,
853
311
  preview: effects.preview,
854
- effect: applyEffectAndFloat,
312
+ effect: combatFx.applyEffectAndFloat,
855
313
  willHitProjectile: projectiles.willHitProjectile,
856
314
  fireProjectile: projectiles.fireProjectile,
857
315
  settleProjectile: projectiles.settleProjectile,
@@ -860,6 +318,7 @@ export function createGameContext(options) {
860
318
  hasLineOfSight: spatial.hasLineOfSight,
861
319
  queryArc: spatial.queryArc,
862
320
  moveToward: spatial.moveToward,
321
+ moveTowardCommit: moveEntityTowardCommit,
863
322
  invalidateSpatial: spatial.invalidate,
864
323
  setColliders(instanceId, colliders) {
865
324
  if (colliders === null)
@@ -869,8 +328,10 @@ export function createGameContext(options) {
869
328
  signal.notify();
870
329
  },
871
330
  collidersOf: entityCollidersOf,
331
+ visualScaleOf: entityVisualScaleOf,
872
332
  form: forms,
873
333
  paint: paintLayer,
334
+ bind,
874
335
  },
875
336
  worldItem: {
876
337
  spawn: spawnWorldItem,
@@ -917,6 +378,9 @@ export function createGameContext(options) {
917
378
  play: (sound, at) => events.emit("audio.play", at === undefined ? { sound } : { sound, at }),
918
379
  music: (theme, transpose) => events.emit("audio.music", transpose === undefined ? { theme } : { theme, transpose }),
919
380
  resume: () => events.emit("audio.resume", {}),
381
+ loop: (id, sound, options) => events.emit("audio.loopStart", options?.at === undefined ? { id, sound } : { id, sound, at: options.at }),
382
+ setLoop: (id, params) => events.emit("audio.loopSet", { id, ...params }),
383
+ stopLoop: (id) => events.emit("audio.loopStop", { id }),
920
384
  },
921
385
  feed: {
922
386
  bind: (action) => feed.bind(action, events),
@@ -933,19 +397,11 @@ export function createGameContext(options) {
933
397
  },
934
398
  },
935
399
  loot,
936
- trade,
937
- quest,
938
- social,
939
- chat,
940
- unlocks,
941
400
  economy,
942
- leaderboard,
943
- roster,
944
401
  store,
945
- cards: features.cards ? { pile } : undefined,
946
- turn: features.turn ? { loop } : undefined,
947
- race: features.race ? { state: raceState } : undefined,
948
- players,
402
+ ...gameFeatureBlock,
403
+ registerSave,
404
+ registerReplicate,
949
405
  },
950
406
  player: {
951
407
  get userId() {
@@ -964,7 +420,7 @@ export function createGameContext(options) {
964
420
  applyLoadout: loadouts.applyLoadout,
965
421
  movement: pose,
966
422
  possession,
967
- cosmetics,
423
+ cosmetics: featureValue("cosmetics"),
968
424
  get motion() {
969
425
  return motionFor(activeUserId());
970
426
  },
@@ -988,11 +444,50 @@ export function createGameContext(options) {
988
444
  input,
989
445
  subscribe: signal.subscribe,
990
446
  version: signal.version,
991
- snapshot: () => composeWorldSnapshot(snapshotModules),
447
+ touch: signal.notify,
448
+ snapshot: (viewer) => composeWorldSnapshot(replicationModules, viewer),
992
449
  hydrate(snapshot) {
993
- applyWorldSnapshot(snapshotModules, snapshot);
450
+ applyWorldSnapshot(replicationModules, snapshot);
994
451
  signal.notify();
995
452
  },
453
+ replicationVersion,
454
+ replicatesPerViewer: () => projectsViewers,
996
455
  };
456
+ ctxRef = ctx;
457
+ const saveOptions = resolveSaveOptions(definition, options);
458
+ if (saveOptions !== undefined) {
459
+ const saveTarget = {
460
+ snapshot: () => composeWorldSnapshot(saveModules),
461
+ hydrate: (snapshot) => {
462
+ applyWorldSnapshot(saveModules, snapshot);
463
+ signal.notify();
464
+ },
465
+ subscribe: signal.subscribe,
466
+ };
467
+ ctx.game.save = createRuntimeSave({ target: saveTarget, ...saveOptions });
468
+ }
997
469
  return ctx;
998
470
  }
471
+ function saveKey(name) {
472
+ const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
473
+ return `jgengine:save:${slug === "" ? "game" : slug}`;
474
+ }
475
+ /** 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. */
476
+ function resolveSaveOptions(definition, options) {
477
+ if (options.save !== undefined)
478
+ return options.save;
479
+ const persist = definition.persist;
480
+ if (persist === undefined || persist === false)
481
+ return undefined;
482
+ if (!isOffline(definition.multiplayer))
483
+ return undefined;
484
+ const config = persist === true ? {} : persist;
485
+ return {
486
+ backend: config.storage === "memory" ? memorySaveBackend() : localSaveBackend(),
487
+ mode: config.mode,
488
+ key: saveKey(definition.name),
489
+ version: config.version,
490
+ autosaveMs: config.autosaveMs,
491
+ now: options.now,
492
+ };
493
+ }