@jgengine/core 0.7.0 → 0.9.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 (329) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +2 -0
  3. package/dist/ai/flock.d.ts +36 -0
  4. package/dist/ai/flock.js +182 -0
  5. package/dist/ai/groupAssist.d.ts +19 -0
  6. package/dist/ai/groupAssist.js +59 -0
  7. package/dist/ai/laneSelect.d.ts +21 -0
  8. package/dist/ai/laneSelect.js +37 -0
  9. package/dist/ai/mobBrain.d.ts +68 -0
  10. package/dist/ai/mobBrain.js +115 -0
  11. package/dist/ai/spawnDirector.d.ts +5 -0
  12. package/dist/ai/spawnDirector.js +14 -2
  13. package/dist/ai/threat.d.ts +2 -0
  14. package/dist/ai/threat.js +34 -0
  15. package/dist/anim/easing.d.ts +14 -0
  16. package/dist/anim/easing.js +55 -0
  17. package/dist/behaviour/behaviour.d.ts +136 -0
  18. package/dist/behaviour/behaviour.js +388 -0
  19. package/dist/cards/cardPile.d.ts +1 -0
  20. package/dist/cards/cardPile.js +12 -3
  21. package/dist/cartridge/runtime.d.ts +15 -0
  22. package/dist/cartridge/runtime.js +556 -0
  23. package/dist/cartridge/spec.d.ts +188 -0
  24. package/dist/cartridge/spec.js +19 -0
  25. package/dist/cartridge/validate.d.ts +2 -0
  26. package/dist/cartridge/validate.js +78 -0
  27. package/dist/combat/abilityKit.d.ts +22 -2
  28. package/dist/combat/abilityKit.js +72 -13
  29. package/dist/combat/castRunner.d.ts +40 -0
  30. package/dist/combat/castRunner.js +56 -0
  31. package/dist/combat/comboPoints.d.ts +14 -0
  32. package/dist/combat/comboPoints.js +52 -0
  33. package/dist/combat/defensiveWindow.d.ts +1 -1
  34. package/dist/combat/defensiveWindow.js +5 -3
  35. package/dist/combat/effects.d.ts +1 -1
  36. package/dist/combat/effects.js +9 -6
  37. package/dist/combat/projectiles.d.ts +64 -6
  38. package/dist/combat/projectiles.js +232 -74
  39. package/dist/combat/resistance.d.ts +16 -4
  40. package/dist/combat/resistance.js +43 -7
  41. package/dist/combat/resourcePool.d.ts +20 -0
  42. package/dist/combat/resourcePool.js +55 -0
  43. package/dist/combat/shotOrigin.d.ts +40 -0
  44. package/dist/combat/shotOrigin.js +57 -0
  45. package/dist/combat/telegraph.d.ts +30 -1
  46. package/dist/combat/telegraph.js +61 -0
  47. package/dist/commands/commandRegistry.d.ts +1 -1
  48. package/dist/commands/commandRegistry.js +2 -1
  49. package/dist/crafting/recipe.js +4 -1
  50. package/dist/data/dataSource.d.ts +26 -0
  51. package/dist/data/dataSource.js +93 -0
  52. package/dist/data/devProxy.d.ts +11 -0
  53. package/dist/data/devProxy.js +38 -0
  54. package/dist/data/fetchJson.d.ts +20 -0
  55. package/dist/data/fetchJson.js +43 -0
  56. package/dist/data/jsonDataSource.d.ts +4 -0
  57. package/dist/data/jsonDataSource.js +6 -0
  58. package/dist/devtools/devtools.d.ts +205 -0
  59. package/dist/devtools/devtools.js +832 -0
  60. package/dist/devtools/transformTunables.d.ts +14 -0
  61. package/dist/devtools/transformTunables.js +38 -0
  62. package/dist/devtools/tunableSchema.d.ts +140 -0
  63. package/dist/devtools/tunableSchema.js +476 -0
  64. package/dist/faction/factions.d.ts +33 -0
  65. package/dist/faction/factions.js +76 -0
  66. package/dist/faction/reputation.d.ts +32 -0
  67. package/dist/faction/reputation.js +82 -0
  68. package/dist/game/chat.d.ts +68 -0
  69. package/dist/game/chat.js +173 -0
  70. package/dist/game/chatFilter.d.ts +15 -0
  71. package/dist/game/chatFilter.js +55 -0
  72. package/dist/game/controlGate.d.ts +4 -0
  73. package/dist/game/controlGate.js +7 -0
  74. package/dist/game/defineGame.d.ts +10 -2
  75. package/dist/game/defineGame.js +2 -1
  76. package/dist/game/events.d.ts +35 -0
  77. package/dist/game/gamePhase.d.ts +20 -0
  78. package/dist/game/gamePhase.js +21 -0
  79. package/dist/game/levelSequence.d.ts +44 -0
  80. package/dist/game/levelSequence.js +78 -0
  81. package/dist/game/lootTable.d.ts +10 -1
  82. package/dist/game/lootTable.js +27 -7
  83. package/dist/game/playableGame.d.ts +286 -6
  84. package/dist/game/playableGame.js +6 -1
  85. package/dist/game/progression.d.ts +1 -0
  86. package/dist/game/progression.js +23 -2
  87. package/dist/game/quest.d.ts +7 -3
  88. package/dist/game/quest.js +17 -8
  89. package/dist/game/race.d.ts +44 -1
  90. package/dist/game/race.js +116 -11
  91. package/dist/game/recordBook.d.ts +29 -0
  92. package/dist/game/recordBook.js +86 -0
  93. package/dist/game/social.d.ts +42 -0
  94. package/dist/game/social.js +122 -0
  95. package/dist/game/spawnPoints.d.ts +19 -0
  96. package/dist/game/spawnPoints.js +23 -0
  97. package/dist/game/talents.d.ts +47 -0
  98. package/dist/game/talents.js +140 -0
  99. package/dist/game/worldItem.d.ts +3 -0
  100. package/dist/game/worldItem.js +12 -6
  101. package/dist/index.d.ts +1 -0
  102. package/dist/index.js +1 -0
  103. package/dist/input/actionBindings.d.ts +10 -0
  104. package/dist/input/actionBindings.js +14 -0
  105. package/dist/input/axisInput.d.ts +26 -3
  106. package/dist/input/axisInput.js +44 -4
  107. package/dist/input/bindingOverrides.d.ts +25 -0
  108. package/dist/input/bindingOverrides.js +95 -0
  109. package/dist/input/gestureSurface.d.ts +31 -0
  110. package/dist/input/gestureSurface.js +120 -0
  111. package/dist/input/pointer.d.ts +45 -0
  112. package/dist/input/pointer.js +48 -0
  113. package/dist/input/pointerAxis.d.ts +30 -0
  114. package/dist/input/pointerAxis.js +31 -0
  115. package/dist/input/touchScheme.d.ts +89 -0
  116. package/dist/input/touchScheme.js +153 -0
  117. package/dist/item/affix.d.ts +1 -1
  118. package/dist/item/affix.js +1 -19
  119. package/dist/meta/changelog.d.ts +1 -1
  120. package/dist/meta/changelog.js +59 -1
  121. package/dist/movement/dash.d.ts +3 -0
  122. package/dist/movement/dash.js +19 -5
  123. package/dist/movement/glideModel.d.ts +44 -0
  124. package/dist/movement/glideModel.js +54 -0
  125. package/dist/movement/grappleSwing.d.ts +34 -0
  126. package/dist/movement/grappleSwing.js +56 -0
  127. package/dist/movement/leaderTrail.d.ts +29 -0
  128. package/dist/movement/leaderTrail.js +67 -0
  129. package/dist/movement/movementModel.d.ts +36 -3
  130. package/dist/movement/movementModel.js +81 -5
  131. package/dist/movement/steering.d.ts +27 -0
  132. package/dist/movement/steering.js +40 -0
  133. package/dist/movement/voxelController.d.ts +61 -0
  134. package/dist/movement/voxelController.js +229 -0
  135. package/dist/multiplayer/chatContract.d.ts +42 -0
  136. package/dist/multiplayer/chatContract.js +57 -0
  137. package/dist/multiplayer/feedWriteGate.d.ts +10 -0
  138. package/dist/multiplayer/feedWriteGate.js +12 -0
  139. package/dist/multiplayer/identity.d.ts +13 -0
  140. package/dist/multiplayer/identity.js +20 -0
  141. package/dist/multiplayer/poseSyncGate.d.ts +1 -0
  142. package/dist/multiplayer/poseSyncGate.js +13 -1
  143. package/dist/multiplayer/presenceModel.d.ts +17 -2
  144. package/dist/multiplayer/presenceModel.js +30 -4
  145. package/dist/multiplayer/voiceContract.d.ts +44 -0
  146. package/dist/multiplayer/voiceContract.js +108 -0
  147. package/dist/nav/corridors.d.ts +30 -0
  148. package/dist/nav/corridors.js +69 -0
  149. package/dist/nav/navConstrain.d.ts +14 -0
  150. package/dist/nav/navConstrain.js +26 -0
  151. package/dist/nav/navFromEnvironment.d.ts +11 -0
  152. package/dist/nav/navFromEnvironment.js +16 -0
  153. package/dist/nav/navGrid.d.ts +10 -0
  154. package/dist/nav/navGrid.js +48 -5
  155. package/dist/nav/pathFollow.d.ts +4 -2
  156. package/dist/nav/pathFollow.js +5 -3
  157. package/dist/nav/railGraph.d.ts +66 -0
  158. package/dist/nav/railGraph.js +129 -0
  159. package/dist/nav/timetable.d.ts +41 -0
  160. package/dist/nav/timetable.js +94 -0
  161. package/dist/physics/ballisticSweep.d.ts +18 -0
  162. package/dist/physics/ballisticSweep.js +49 -0
  163. package/dist/physics/buoyancy.d.ts +6 -0
  164. package/dist/physics/buoyancy.js +14 -0
  165. package/dist/physics/flowTube.d.ts +32 -0
  166. package/dist/physics/flowTube.js +64 -0
  167. package/dist/physics/forceVolume.d.ts +27 -0
  168. package/dist/physics/forceVolume.js +43 -5
  169. package/dist/physics/kinematicVehicle.d.ts +52 -0
  170. package/dist/physics/kinematicVehicle.js +81 -0
  171. package/dist/physics/physicsWorld.d.ts +54 -8
  172. package/dist/physics/physicsWorld.js +400 -79
  173. package/dist/physics/ragdoll.js +1 -1
  174. package/dist/physics/spatialGrid.d.ts +1 -1
  175. package/dist/physics/spatialGrid.js +5 -0
  176. package/dist/physics/vehicleBody.d.ts +6 -0
  177. package/dist/physics/vehicleBody.js +18 -18
  178. package/dist/puzzle/cellGrid.d.ts +26 -0
  179. package/dist/puzzle/cellGrid.js +110 -0
  180. package/dist/puzzle/fallingPiece.d.ts +24 -0
  181. package/dist/puzzle/fallingPiece.js +51 -0
  182. package/dist/random/nameGen.d.ts +18 -0
  183. package/dist/random/nameGen.js +35 -0
  184. package/dist/random/rng.d.ts +2 -0
  185. package/dist/random/rng.js +23 -0
  186. package/dist/random/seedLink.d.ts +5 -0
  187. package/dist/random/seedLink.js +35 -0
  188. package/dist/runtime/adapter.d.ts +35 -0
  189. package/dist/runtime/adapter.js +37 -1
  190. package/dist/runtime/cameraDirector.d.ts +15 -0
  191. package/dist/runtime/cameraDirector.js +31 -0
  192. package/dist/runtime/commandRunner.d.ts +2 -2
  193. package/dist/runtime/commandRunner.js +2 -2
  194. package/dist/runtime/gameContext.d.ts +65 -2
  195. package/dist/runtime/gameContext.js +280 -9
  196. package/dist/runtime/gameRuntime.d.ts +5 -1
  197. package/dist/runtime/gameRuntime.js +13 -12
  198. package/dist/runtime/hostPersistence.d.ts +30 -1
  199. package/dist/runtime/hostPersistence.js +70 -5
  200. package/dist/runtime/inputSnapshot.d.ts +12 -0
  201. package/dist/runtime/inputSnapshot.js +17 -0
  202. package/dist/runtime/motionIntents.d.ts +33 -0
  203. package/dist/runtime/motionIntents.js +43 -0
  204. package/dist/runtime/snapshot.d.ts +1 -0
  205. package/dist/runtime/snapshot.js +1 -1
  206. package/dist/runtime/transport.d.ts +32 -0
  207. package/dist/runtime/visibility.d.ts +74 -0
  208. package/dist/runtime/visibility.js +127 -0
  209. package/dist/scene/colliders.d.ts +44 -0
  210. package/dist/scene/colliders.js +77 -0
  211. package/dist/scene/entityStore.d.ts +30 -5
  212. package/dist/scene/entityStore.js +83 -13
  213. package/dist/scene/movementSpeed.d.ts +35 -0
  214. package/dist/scene/movementSpeed.js +20 -0
  215. package/dist/scene/objectQuery.d.ts +29 -0
  216. package/dist/scene/objectQuery.js +140 -0
  217. package/dist/scene/objectStore.d.ts +13 -0
  218. package/dist/scene/objectStore.js +130 -6
  219. package/dist/scene/paintLayer.d.ts +15 -0
  220. package/dist/scene/paintLayer.js +53 -0
  221. package/dist/scene/sceneRaycast.d.ts +80 -0
  222. package/dist/scene/sceneRaycast.js +284 -0
  223. package/dist/scene/spatial.d.ts +17 -1
  224. package/dist/scene/spatial.js +140 -17
  225. package/dist/sensor/concealment.d.ts +21 -0
  226. package/dist/sensor/concealment.js +63 -0
  227. package/dist/sensor/freezeMonitor.d.ts +17 -0
  228. package/dist/sensor/freezeMonitor.js +35 -0
  229. package/dist/sensor/recordingBuffer.d.ts +6 -0
  230. package/dist/sensor/recordingBuffer.js +7 -0
  231. package/dist/sensor/replayLoop.d.ts +39 -0
  232. package/dist/sensor/replayLoop.js +64 -0
  233. package/dist/sensor/visionCone.d.ts +31 -0
  234. package/dist/sensor/visionCone.js +72 -0
  235. package/dist/session/extraction.d.ts +2 -2
  236. package/dist/session/extraction.js +13 -2
  237. package/dist/session/roles.d.ts +6 -0
  238. package/dist/session/roles.js +32 -0
  239. package/dist/session/roundState.d.ts +41 -17
  240. package/dist/session/roundState.js +67 -38
  241. package/dist/settings/settingsModel.d.ts +97 -0
  242. package/dist/settings/settingsModel.js +100 -0
  243. package/dist/stats/statModifiers.d.ts +5 -2
  244. package/dist/stats/statModifiers.js +32 -14
  245. package/dist/store/observableKeyedStore.js +8 -2
  246. package/dist/survival/decayMeter.d.ts +1 -1
  247. package/dist/survival/decayMeter.js +0 -1
  248. package/dist/tactics/fallingGrid.d.ts +62 -0
  249. package/dist/tactics/fallingGrid.js +158 -0
  250. package/dist/tactics/tacticalGrid.d.ts +11 -0
  251. package/dist/tactics/tacticalGrid.js +17 -0
  252. package/dist/time/beatClock.d.ts +18 -0
  253. package/dist/time/beatClock.js +24 -0
  254. package/dist/time/idleProgress.d.ts +29 -0
  255. package/dist/time/idleProgress.js +31 -0
  256. package/dist/time/serverTick.d.ts +7 -1
  257. package/dist/time/serverTick.js +27 -4
  258. package/dist/time/simClock.d.ts +25 -1
  259. package/dist/time/simClock.js +28 -3
  260. package/dist/time/stateSchedule.d.ts +57 -0
  261. package/dist/time/stateSchedule.js +121 -0
  262. package/dist/turn/commit.d.ts +2 -2
  263. package/dist/turn/commit.js +2 -2
  264. package/dist/turn/intent.d.ts +14 -0
  265. package/dist/turn/intent.js +29 -0
  266. package/dist/turn/turnLoop.d.ts +2 -0
  267. package/dist/turn/turnLoop.js +8 -1
  268. package/dist/ui/hudLayout.d.ts +58 -0
  269. package/dist/ui/hudLayout.js +207 -0
  270. package/dist/ui/hudScale.d.ts +44 -0
  271. package/dist/ui/hudScale.js +55 -0
  272. package/dist/visibility/assetStreaming.d.ts +66 -0
  273. package/dist/visibility/assetStreaming.js +201 -0
  274. package/dist/visibility/bounds.d.ts +66 -0
  275. package/dist/visibility/bounds.js +113 -0
  276. package/dist/visibility/camera.d.ts +21 -0
  277. package/dist/visibility/camera.js +3 -0
  278. package/dist/visibility/config.d.ts +21 -0
  279. package/dist/visibility/config.js +1 -0
  280. package/dist/visibility/diagnostics.d.ts +26 -0
  281. package/dist/visibility/diagnostics.js +36 -0
  282. package/dist/visibility/distance.d.ts +11 -0
  283. package/dist/visibility/distance.js +30 -0
  284. package/dist/visibility/frustum.d.ts +47 -0
  285. package/dist/visibility/frustum.js +186 -0
  286. package/dist/visibility/occlusion.d.ts +36 -0
  287. package/dist/visibility/occlusion.js +45 -0
  288. package/dist/visibility/settings.d.ts +75 -0
  289. package/dist/visibility/settings.js +49 -0
  290. package/dist/visibility/simulationCulling.d.ts +34 -0
  291. package/dist/visibility/simulationCulling.js +36 -0
  292. package/dist/visibility/spatialIndex.d.ts +45 -0
  293. package/dist/visibility/spatialIndex.js +198 -0
  294. package/dist/visibility/visibilitySystem.d.ts +90 -0
  295. package/dist/visibility/visibilitySystem.js +257 -0
  296. package/dist/world/buildings.d.ts +6 -0
  297. package/dist/world/buildings.js +152 -0
  298. package/dist/world/carve.d.ts +7 -2
  299. package/dist/world/carve.js +17 -7
  300. package/dist/world/cellStates.d.ts +45 -0
  301. package/dist/world/cellStates.js +103 -0
  302. package/dist/world/environmentSummary.d.ts +61 -0
  303. package/dist/world/environmentSummary.js +138 -0
  304. package/dist/world/features.d.ts +138 -9
  305. package/dist/world/features.js +73 -2
  306. package/dist/world/gridInstances.d.ts +12 -0
  307. package/dist/world/gridInstances.js +160 -0
  308. package/dist/world/lod.d.ts +17 -0
  309. package/dist/world/lod.js +66 -0
  310. package/dist/world/mapLayers.d.ts +60 -0
  311. package/dist/world/mapLayers.js +44 -0
  312. package/dist/world/massing.d.ts +45 -0
  313. package/dist/world/massing.js +581 -0
  314. package/dist/world/minimap.d.ts +13 -1
  315. package/dist/world/minimap.js +21 -2
  316. package/dist/world/placementController.d.ts +22 -1
  317. package/dist/world/placementController.js +56 -0
  318. package/dist/world/terrain.d.ts +62 -1
  319. package/dist/world/terrain.js +253 -9
  320. package/dist/world/voxelField.d.ts +54 -0
  321. package/dist/world/voxelField.js +241 -0
  322. package/dist/world/water.d.ts +7 -0
  323. package/dist/world/water.js +19 -4
  324. package/dist/world/weather.d.ts +3 -18
  325. package/dist/world/weather.js +4 -17
  326. package/dist/world/windZones.d.ts +47 -0
  327. package/dist/world/windZones.js +59 -0
  328. package/llms.txt +4983 -0
  329. package/package.json +14 -3
