@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
@@ -72,12 +72,25 @@ export function createSocial(deps) {
72
72
  friendRequests.delete(requestId);
73
73
  }
74
74
  },
75
+ decline(userId, requestId) {
76
+ const request = friendRequests.get(requestId);
77
+ if (request !== undefined && request.to === userId)
78
+ friendRequests.delete(requestId);
79
+ },
75
80
  list(userId) {
76
81
  return Array.from(friendships.get(userId) ?? [], (friendUserId) => ({
77
82
  userId: friendUserId,
78
83
  online: deps.presence?.(friendUserId).online ?? false,
79
84
  }));
80
85
  },
86
+ requestsFor(userId) {
87
+ const entries = [];
88
+ for (const [requestId, request] of friendRequests) {
89
+ if (request.to === userId)
90
+ entries.push({ requestId, fromUserId: request.from });
91
+ }
92
+ return entries;
93
+ },
81
94
  snapshot(userId) {
82
95
  return {
83
96
  friends: Array.from(friendships.get(userId) ?? []),
@@ -212,9 +225,117 @@ export function createSocial(deps) {
212
225
  role: member === current.leader ? "leader" : "member",
213
226
  }));
214
227
  },
228
+ decline(userId, inviteId) {
229
+ const invite = partyInvites.get(inviteId);
230
+ if (invite !== undefined && invite.to === userId)
231
+ partyInvites.delete(inviteId);
232
+ },
215
233
  membersOf(userId) {
216
234
  return partyOf(userId)?.members.slice() ?? [];
217
235
  },
236
+ invitesFor(userId) {
237
+ const ttlMs = partyConfig?.inviteTtlMs ?? DEFAULT_INVITE_TTL_MS;
238
+ const cutoff = now() - ttlMs;
239
+ const entries = [];
240
+ for (const [inviteId, invite] of partyInvites) {
241
+ if (invite.createdAt < cutoff) {
242
+ partyInvites.delete(inviteId);
243
+ continue;
244
+ }
245
+ if (invite.to === userId) {
246
+ entries.push({ inviteId, fromUserId: invite.from, createdAt: invite.createdAt });
247
+ }
248
+ }
249
+ return entries;
250
+ },
251
+ };
252
+ const worldInviteTtlMs = deps.worldInviteTtlMs ?? DEFAULT_INVITE_TTL_MS;
253
+ const worldInviteEntries = new Map();
254
+ let worldInviteCounter = 0;
255
+ function pruneWorldInvites() {
256
+ const cutoff = now() - worldInviteTtlMs;
257
+ for (const [id, entry] of worldInviteEntries) {
258
+ if (entry.createdAt < cutoff)
259
+ worldInviteEntries.delete(id);
260
+ }
261
+ }
262
+ function canWorldInvite(fromUserId, toUserId) {
263
+ if (fromUserId === toUserId)
264
+ return { reason: "cannot invite yourself" };
265
+ if (blocked.get(toUserId)?.has(fromUserId) || blocked.get(fromUserId)?.has(toUserId)) {
266
+ return { reason: "blocked" };
267
+ }
268
+ pruneWorldInvites();
269
+ for (const entry of worldInviteEntries.values()) {
270
+ if (entry.fromUserId === fromUserId && entry.toUserId === toUserId) {
271
+ return { reason: "invite already pending" };
272
+ }
273
+ }
274
+ return null;
275
+ }
276
+ const worldInvites = {
277
+ canInvite: canWorldInvite,
278
+ invite(fromUserId, toUserId, target) {
279
+ const denied = canWorldInvite(fromUserId, toUserId);
280
+ if (denied !== null)
281
+ return denied;
282
+ worldInviteCounter += 1;
283
+ const inviteId = `winv_${worldInviteCounter}`;
284
+ const entry = {
285
+ id: inviteId,
286
+ fromUserId,
287
+ toUserId,
288
+ serverId: target.serverId,
289
+ createdAt: now(),
290
+ };
291
+ if (target.joinCode !== undefined)
292
+ entry.joinCode = target.joinCode;
293
+ worldInviteEntries.set(inviteId, entry);
294
+ const invited = {
295
+ inviteId,
296
+ fromUserId,
297
+ toUserId,
298
+ serverId: target.serverId,
299
+ };
300
+ if (target.joinCode !== undefined)
301
+ invited.joinCode = target.joinCode;
302
+ events.emit("social.world.invited", invited);
303
+ return { inviteId };
304
+ },
305
+ accept(userId, inviteId) {
306
+ const entry = worldInviteEntries.get(inviteId);
307
+ if (entry === undefined)
308
+ return { reason: `unknown invite "${inviteId}"` };
309
+ if (entry.toUserId !== userId)
310
+ return { reason: "invite is not addressed to this user" };
311
+ if (now() - entry.createdAt > worldInviteTtlMs) {
312
+ worldInviteEntries.delete(inviteId);
313
+ return { reason: "invite expired" };
314
+ }
315
+ worldInviteEntries.delete(inviteId);
316
+ const target = { serverId: entry.serverId };
317
+ if (entry.joinCode !== undefined)
318
+ target.joinCode = entry.joinCode;
319
+ const accepted = {
320
+ inviteId,
321
+ userId,
322
+ fromUserId: entry.fromUserId,
323
+ serverId: entry.serverId,
324
+ };
325
+ if (entry.joinCode !== undefined)
326
+ accepted.joinCode = entry.joinCode;
327
+ events.emit("social.world.accepted", accepted);
328
+ return { target };
329
+ },
330
+ decline(userId, inviteId) {
331
+ const entry = worldInviteEntries.get(inviteId);
332
+ if (entry !== undefined && entry.toUserId === userId)
333
+ worldInviteEntries.delete(inviteId);
334
+ },
335
+ listFor(userId) {
336
+ pruneWorldInvites();
337
+ return Array.from(worldInviteEntries.values()).filter((entry) => entry.toUserId === userId);
338
+ },
218
339
  };
219
340
  const emotes = {
220
341
  play(fromUserId, emoteId, radius) {
@@ -239,5 +360,6 @@ export function createSocial(deps) {
239
360
  },
240
361
  },
241
362
  emotes,
363
+ worldInvites,
242
364
  };
243
365
  }
@@ -0,0 +1,19 @@
1
+ import type { EntityPose } from "../scene/entityStore.js";
2
+ export interface SpawnPointPose {
3
+ x: number;
4
+ y: number;
5
+ z: number;
6
+ rotationY?: number;
7
+ }
8
+ export interface RespawnTarget {
9
+ setPose(id: string, pose: EntityPose): boolean;
10
+ }
11
+ export interface SpawnPoints {
12
+ record(id: string, pose: SpawnPointPose): void;
13
+ get(id: string): SpawnPointPose | undefined;
14
+ list(): readonly (SpawnPointPose & {
15
+ id: string;
16
+ })[];
17
+ respawn(entities: RespawnTarget, entityId: string, spawnId: string): boolean;
18
+ }
19
+ export declare function createSpawnPoints(): SpawnPoints;
@@ -0,0 +1,23 @@
1
+ export function createSpawnPoints() {
2
+ const points = new Map();
3
+ return {
4
+ record(id, pose) {
5
+ points.set(id, { ...pose });
6
+ },
7
+ get(id) {
8
+ return points.get(id);
9
+ },
10
+ list() {
11
+ return Array.from(points, ([id, pose]) => ({ id, ...pose }));
12
+ },
13
+ respawn(entities, entityId, spawnId) {
14
+ const pose = points.get(spawnId);
15
+ if (pose === undefined)
16
+ return false;
17
+ return entities.setPose(entityId, {
18
+ position: { x: pose.x, y: pose.y, z: pose.z },
19
+ ...(pose.rotationY === undefined ? {} : { rotationY: pose.rotationY }),
20
+ });
21
+ },
22
+ };
23
+ }
@@ -0,0 +1,47 @@
1
+ import type { StatModifierSet } from "../stats/statModifiers.js";
2
+ export type TalentRequirement = string | {
3
+ nodeId: string;
4
+ rank: number;
5
+ };
6
+ export interface TalentNodeDef<TStat extends string = string> {
7
+ id: string;
8
+ branch?: string;
9
+ maxRank: number;
10
+ requires?: readonly TalentRequirement[];
11
+ requiresPointsInBranch?: number;
12
+ modifiersPerRank?: StatModifierSet<TStat>;
13
+ grantsAbilities?: readonly string[];
14
+ }
15
+ export type TalentAllocateReason = "unknown-node" | "max-rank" | "no-points" | "requires" | "branch-points";
16
+ export type TalentAllocateResult = {
17
+ ok: true;
18
+ } | {
19
+ ok: false;
20
+ reason: TalentAllocateReason;
21
+ };
22
+ export interface ResolvedTalents<TStat extends string = string> {
23
+ stats: StatModifierSet<TStat>;
24
+ abilities: readonly string[];
25
+ }
26
+ export interface TalentSnapshot {
27
+ points: number;
28
+ ranks: Record<string, number>;
29
+ }
30
+ export interface TalentTreeConfig<TStat extends string = string> {
31
+ nodes: readonly TalentNodeDef<TStat>[];
32
+ points?: number;
33
+ }
34
+ export interface TalentTree<TStat extends string = string> {
35
+ rank(nodeId: string): number;
36
+ pointsAvailable(): number;
37
+ pointsSpent(): number;
38
+ pointsInBranch(branch: string): number;
39
+ grantPoints(amount: number): void;
40
+ canAllocate(nodeId: string): TalentAllocateResult;
41
+ allocate(nodeId: string): TalentAllocateResult;
42
+ resolved(): ResolvedTalents<TStat>;
43
+ reset(): void;
44
+ snapshot(): TalentSnapshot;
45
+ hydrate(snapshot: TalentSnapshot): void;
46
+ }
47
+ export declare function createTalentTree<TStat extends string = string>(config: TalentTreeConfig<TStat>): TalentTree<TStat>;
@@ -0,0 +1,140 @@
1
+ const DEFAULT_BRANCH = "";
2
+ export function createTalentTree(config) {
3
+ const nodesById = new Map();
4
+ for (const node of config.nodes)
5
+ nodesById.set(node.id, node);
6
+ const ranks = new Map();
7
+ let points = Math.max(0, config.points ?? 0);
8
+ let cache = null;
9
+ function rank(nodeId) {
10
+ return ranks.get(nodeId) ?? 0;
11
+ }
12
+ function branchOf(node) {
13
+ return node.branch ?? DEFAULT_BRANCH;
14
+ }
15
+ function pointsInBranch(branch) {
16
+ let total = 0;
17
+ for (const node of nodesById.values()) {
18
+ if (branchOf(node) === branch)
19
+ total += rank(node.id);
20
+ }
21
+ return total;
22
+ }
23
+ function pointsSpent() {
24
+ let total = 0;
25
+ for (const value of ranks.values())
26
+ total += value;
27
+ return total;
28
+ }
29
+ function requirementUnmet(requirement) {
30
+ const reqNodeId = typeof requirement === "string" ? requirement : requirement.nodeId;
31
+ const reqRank = typeof requirement === "string" ? 1 : requirement.rank;
32
+ return rank(reqNodeId) < reqRank;
33
+ }
34
+ function canAllocate(nodeId) {
35
+ const node = nodesById.get(nodeId);
36
+ if (!node)
37
+ return { ok: false, reason: "unknown-node" };
38
+ const currentRank = rank(nodeId);
39
+ if (currentRank >= node.maxRank)
40
+ return { ok: false, reason: "max-rank" };
41
+ if (points < 1)
42
+ return { ok: false, reason: "no-points" };
43
+ for (const requirement of node.requires ?? []) {
44
+ if (requirementUnmet(requirement))
45
+ return { ok: false, reason: "requires" };
46
+ }
47
+ if (node.requiresPointsInBranch !== undefined) {
48
+ const inBranch = pointsInBranch(branchOf(node)) - currentRank;
49
+ if (inBranch < node.requiresPointsInBranch)
50
+ return { ok: false, reason: "branch-points" };
51
+ }
52
+ return { ok: true };
53
+ }
54
+ function resolve() {
55
+ const stats = {};
56
+ const abilities = [];
57
+ const seenAbilities = new Set();
58
+ for (const node of nodesById.values()) {
59
+ const nodeRank = rank(node.id);
60
+ if (nodeRank <= 0)
61
+ continue;
62
+ if (node.modifiersPerRank) {
63
+ for (const stat of Object.keys(node.modifiersPerRank)) {
64
+ const modifier = node.modifiersPerRank[stat];
65
+ if (!modifier)
66
+ continue;
67
+ const entry = stats[stat] ?? {};
68
+ if (modifier.add !== undefined)
69
+ entry.add = (entry.add ?? 0) + modifier.add * nodeRank;
70
+ if (modifier.multiply !== undefined) {
71
+ entry.multiply = (entry.multiply ?? 1) * Math.pow(modifier.multiply, nodeRank);
72
+ }
73
+ stats[stat] = entry;
74
+ }
75
+ }
76
+ if (node.grantsAbilities) {
77
+ for (const ability of node.grantsAbilities) {
78
+ if (seenAbilities.has(ability))
79
+ continue;
80
+ seenAbilities.add(ability);
81
+ abilities.push(ability);
82
+ }
83
+ }
84
+ }
85
+ return { stats, abilities };
86
+ }
87
+ return {
88
+ rank,
89
+ pointsAvailable() {
90
+ return points;
91
+ },
92
+ pointsSpent,
93
+ pointsInBranch,
94
+ grantPoints(amount) {
95
+ if (amount > 0)
96
+ points += amount;
97
+ },
98
+ canAllocate,
99
+ allocate(nodeId) {
100
+ const result = canAllocate(nodeId);
101
+ if (!result.ok)
102
+ return result;
103
+ points -= 1;
104
+ ranks.set(nodeId, rank(nodeId) + 1);
105
+ cache = null;
106
+ return { ok: true };
107
+ },
108
+ resolved() {
109
+ if (cache === null)
110
+ cache = resolve();
111
+ return cache;
112
+ },
113
+ reset() {
114
+ points += pointsSpent();
115
+ ranks.clear();
116
+ cache = null;
117
+ },
118
+ snapshot() {
119
+ const snapshotRanks = {};
120
+ for (const [nodeId, value] of ranks) {
121
+ if (value > 0)
122
+ snapshotRanks[nodeId] = value;
123
+ }
124
+ return { points, ranks: snapshotRanks };
125
+ },
126
+ hydrate(snapshot) {
127
+ ranks.clear();
128
+ points = snapshot.points;
129
+ for (const [nodeId, value] of Object.entries(snapshot.ranks)) {
130
+ const node = nodesById.get(nodeId);
131
+ if (!node)
132
+ continue;
133
+ const clamped = Math.max(0, Math.min(value, node.maxRank));
134
+ if (clamped > 0)
135
+ ranks.set(nodeId, clamped);
136
+ }
137
+ cache = null;
138
+ },
139
+ };
140
+ }
@@ -44,7 +44,10 @@ export interface WorldItemStore {
44
44
  get(instanceId: string): WorldItemRecord | null;
45
45
  list(): readonly WorldItemRecord[];
46
46
  nearestInRadius(from: EntityPosition, radius: number, filter?: (record: WorldItemRecord) => boolean): string | null;
47
+ /** Detaches the record for a pickup: caller is expected to grant its contents (e.g. `loot.grantToPlayer`) itself. */
47
48
  take(instanceId: string): WorldItemRecord | null;
49
+ /** Consumes/destroys the record with no grant implied (decay, an explosion, a cleanup sweep) — same despawn path as `take`, leaves no orphaned record. */
50
+ remove(instanceId: string): WorldItemRecord | null;
48
51
  }