@@ -1,3 +1,4 @@
1
+ import { type CardPile, type CardPileConfig } from "../cards/cardPile.js";
1
2
  import { type OnDeathSpec } from "../combat/death.js";
2
3
  import { type EffectInput, type EffectResult, type EffectVia, type ReceiveMap, type SingleTargetEffectInput } from "../combat/effects.js";
3
4
  import { type ProjectileSystem } from "../combat/projectiles.js";
@@ -6,6 +7,7 @@ import { type TelegraphShape } from "../combat/telegraph.js";
6
7
  import { type CommandDefinition, type CommandResult } from "../commands/commandRegistry.js";
7
8
  import { type Cosmetics } from "../game/cosmetics.js";
8
9
  import type { GameDefinition } from "../game/defineGame.js";
10
+ import { type TerrainField } from "../world/terrain.js";
9
11
  import { type GameEventMap, type GameEvents } from "../game/events.js";
10
12
  import { type GameFeed } from "../game/feed.js";
11
13
  import { type Leaderboard } from "../game/leaderboard.js";
@@ -13,6 +15,7 @@ import { type Loadouts } from "../game/loadout.js";
13
15
  import { type Drop, type LootTableDef } from "../game/lootTable.js";
14
16
  import { type QuestJournal } from "../game/quest.js";