49
52
  /** Pure pickup-radius + nearest-item selection, usable without a live store (click-to-grab, proximity prompts). */
50
53
  export declare function selectNearestWorldItem(candidates: readonly {
@@ -33,6 +33,14 @@ export function selectNearestWorldItem(candidates, from, radius) {
33
33
  export function createWorldItemStore(deps) {
34
34
  const records = new Map();
35
35
  const now = deps.now ?? Date.now;
36
+ function removeRecord(instanceId) {
37
+ const record = records.get(instanceId);
38
+ if (record === undefined)
39
+ return null;
40
+ records.delete(instanceId);
41
+ deps.despawnEntity(instanceId);
42
+ return record;
43
+ }
36
44
  return {
37
45
  spawn(input) {
38
46
  const instanceId = deps.spawnEntity(input.position);
@@ -69,12 +77,10 @@ export function createWorldItemStore(deps) {
69
77
  return selectNearestWorldItem(candidates, from, radius);
70
78
  },
71
79
  take(instanceId) {
72
- const record = records.get(instanceId);
73
- if (record === undefined)
74
- return null;
75
- records.delete(instanceId);
76
- deps.despawnEntity(instanceId);
77
- return record;
80
+ return removeRecord(instanceId);
81
+ },
82
+ remove(instanceId) {
83
+ return removeRecord(instanceId);
78
84
  },
79
85
  };
80
86
  }
package/dist/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./runtime/commandRunner.js";
4
4
  export * from "./runtime/gameRuntime.js";
5
5
  export * from "./runtime/adapter.js";
6
6
  export * from "./runtime/transport.js";
7
+ export * from "./runtime/visibility.js";
package/dist/index.js CHANGED
@@ -4,3 +4,4 @@ export * from "./runtime/commandRunner.js";
4
4
  export * from "./runtime/gameRuntime.js";
5
5
  export * from "./runtime/adapter.js";
6
6
  export * from "./runtime/transport.js";
7
+ export * from "./runtime/visibility.js";
@@ -27,8 +27,18 @@ export type ActionStateBindingMap<TAction extends string, TCode extends string =
27
27
  export type ActionCodes<TCode extends string = string> = readonly TCode[] | {
28
28
  hold?: readonly TCode[];
29
29
  toggle?: readonly TCode[];
30
+ repeatMs?: number;
30
31
  };
31
32
  export type ActionCodesMap<TAction extends string = string, TCode extends string = string> = Record<TAction, ActionCodes<TCode>>;
33
+ export declare function actionRepeatMs(codes: ActionCodes | undefined): number | undefined;
34
+ export interface ShouldDispatchActionInput {
35
+ pressed: boolean;
36
+ down: boolean;
37
+ repeatMs: number | undefined;
38
+ lastFiredAt: number | null;
39
+ now: number;
40
+ }
41
+ export declare function shouldDispatchAction({ pressed, down, repeatMs, lastFiredAt, now }: ShouldDispatchActionInput): boolean;
32
42
  export declare function toActionStateBindingMap<TAction extends string, TCode extends string>(map: ActionCodesMap<TAction, TCode>): ActionStateBindingMap<TAction, TCode>;
33
43
  export declare function hotbarSlotBindings(count: number, options?: {
34
44
  action?: (slot: number) => string;
@@ -27,6 +27,20 @@ export function normalizeKeyCode(code) {
27
27
  return "Control";
28
28
  return code;
29
29
  }
30
+ export function actionRepeatMs(codes) {
31
+ if (codes === undefined || Array.isArray(codes))
32
+ return undefined;
33
+ return codes.repeatMs;
34
+ }
35
+ export function shouldDispatchAction({ pressed, down, repeatMs, lastFiredAt, now }) {
36
+ if (pressed)
37
+ return true;
38
+ if (repeatMs === undefined || repeatMs <= 0)
39
+ return false;
40
+ if (!down || lastFiredAt === null)
41
+ return false;
42
+ return now - lastFiredAt >= repeatMs;
43
+ }
30
44
  function toBindings(codes) {
31
45
  return codes.map((code) => ({ primary: code, secondary: null }));
32
46
  }
@@ -1,3 +1,4 @@
1
+ import { type PointerAxisBinding, type PointerAxisState } from "./pointerAxis.js";
1
2
  export interface AxisInput {
2
3
  throttle: number;
3
4
  brake: number;
@@ -9,6 +10,8 @@ export type AxisName = keyof AxisInput;
9
10
  export interface AxisBinding {
10
11
  positive: readonly string[];
11
12
  negative?: readonly string[];
13
+ /** Pointer-position source for this axis (#293) — takes over from the key lists whenever a pointer is active. */
14
+ pointer?: PointerAxisBinding;
12
15
  }
13
16
  export type AxisBindingMap = Record<AxisName, AxisBinding>;
14
17
  export interface AxisChannelConfig {
@@ -25,8 +28,9 @@ export declare function rampToward(current: number, target: number, rate: number
25
28
  /**
26
29
  * Analog control channel — distinct from the digital action bindings. Throttle/brake/steer/handbrake
27
30
  * are continuous values ramped from held keys (a keyboard feels like a pedal) or driven directly from
28
- * a gamepad axis via `setAnalog`. `sample(dt, isDown)` folds the current held-key state into the
29
- * smoothed value; a `setAnalog` override replaces the digital target for that axis until cleared.
31
+ * a gamepad axis via `setAnalog`. `sample(dt, isDown, pointer?)` folds the current held-key state into
32
+ * the smoothed value; a binding's `pointer` source takes over from its keys while a pointer is active,
33
+ * and a `setAnalog` override replaces both for that axis until cleared.
30
34
  */
31
35
  export declare class AxisChannel {
32
36
  private readonly bindings;
@@ -38,6 +42,25 @@ export declare class AxisChannel {
38
42
  clearAnalog(axis: AxisName): void;
39
43
  reset(): void;
40
44
  get value(): AxisInput;
41
- sample(dt: number, isDown: (code: string) => boolean): AxisInput;
45
+ sample(dt: number, isDown: (code: string) => boolean, pointer?: PointerAxisState | null): AxisInput;
42
46
  }
43
47
  export declare const DRIVE_AXIS_BINDINGS: AxisBindingMap;
48
+ export interface GenericAxisConfig<TAxes extends string> {
49
+ bindings: Record<TAxes, AxisBinding>;
50
+ /** Per-axis value range; unlisted axes default to bipolar `[-1, 1]` (use `{ min: 0, max: 1 }` for pedals). */
51
+ ranges?: Partial<Record<TAxes, AxisRange>>;
52
+ smoothing?: number;
53
+ }
54
+ /**
55
+ * The held-key-ramping analog channel for any axis schema (#282.7) — drones (pitch/roll/strafe),
56
+ * boats, mechs — not just the four car axes `AxisChannel` hardcodes. Same semantics: keys ramp,
57
+ * `setAnalog` overrides, a binding's `pointer` source takes over while a pointer is active.
58
+ */
59
+ export interface GenericAxisChannel<TAxes extends string> {
60
+ sample(dt: number, isDown: (code: string) => boolean, pointer?: PointerAxisState | null): Record<TAxes, number>;
61
+ setAnalog(axis: TAxes, value: number): void;
62
+ clearAnalog(axis: TAxes): void;
63
+ reset(): void;
64
+ readonly value: Record<TAxes, number>;
65
+ }
66
+ export declare function createAxisChannel<TAxes extends string>(config: GenericAxisConfig<TAxes>): GenericAxisChannel<TAxes>;
@@ -1,3 +1,4 @@
1
+ import { pointerAxisValue } from "./pointerAxis.js";
1
2
  export const NEUTRAL_AXIS = { throttle: 0, brake: 0, steer: 0, handbrake: 0 };
2
3
  export const AXIS_RANGE = {
3
4
  throttle: { min: 0, max: 1 },
@@ -25,8 +26,9 @@ function digitalTarget(binding, isDown) {
25
26
  /**
26
27
  * Analog control channel — distinct from the digital action bindings. Throttle/brake/steer/handbrake
27
28
  * are continuous values ramped from held keys (a keyboard feels like a pedal) or driven directly from
28
- * a gamepad axis via `setAnalog`. `sample(dt, isDown)` folds the current held-key state into the
29
- * smoothed value; a `setAnalog` override replaces the digital target for that axis until cleared.
29
+ * a gamepad axis via `setAnalog`. `sample(dt, isDown, pointer?)` folds the current held-key state into
30
+ * the smoothed value; a binding's `pointer` source takes over from its keys while a pointer is active,
31
+ * and a `setAnalog` override replaces both for that axis until cleared.
30
32
  */
31
33
  export class AxisChannel {
32
34
  bindings;
@@ -52,11 +54,15 @@ export class AxisChannel {
52
54
  get value() {
53
55
  return this.current;
54
56
  }
55
- sample(dt, isDown) {
57
+ sample(dt, isDown, pointer) {
56
58
  for (const axis of Object.keys(this.bindings)) {
57
59
  const range = AXIS_RANGE[axis];
58
60
  const override = this.overrides[axis];
59
- const target = override !== undefined ? override : clampAxis(digitalTarget(this.bindings[axis], isDown), range);
61
+ const binding = this.bindings[axis];
62
+ const fromPointer = binding.pointer === undefined ? null : pointerAxisValue(binding.pointer, pointer);
63
+ const target = override !== undefined
64
+ ? override
65
+ : clampAxis(fromPointer ?? digitalTarget(binding, isDown), range);
60
66
  this.current[axis] = clampAxis(rampToward(this.current[axis], target, this.smoothing, dt), range);
61
67
  }
62
68
  return this.current;
@@ -68,3 +74,37 @@ export const DRIVE_AXIS_BINDINGS = {
68
74
  steer: { positive: ["KeyD", "ArrowRight"], negative: ["KeyA", "ArrowLeft"] },
69
75
  handbrake: { positive: ["Space"] },
70
76
  };
77
+ const BIPOLAR_RANGE = { min: -1, max: 1 };
78
+ export function createAxisChannel(config) {
79
+ const axes = Object.keys(config.bindings);
80
+ const smoothing = config.smoothing ?? 6;
81
+ const current = Object.fromEntries(axes.map((axis) => [axis, 0]));
82
+ const overrides = {};
83
+ function rangeOf(axis) {
84
+ return config.ranges?.[axis] ?? BIPOLAR_RANGE;
85
+ }
86
+ return {
87
+ value: current,
88
+ setAnalog(axis, value) {
89
+ overrides[axis] = clampAxis(value, rangeOf(axis));
90
+ },
91
+ clearAnalog(axis) {
92
+ delete overrides[axis];
93
+ },
94
+ reset() {
95
+ for (const axis of axes)
96
+ current[axis] = 0;
97
+ },
98
+ sample(dt, isDown, pointer) {
99
+ for (const axis of axes) {
100
+ const range = rangeOf(axis);
101
+ const override = overrides[axis];
102
+ const binding = config.bindings[axis];
103
+ const fromPointer = binding.pointer === undefined ? null : pointerAxisValue(binding.pointer, pointer);
104
+ const target = override !== undefined ? override : clampAxis(fromPointer ?? digitalTarget(binding, isDown), range);
105
+ current[axis] = clampAxis(rampToward(current[axis], target, smoothing, dt), range);
106
+ }
107
+ return current;
108
+ },
109
+ };
110
+ }
@@ -0,0 +1,25 @@
1
+ import type { ActionCodes, ActionCodesMap } from "./actionBindings.js";
2
+ /**
3
+ * Player-rebound keys, keyed by action name. Values mirror an `ActionCodes`
4
+ * entry so hold/toggle/repeat semantics survive a rebind — the settings menu
5
+ * only swaps which physical codes drive the action.
6
+ */
7
+ export type BindingOverrides = Record<string, ActionCodes>;
8
+ export declare const BINDING_OVERRIDES_STORAGE_PREFIX = "jgengine:keybinds:";
9
+ interface WebStorageLike {
10
+ getItem(key: string): string | null;
11
+ setItem(key: string, value: string): void;
12
+ removeItem(key: string): void;
13
+ }
14
+ export declare function bindingOverridesStorageKey(gameId: string): string;
15
+ export declare function loadBindingOverrides(gameId: string, storage?: Pick<WebStorageLike, "getItem"> | null | undefined): BindingOverrides;
16
+ export declare function saveBindingOverride(gameId: string, action: string, codes: ActionCodes, storage?: Pick<WebStorageLike, "getItem" | "setItem" | "removeItem"> | null | undefined): BindingOverrides;
17
+ export declare function clearBindingOverride(gameId: string, action: string, storage?: Pick<WebStorageLike, "getItem" | "setItem" | "removeItem"> | null | undefined): BindingOverrides;
18
+ export declare function clearAllBindingOverrides(gameId: string, storage?: Pick<WebStorageLike, "removeItem"> | null | undefined): void;
19
+ /**
20
+ * Merge player rebinds over a game's authored `input` map. Only actions the
21
+ * game already declares can be overridden; unknown override keys are ignored so
22
+ * a stale localStorage entry can't inject phantom actions.
23
+ */
24
+ export declare function applyBindingOverrides<TAction extends string, TCode extends string>(input: ActionCodesMap<TAction, TCode>, overrides: BindingOverrides): ActionCodesMap<TAction, TCode>;
25
+ export {};