15
17
  import { type WorldItemRecord, type WorldItemSpawnInput } from "../game/worldItem.js";
18
+ import { type Chat } from "../game/chat.js";
16
19
  import { type Social } from "../game/social.js";
17
20
  import { type TradeField, type TradeSystem } from "../game/trade.js";
18
21
  import { type Unlocks } from "../game/unlocks.js";
@@ -23,16 +26,26 @@ import { type ItemUseHandler, type ItemUseInput, type ItemUseRejection, type Ite
23
26
  import { type WeaponStats } from "../item/weapon.js";
24
27
  import { type PoseAllowedStates, type PoseState } from "../movement/poseState.js";
25
28
  import type { ModelAssetRef } from "../scene/assetCatalog.js";
29
+ import { type PaintLayer } from "../scene/paintLayer.js";
26
30
  import { type EntityStatsApi, type StatCatalog } from "../scene/entityStats.js";
27
- import type { EntityPose, EntityPosition, SceneEntity, SpawnOptions } from "../scene/entityStore.js";
31
+ import type { EntityPose, EntityPosition, EntityStore, SceneEntity, SpawnOptions, SpawnPose } from "../scene/entityStore.js";
28
32
  import { type Forms } from "../scene/form.js";
33
+ import type { EntityColliderSet } from "../scene/colliders.js";
34
+ import { type ObjectRaycastHit, type ObjectRaycastInput } from "../scene/objectQuery.js";
29
35
  import { type ObjectStore } from "../scene/objectStore.js";
30
36
  import { type Roster } from "../scene/roster.js";
31
37
  import { type Possession } from "../scene/possession.js";
38
+ import { type SceneRaycastHit, type SceneRaycastInput } from "../scene/sceneRaycast.js";
32
39
  import { type SpatialApi } from "../scene/spatial.js";
33
40
  import { type CycleTargetOptions } from "../scene/targeting.js";
34
41
  import { type Stats } from "../stats/statModifiers.js";
42
+ import { type ObservableKeyedStore } from "../store/observableKeyedStore.js";
35
43
  import { type SimClock } from "../time/simClock.js";
44
+ import { type TurnLoop, type TurnLoopConfig } from "../turn/turnLoop.js";
45
+ import { RaceState, type RaceStateConfig } from "../game/race.js";
46
+ import { type CameraDirector } from "./cameraDirector.js";
47
+ import { type InputSnapshot } from "./inputSnapshot.js";
48
+ import { type MotionIntents } from "./motionIntents.js";
36
49
  export interface GameContextItemEntry {
37
50
  use?: string;
38
51
  weapon?: Record<string, unknown>;
@@ -53,6 +66,8 @@ export interface GameContextEntityEntry {
53
66
  role?: CatalogEntityRole;
54
67
  /** Right-click context-menu verbs for this entity (#31). */
55
68
  verbs?: readonly ContextVerb[];
69
+ /** Purpose-specific colliders (physical body + named damage hitboxes). */
70
+ colliders?: EntityColliderSet;
56
71
  }
57
72
  export interface GameContextObjectEntry {
58
73
  proximityPrompt?: ProximityPrompt;
@@ -62,6 +77,8 @@ export interface GameContextObjectEntry {
62
77
  slotInventory?: InventoryLayout;
63
78
  /** Right-click context-menu verbs for this object (#31). */
64
79
  verbs?: readonly ContextVerb[];
80
+ colliders?: EntityColliderSet;
81
+ halfExtents?: readonly [number, number, number];
65
82
  }
66
83
  export interface GameContextContent {
67
84
  itemById?(itemId: string): GameContextItemEntry | null | undefined;
@@ -76,9 +93,14 @@ export interface GameContextOptions<TAssetRef extends ModelAssetRef = ModelAsset
76
93
  isNew: boolean;
77
94
  };
78
95
  now?: () => number;
96
+ occluder?: (from: EntityPosition, to: EntityPosition) => boolean;
79
97
  }
80
98
  export interface SceneObjectContext extends ObjectStore {
81
99
  catalog(instanceId: string): GameContextObjectEntry | null;
100
+ raycast(input: ObjectRaycastInput): ObjectRaycastHit | null;
101
+ raycastAll(input: ObjectRaycastInput): readonly ObjectRaycastHit[];
102
+ setColliders(instanceId: string, colliders: EntityColliderSet | null): void;
103
+ collidersOf(instanceId: string): EntityColliderSet | null;
82
104
  }
83
105
  export interface FloatTextInput {
84
106
  instanceId?: string;
@@ -115,9 +137,14 @@ export interface HitReactionInput {
115
137
  export interface SceneEntityContext {
116
138
  spawn(name: string, options?: SpawnOptions): string;
117
139
  despawn(instanceId: string): boolean;
140
+ update: EntityStore["update"];
118
141
  setPose(instanceId: string, pose: EntityPose): boolean;
142
+ setPoseConstraint: EntityStore["setPoseConstraint"];
119
143
  get(instanceId: string): SceneEntity | null;
120
144
  list(): readonly SceneEntity[];
145
+ spawnPoseOf(instanceId: string): SpawnPose | null;
146
+ resetToSpawn(instanceId: string): boolean;
147
+ resetAllToSpawn(filter?: (entity: SceneEntity) => boolean): number;
121
148
  stats: EntityStatsApi;
122
149
  floatText(input: FloatTextInput): void;
123
150
  telegraph(input: TelegraphInput): () => void;
@@ -125,7 +152,7 @@ export interface SceneEntityContext {
125
152
  setTarget(fromId: string, toId: string | null): void;
126
153
  getTarget(fromId: string): string | null;
127
154
  cycleTarget(fromId: string, options?: CycleTargetOptions): string | null;
128
- canReceive(instanceId: string, effect: string): string | null;
155
+ canReceive(instanceId: string, effect: string, magnitude?: number): string | null;
129
156
  preview(input: SingleTargetEffectInput): number;
130
157
  effect(input: EffectInput): EffectResult[];
131
158
  willHitProjectile: ProjectileSystem["willHitProjectile"];
@@ -136,7 +163,11 @@ export interface SceneEntityContext {
136
163
  hasLineOfSight: SpatialApi["hasLineOfSight"];
137
164
  queryArc: SpatialApi["queryArc"];
138
165
  moveToward: SpatialApi["moveToward"];
166
+ invalidateSpatial: SpatialApi["invalidate"];
167
+ setColliders(instanceId: string, colliders: EntityColliderSet | null): void;
168
+ collidersOf(instanceId: string): EntityColliderSet | null;
139
169
  form: Forms;
170
+ paint: PaintLayer;
140
171
  }
141
172
  export type WorldItemPickupResult = {
142
173
  status: "ok";
@@ -151,6 +182,7 @@ export interface SceneWorldItemContext {
151
182
  list(): readonly WorldItemRecord[];
152
183
  nearestInRadius(from: EntityPosition, radius: number, filter?: (record: WorldItemRecord) => boolean): string | null;
153
184
  pickup(instanceId: string, userId: string): WorldItemPickupResult;
185
+ consume(instanceId: string): WorldItemRecord | null;
154
186
  }
155
187
  export interface GameContextCommands {
156
188
  define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void;
@@ -180,12 +212,31 @@ export interface GameContextItemUse {
180
212
  can(input: ItemUseInput): ItemUseRejection | null;
181
213
  use(input: ItemUseInput): ItemUseResult<GameContext>;
182
214
  }
215
+ export interface GameContextWorld {
216
+ ground: TerrainField;
217
+ groundHeightAt(x: number, z: number): number;
218
+ }
219
+ export interface GameContextCards {
220
+ /** Lazily creates (on first call, `config` required) or returns the existing notify-wrapped pile for `id`. */
221
+ pile(id: string, config?: CardPileConfig): CardPile;
222
+ }
223
+ export interface GameContextTurn {
224
+ /** Lazily creates (on first call, `config` required) or returns the existing notify-wrapped turn loop for `id`. */
225
+ loop(id: string, config?: TurnLoopConfig): TurnLoop;
226
+ }
227
+ export interface GameContextRace {
228
+ /** Lazily creates (on first call, `config` required) or returns the existing reactive race for `id` — discrete mutations and eventful `update` calls bump `ctx.version()`, so HUDs stop hand-managing `store.set` (#286.2). */
229
+ state(id: string, config?: RaceStateConfig): RaceState;
230
+ }
183
231
  export interface GameContext {
184
232
  scene: {
185
233
  object: SceneObjectContext;
186
234
  entity: SceneEntityContext;
187
235
  worldItem: SceneWorldItemContext;
236
+ raycast(input: SceneRaycastInput): SceneRaycastHit | null;
237
+ raycastAll(input: SceneRaycastInput): readonly SceneRaycastHit[];
188
238
  };
239
+ world: GameContextWorld;
189
240
  game: {
190
241
  commands: GameContextCommands;
191
242
  events: GameEvents;
@@ -194,10 +245,16 @@ export interface GameContext {
194
245
  trade: TradeSystem;
195
246
  quest: QuestJournal;
196
247
  social: Social;
248
+ chat: Chat;
197
249
  unlocks: Unlocks;
198
250
  economy: GameContextEconomy;
199
251
  leaderboard: Leaderboard;
200
252
  roster: Roster;
253
+ /** Game-defined keyed reactive store slot (#163.1); mutations bump `ctx.version()`/notify `ctx.subscribe`. */
254
+ store: ObservableKeyedStore<unknown>;
255
+ cards: GameContextCards;
256
+ turn: GameContextTurn;
257
+ race: GameContextRace;
201
258
  };
202
259
  player: {
203
260
  userId: string;
@@ -211,12 +268,18 @@ export interface GameContext {
211
268
  movement: PoseState;
212
269
  possession: Possession;
213
270
  cosmetics: Cosmetics;
271
+ /** Vertical-motion seam into the shell-owned FrameDriver (#162.4); see `MotionIntents`. */
272
+ motion: MotionIntents;
214
273
  };
215
274
  item: {
216
275
  use: GameContextItemUse;
217
276
  weapon: WeaponStats;
218
277
  };
219
278
  time: SimClock;
279
+ /** Runtime camera-follow/cinematic override (#196.2); the shell reads `followedEntityId()` each frame. */
280
+ camera: CameraDirector;
281
+ /** Held-input snapshot published by the shell each frame before `onTick` (#164.1). */
282
+ input: InputSnapshot;
220
283
  subscribe(listener: () => void): () => void;
221
284
  version(): number;
222
285
  }
@@ -1,3 +1,4 @@
1
+ import { createCardPile } from "../cards/cardPile.js";
1
2
  import { createDeathSystem, deathReasonFromEffect, normalizeOnDeath } from "../combat/death.js";
2
3
  import { createEffectSystem, } from "../combat/effects.js";
3
4
  import { createProjectileSystem } from "../combat/projectiles.js";
@@ -6,6 +7,7 @@ import { pointInTelegraph, } from "../combat/telegraph.js";
6
7
  import { createCommandRegistry, } from "../commands/commandRegistry.js";
7
8
  import { balance as walletBalance, canAfford as walletCanAfford, charge as walletCharge, chargeAll as walletChargeAll, createEmptyWallet, grant as walletGrant, } from "../economy/wallet.js";
8
9
  import { createCosmetics } from "../game/cosmetics.js";
10
+ import { groundFieldFor } from "../world/terrain.js";
9
11
  import { createGameEvents } from "../game/events.js";
10
12
  import { createGameFeed } from "../game/feed.js";
11
13
  import { createLeaderboard } from "../game/leaderboard.js";
@@ -13,6 +15,7 @@ import { createLoadouts } from "../game/loadout.js";
13
15
  import { createLootRegistry, grantDrops } from "../game/lootTable.js";
14
16
  import { createQuestJournal } from "../game/quest.js";
15
17
  import { createWorldItemStore, resolveDeathDrops, DEFAULT_RARITY, WORLD_ITEM_ENTITY_NAME, } from "../game/worldItem.js";
18
+ import { createChat } from "../game/chat.js";
16
19
  import { createSocial } from "../game/social.js";
17
20
  import { createTradeSystem } from "../game/trade.js";
18
21
  import { createUnlocks } from "../game/unlocks.js";
@@ -20,21 +23,32 @@ import { createInventorySet, putItem, } from "../inventory/inventoryModel.js";
20
23
  import { createItemUse, } from "../item/use.js";
21
24
  import { createWeaponStats } from "../item/weapon.js";
22
25
  import { createPoseState } from "../movement/poseState.js";
26
+ import { createPaintLayer } from "../scene/paintLayer.js";
23
27
  import { createEntityStatsApi, seedStatValues, setStatValue, } from "../scene/entityStats.js";
24
28
  import { createForms } from "../scene/form.js";
29
+ import { raycastObjects, raycastObjectsAll } from "../scene/objectQuery.js";
25
30
  import { createObjectStore } from "../scene/objectStore.js";
26
31
  import { createRoster } from "../scene/roster.js";
27
32
  import { createPossession } from "../scene/possession.js";
33
+ import { createSceneRaycast, } from "../scene/sceneRaycast.js";
28
34
  import { createSpatialApi } from "../scene/spatial.js";
29
35
  import { createTargeting } from "../scene/targeting.js";
30
36
  import { createStats } from "../stats/statModifiers.js";
31
37
  import { createChangeSignal, notifyAfter } from "../store/changeSignal.js";
38
+ import { createObservableKeyedStore } from "../store/observableKeyedStore.js";
32
39
  import { createSimClock } from "../time/simClock.js";
40
+ import { createTurnLoop } from "../turn/turnLoop.js";
41
+ import { RaceState } from "../game/race.js";
42
+ import { createCameraDirector } from "./cameraDirector.js";
43
+ import { createInputSnapshot } from "./inputSnapshot.js";
44
+ import { createMotionIntents } from "./motionIntents.js";
33
45
  export function createGameContext(options) {
34
46
  const { definition, content, player } = options;
35
47
  const now = options.now ?? Date.now;
48
+ const occluder = options.occluder;
36
49
  const signal = createChangeSignal();
37
50
  const time = createSimClock({ config: definition.time, onChange: signal.notify });
51
+ const ground = groundFieldFor(definition.world);
38
52
  const entities = definition.scene;
39
53
  const objects = createObjectStore();
40
54
  entities.subscribe(signal.notify);
@@ -57,12 +71,46 @@ export function createGameContext(options) {
57
71
  const object = objects.get(instanceId);
58
72
  return object === null ? undefined : content.objectById?.(object.catalogId);
59
73
  }
74
+ let spatialGeneration = 0;
75
+ const candidateIds = [];
76
+ let candidateIdsDirty = true;
77
+ function refreshCandidateIds() {
78
+ if (!candidateIdsDirty)
79
+ return candidateIds;
80
+ candidateIds.length = 0;
81
+ for (const entity of entities.list())
82
+ candidateIds.push(entity.id);
83
+ candidateIdsDirty = false;
84
+ return candidateIds;
85
+ }
60
86
  const spatial = createSpatialApi({
61
87
  resolvePosition: (instanceId) => entities.get(instanceId)?.position,
62
- candidates: () => entities.list().map((entity) => entity.id),
88
+ candidates: refreshCandidateIds,
89
+ grid: { cellSize: 8 },
90
+ getVersion: () => spatialGeneration,
91
+ ...(occluder !== undefined ? { occluder } : {}),
63
92
  });
93
+ entities.subscribe(() => {
94
+ candidateIdsDirty = true;
95
+ spatialGeneration += 1;
96
+ spatial.invalidate();
97
+ });
98
+ const entityColliders = new Map();
99
+ const objectColliders = new Map();
100
+ function entityCollidersOf(instanceId) {
101
+ const override = entityColliders.get(instanceId);
102
+ if (override !== undefined)
103
+ return override;
104
+ return catalogEntry(instanceId)?.colliders ?? null;
105
+ }
106
+ function objectCollidersOf(instanceId) {
107
+ const override = objectColliders.get(instanceId);
108
+ if (override !== undefined)
109
+ return override;
110
+ return catalogObject(instanceId)?.colliders ?? null;
111
+ }
64
112
  const targeting = notifyAfter(createTargeting({
65
- candidates: () => entities.list().map((entity) => entity.id),
113
+ candidates: () => [...refreshCandidateIds()],
66
114
  classify(_fromId, toId) {
67
115
  const role = catalogEntry(toId)?.role;
68
116
  return role === "enemy" || role === "hostile" ? "hostile" : "friendly";
@@ -71,9 +119,43 @@ export function createGameContext(options) {
71
119
  }), ["setTarget", "cycleTarget"], signal.notify);
72
120
  const combatSpatial = {
73
121
  inRadius: (center, radius) => spatial.inRadius(center, radius),
74
- hasLineOfSight: (from, to) => typeof from === "string" ? spatial.hasLineOfSight(from, to) : entities.get(to) !== null,
122
+ hasLineOfSight: (from, to) => {
123
+ if (typeof from === "string")
124
+ return spatial.hasLineOfSight(from, to);
125
+ const toPos = entities.get(to)?.position;
126
+ if (toPos === undefined)
127
+ return false;
128
+ if (occluder === undefined)
129
+ return true;
130
+ return !occluder(from, toPos);
131
+ },
75
132
  positionOf: (instanceId) => entities.get(instanceId)?.position,
76
133
  };
134
+ const sceneRaycast = createSceneRaycast({
135
+ entities: {
136
+ list: () => entities.list().map((entity) => ({
137
+ id: entity.id,
138
+ position: entity.position,
139
+ rotationY: entity.rotationY,
140
+ name: entity.name,
141
+ })),
142
+ collidersOf: entityCollidersOf,
143
+ inRadius: (center, radius) => spatial.inRadius(center, radius),
144
+ get: (id) => {
145
+ const entity = entities.get(id);
146
+ if (entity === null)
147
+ return null;
148
+ return { id: entity.id, position: entity.position, rotationY: entity.rotationY, name: entity.name };
149
+ },
150
+ },
151
+ objects: {
152
+ list: () => objects.list(),
153
+ inBox: (min, max) => objects.inBox(min, max),
154
+ collidersOf: objectCollidersOf,
155
+ halfExtentsOf: (catalogId) => content.objectById?.(catalogId)?.halfExtents ?? null,
156
+ },
157
+ terrain: ground,
158
+ });
77
159
  const weapon = createWeaponStats((itemId) => content.itemById?.(itemId));
78
160
  const rawEvents = createGameEvents();
79
161
  const events = {
@@ -84,7 +166,7 @@ export function createGameContext(options) {
84
166
  signal.notify();
85
167
  },
86
168
  };
87
- const feed = createGameFeed();
169
+ const feed = createGameFeed(definition.feed);
88
170
  const lootRegistry = createLootRegistry();
89
171
  const unlocks = notifyAfter(createUnlocks(), ["grant", "hydrate"], signal.notify);
90
172
  const rawSocial = createSocial({
@@ -96,11 +178,22 @@ export function createGameContext(options) {
96
178
  },
97
179
  });
98
180
  const social = {
99
- friends: notifyAfter(rawSocial.friends, ["request", "accept", "remove", "block", "hydrate"], signal.notify),
100
- party: notifyAfter(rawSocial.party, ["invite", "accept", "kick", "leave", "promote"], signal.notify),
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),
101
183
  presence: rawSocial.presence,
102
184
  emotes: rawSocial.emotes,
185
+ worldInvites: notifyAfter(rawSocial.worldInvites, ["invite", "accept", "decline"], signal.notify),
103
186
  };
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);
104
197
  const leaderboard = notifyAfter(createLeaderboard(), ["increment", "hydrate"], signal.notify);
105
198
  const roster = notifyAfter(createRoster({ now }), ["capture", "release", "setEquipped", "hydrate"], signal.notify);
106
199
  const playerStats = createStats({});
@@ -110,6 +203,7 @@ export function createGameContext(options) {
110
203
  const possession = notifyAfter(createPossession({ entities, events }), ["possess", "own", "disown"], signal.notify);
111
204
  const forms = notifyAfter(createForms({ entities, time, events }), ["shapeshift", "revert"], signal.notify);
112
205
  const cosmetics = notifyAfter(createCosmetics({ events }), ["apply", "equip", "hydrate"], signal.notify);
206
+ const paintLayer = notifyAfter(createPaintLayer(), ["paint", "clear"], signal.notify);
113
207
  const inventoryDeclarations = definition.inventories ?? {};
114
208
  const inventoryIds = Object.keys(inventoryDeclarations);
115
209
  const layouts = {};
@@ -259,13 +353,24 @@ export function createGameContext(options) {
259
353
  statsByInstance.delete(instanceId);
260
354
  targeting.clearAll(instanceId);
261
355
  pose.clear(instanceId);
356
+ entityColliders.delete(instanceId);
262
357
  return existed;
263
358
  }
359
+ function resetAllToSpawn(filter) {
360
+ let count = 0;
361
+ for (const entity of entities.list()) {
362
+ if (filter !== undefined && !filter(entity))
363
+ continue;
364
+ if (entities.resetToSpawn(entity.id))
365
+ count += 1;
366
+ }
367
+ return count;
368
+ }
264
369
  const worldItems = notifyAfter(createWorldItemStore({
265
370
  spawnEntity: (position) => entities.spawn(WORLD_ITEM_ENTITY_NAME, { position, role: "prop" }),
266
371
  despawnEntity,
267
372
  resolvePosition: (instanceId) => entities.get(instanceId)?.position,
268
- }), ["spawn", "take"], signal.notify);
373
+ }), ["spawn", "take", "remove"], signal.notify);
269
374
  function spawnWorldItem(input) {
270
375
  const record = worldItems.spawn(input);
271
376
  events.emit("worldItem.dropped", {
@@ -389,13 +494,14 @@ export function createGameContext(options) {
389
494
  if (input.dir !== undefined)
390
495
  telegraphEvent.dir = input.dir;
391
496
  events.emit("combat.telegraph", telegraphEvent);
497
+ const cancelVisual = () => events.emit("combat.telegraphCancelled", { id });
392
498
  const bound = input.effect;
393
499
  if (bound === undefined)
394
- return () => { };
500
+ return cancelVisual;
395
501
  const config = { shape: input.shape, at: input.at, windupMs: input.windupMs };
396
502
  if (input.dir !== undefined)
397
503
  config.dir = input.dir;
398
- return time.after(input.windupMs / 1000, () => {
504
+ const cancelEffect = time.after(input.windupMs / 1000, () => {
399
505
  const targets = entities.list().filter((entity) => pointInTelegraph(config, entity.position));
400
506
  for (const target of targets) {
401
507
  applyEffectAndFloat({
@@ -406,6 +512,10 @@ export function createGameContext(options) {
406
512
  });
407
513
  }
408
514
  });
515
+ return () => {
516
+ cancelEffect();
517
+ cancelVisual();
518
+ };
409
519
  }
410
520
  function applyHitReaction(input) {
411
521
  const attacker = entities.get(input.from);
@@ -466,10 +576,51 @@ export function createGameContext(options) {
466
576
  preview: effects.preview,
467
577
  applyEffect: applyEffectAndFloat,
468
578
  };
579
+ const projectileObstacles = definition.physics?.projectileObstacles === true;
580
+ const projectileSceneRaycast = {
581
+ raycast(input) {
582
+ return sceneRaycast.raycast({
583
+ ...input,
584
+ filter: {
585
+ entities: true,
586
+ objects: projectileObstacles,
587
+ terrain: false,
588
+ walls: projectileObstacles,
589
+ ...input.filter,
590
+ },
591
+ });
592
+ },
593
+ raycastAll(input) {
594
+ return sceneRaycast.raycastAll({
595
+ ...input,
596
+ filter: {
597
+ entities: true,
598
+ objects: projectileObstacles,
599
+ terrain: false,
600
+ walls: projectileObstacles,
601
+ ...input.filter,
602
+ },
603
+ });
604
+ },
605
+ };
469
606
  const projectiles = notifyAfter(createProjectileSystem({
470
607
  effects: floatingEffects,
471
608
  spatial: combatSpatial,
472
609
  getStat: weapon.getStat,
610
+ sceneRaycast: projectileSceneRaycast,
611
+ objects: projectileObstacles
612
+ ? {
613
+ list: () => objects.list(),
614
+ inBox: (min, max) => objects.inBox(min, max),
615
+ halfExtents: (catalogId) => {
616
+ const half = content.objectById?.(catalogId)?.halfExtents;
617
+ return half === undefined ? null : [half[0], half[1], half[2]];
618
+ },
619
+ collidersOf: objectCollidersOf,
620
+ }
621
+ : undefined,
622
+ entityCollidersOf,
623
+ rotationYOf: (instanceId) => entities.get(instanceId)?.rotationY,
473
624
  now,
474
625
  onSettle(report) {
475
626
  events.emit("projectile.settled", {
@@ -484,16 +635,111 @@ export function createGameContext(options) {
484
635
  const sceneObjects = {
485
636
  ...objects,
486
637
  catalog: (instanceId) => catalogObject(instanceId) ?? null,
638
+ raycast: (input) => raycastObjects(objects, input),
639
+ raycastAll: (input) => raycastObjectsAll(objects, input),
640
+ setColliders(instanceId, colliders) {
641
+ if (colliders === null)
642
+ objectColliders.delete(instanceId);
643
+ else
644
+ objectColliders.set(instanceId, colliders);
645
+ signal.notify();
646
+ },
647
+ collidersOf: objectCollidersOf,
487
648
  };
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`);
657
+ }
658
+ const created = notifyAfter(createCardPile(config), ["shuffle", "draw", "discard", "exhaust", "move", "reset"], signal.notify);
659
+ cardPiles.set(id, created);
660
+ return created;
661
+ }
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;
689
+ }
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();
702
+ }
703
+ eliminate(racerId) {
704
+ super.eliminate(racerId);
705
+ signal.notify();
706
+ }
707
+ update(now, positions) {
708
+ const raceEvents = super.update(now, positions);
709
+ if (raceEvents.length > 0)
710
+ signal.notify();
711
+ return raceEvents;
712
+ }
713
+ }
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;
725
+ }
726
+ const camera = notifyAfter(createCameraDirector(), ["follow", "setCinematic", "setChaseTuning"], signal.notify);
727
+ const input = createInputSnapshot();
728
+ const motion = createMotionIntents();
488
729
  const ctx = {
489
730
  scene: {
490
731
  object: sceneObjects,
491
732
  entity: {
492
733
  spawn: spawnEntity,
493
734
  despawn: despawnEntity,
735
+ update: entities.update,
494
736
  setPose: entities.setPose,
737
+ setPoseConstraint: entities.setPoseConstraint,
495
738
  get: entities.get,
496
739
  list: entities.list,
740
+ spawnPoseOf: entities.spawnPoseOf,
741
+ resetToSpawn: entities.resetToSpawn,
742
+ resetAllToSpawn,
497
743
  stats: entityStats,
498
744
  floatText: emitFloatText,
499
745
  telegraph: fireTelegraph,
@@ -512,7 +758,17 @@ export function createGameContext(options) {
512
758
  hasLineOfSight: spatial.hasLineOfSight,
513
759
  queryArc: spatial.queryArc,
514
760
  moveToward: spatial.moveToward,
761
+ invalidateSpatial: spatial.invalidate,
762
+ setColliders(instanceId, colliders) {
763
+ if (colliders === null)
764
+ entityColliders.delete(instanceId);
765
+ else
766
+ entityColliders.set(instanceId, colliders);
767
+ signal.notify();
768
+ },
769
+ collidersOf: entityCollidersOf,
515
770
  form: forms,
771
+ paint: paintLayer,
516
772
  },
517
773
  worldItem: {
518
774
  spawn: spawnWorldItem,
@@ -520,7 +776,14 @@ export function createGameContext(options) {
520
776
  list: worldItems.list,
521
777
  nearestInRadius: worldItems.nearestInRadius,
522
778
  pickup: pickupWorldItem,
779
+ consume: worldItems.remove,
523
780
  },
781
+ raycast: (input) => sceneRaycast.raycast(input),
782
+ raycastAll: (input) => sceneRaycast.raycastAll(input),
783
+ },
784
+ world: {
785
+ ground,
786
+ groundHeightAt: ground.sampleHeight,
524
787
  },
525
788
  game: {
526
789
  commands: {
@@ -552,10 +815,15 @@ export function createGameContext(options) {
552
815
  trade,
553
816
  quest,
554
817
  social,
818
+ chat,
555
819
  unlocks,
556
820
  economy,
557
821
  leaderboard,
558
822
  roster,
823
+ store,
824
+ cards: { pile },
825
+ turn: { loop },
826
+ race: { state: raceState },
559
827
  },
560
828
  player: {
561
829
  userId: player.userId,
@@ -567,6 +835,7 @@ export function createGameContext(options) {
567
835
  movement: pose,
568
836
  possession,
569
837
  cosmetics,
838
+ motion,
570
839
  },
571
840
  item: {
572
841
  use: {
@@ -582,6 +851,8 @@ export function createGameContext(options) {
582
851
  weapon,
583
852
  },
584
853
  time,
854
+ camera,
855
+ input,
585
856
  subscribe: signal.subscribe,
586
857
  version: signal.version,
587
858
  };
@@ -5,7 +5,7 @@ import { type GameRuntimeSnapshot, type RuntimeChunkRow, type RuntimePlayerRow,
5
5
  export type ServerLoopHooks = {
6
6
  onInit?: (ctx: RuntimeInitContext) => void;
7
7
  onNewPlayer?: (ctx: RuntimeLoopContext) => void;
8
- onTick?: (ctx: RuntimeLoopContext, dtSeconds: number) => void;
8
+ onTick?: (ctx: RuntimeWorldContext, dtSeconds: number) => void;
9
9
  };
10
10
  export type RuntimeInitContext = {
11
11
  snapshot: GameRuntimeSnapshot;
@@ -17,6 +17,9 @@ export type RuntimeLoopContext = RuntimeInitContext & {
17
17
  isNew: boolean;
18
18
  };
19
19
  };
20
+ export type RuntimeWorldContext = RuntimeInitContext & {
21
+ playerIds: readonly string[];
22
+ };
20
23
  export type GameRuntimeDefinition = {
21
24
  gameId: string;
22
25
  save: SaveConfig;
@@ -29,6 +32,7 @@ export type HydrateInput = {
29
32
  serverRow: RuntimeServerRow;
30
33
  playersByUserId: Record<string, RuntimePlayerRow>;
31
34
  chunksByKey: Record<string, RuntimeChunkRow>;
35
+ revision?: number;
32
36
  };
33
37
  export type GameRuntime = {
34
38
  gameId: string;