@jgengine/core 0.6.0 → 0.8.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 (330) hide show
  1. package/CHANGELOG.md +214 -2
  2. package/README.md +2 -0
  3. package/dist/ai/crowd.d.ts +36 -0
  4. package/dist/ai/crowd.js +213 -0
  5. package/dist/ai/jobBoard.d.ts +49 -0
  6. package/dist/ai/jobBoard.js +155 -0
  7. package/dist/ai/spawnDirector.d.ts +61 -0
  8. package/dist/ai/spawnDirector.js +162 -0
  9. package/dist/ai/threat.d.ts +25 -0
  10. package/dist/ai/threat.js +67 -0
  11. package/dist/anim/easing.d.ts +14 -0
  12. package/dist/anim/easing.js +55 -0
  13. package/dist/audio/audioFalloff.d.ts +38 -0
  14. package/dist/audio/audioFalloff.js +32 -0
  15. package/dist/behaviour/behaviour.d.ts +136 -0
  16. package/dist/behaviour/behaviour.js +388 -0
  17. package/dist/board/laneBoard.d.ts +63 -0
  18. package/dist/board/laneBoard.js +114 -0
  19. package/dist/board/timelineBoard.d.ts +36 -0
  20. package/dist/board/timelineBoard.js +70 -0
  21. package/dist/cards/cardPile.d.ts +64 -0
  22. package/dist/cards/cardPile.js +175 -0
  23. package/dist/cards/modifierPipeline.d.ts +33 -0
  24. package/dist/cards/modifierPipeline.js +48 -0
  25. package/dist/combat/abilityKit.d.ts +47 -0
  26. package/dist/combat/abilityKit.js +139 -0
  27. package/dist/combat/animationState.d.ts +47 -0
  28. package/dist/combat/animationState.js +101 -0
  29. package/dist/combat/attackTags.d.ts +14 -0
  30. package/dist/combat/attackTags.js +26 -0
  31. package/dist/combat/breakMeters.d.ts +32 -0
  32. package/dist/combat/breakMeters.js +51 -0
  33. package/dist/combat/comboString.d.ts +35 -0
  34. package/dist/combat/comboString.js +59 -0
  35. package/dist/combat/death.d.ts +8 -0
  36. package/dist/combat/death.js +5 -2
  37. package/dist/combat/defensiveWindow.d.ts +34 -0
  38. package/dist/combat/defensiveWindow.js +66 -0
  39. package/dist/combat/downed.d.ts +35 -0
  40. package/dist/combat/downed.js +104 -0
  41. package/dist/combat/effects.d.ts +12 -1
  42. package/dist/combat/effects.js +31 -19
  43. package/dist/combat/hitReaction.d.ts +23 -0
  44. package/dist/combat/hitReaction.js +20 -0
  45. package/dist/combat/projectiles.d.ts +42 -5
  46. package/dist/combat/projectiles.js +98 -12
  47. package/dist/combat/resistance.d.ts +13 -0
  48. package/dist/combat/resistance.js +46 -0
  49. package/dist/combat/telegraph.d.ts +31 -0
  50. package/dist/combat/telegraph.js +51 -0
  51. package/dist/commands/commandRegistry.d.ts +1 -1
  52. package/dist/commands/commandRegistry.js +2 -1
  53. package/dist/crafting/crop.d.ts +59 -0
  54. package/dist/crafting/crop.js +181 -0
  55. package/dist/crafting/production.d.ts +64 -0
  56. package/dist/crafting/production.js +130 -0
  57. package/dist/crafting/recipe.d.ts +62 -0
  58. package/dist/crafting/recipe.js +101 -0
  59. package/dist/data/dataSource.d.ts +26 -0
  60. package/dist/data/dataSource.js +93 -0
  61. package/dist/data/devProxy.d.ts +11 -0
  62. package/dist/data/devProxy.js +38 -0
  63. package/dist/data/fetchJson.d.ts +20 -0
  64. package/dist/data/fetchJson.js +43 -0
  65. package/dist/data/jsonDataSource.d.ts +4 -0
  66. package/dist/data/jsonDataSource.js +6 -0
  67. package/dist/devtools/devtools.d.ts +157 -0
  68. package/dist/devtools/devtools.js +484 -0
  69. package/dist/devtools/transformTunables.d.ts +14 -0
  70. package/dist/devtools/transformTunables.js +38 -0
  71. package/dist/economy/sharedWallet.d.ts +28 -0
  72. package/dist/economy/sharedWallet.js +62 -0
  73. package/dist/economy/techTree.d.ts +40 -0
  74. package/dist/economy/techTree.js +66 -0
  75. package/dist/game/chat.d.ts +66 -0
  76. package/dist/game/chat.js +165 -0
  77. package/dist/game/cosmetics.d.ts +25 -0
  78. package/dist/game/cosmetics.js +51 -0
  79. package/dist/game/defineGame.d.ts +10 -2
  80. package/dist/game/defineGame.js +2 -1
  81. package/dist/game/events.d.ts +87 -0
  82. package/dist/game/levelSequence.d.ts +44 -0
  83. package/dist/game/levelSequence.js +78 -0
  84. package/dist/game/lootFilter.d.ts +33 -0
  85. package/dist/game/lootFilter.js +48 -0
  86. package/dist/game/ping.d.ts +67 -0
  87. package/dist/game/ping.js +86 -0
  88. package/dist/game/playableGame.d.ts +516 -8
  89. package/dist/game/playableGame.js +6 -1
  90. package/dist/game/progression.d.ts +1 -0
  91. package/dist/game/progression.js +23 -2
  92. package/dist/game/race.d.ts +100 -0
  93. package/dist/game/race.js +236 -0
  94. package/dist/game/runDraft.d.ts +34 -0
  95. package/dist/game/runDraft.js +97 -0
  96. package/dist/game/social.d.ts +69 -0
  97. package/dist/game/social.js +138 -0
  98. package/dist/game/spawnPoints.d.ts +19 -0
  99. package/dist/game/spawnPoints.js +23 -0
  100. package/dist/game/worldItem.d.ts +95 -0
  101. package/dist/game/worldItem.js +138 -0
  102. package/dist/input/actionBindings.d.ts +10 -0
  103. package/dist/input/actionBindings.js +14 -0
  104. package/dist/input/axisInput.d.ts +43 -0
  105. package/dist/input/axisInput.js +70 -0
  106. package/dist/input/gestureSurface.d.ts +31 -0
  107. package/dist/input/gestureSurface.js +120 -0
  108. package/dist/input/pointer.d.ts +69 -0
  109. package/dist/input/pointer.js +65 -0
  110. package/dist/input/touchScheme.d.ts +83 -0
  111. package/dist/input/touchScheme.js +115 -0
  112. package/dist/interaction/contextMenu.d.ts +30 -0
  113. package/dist/interaction/contextMenu.js +22 -0
  114. package/dist/interaction/qte.d.ts +20 -0
  115. package/dist/interaction/qte.js +24 -0
  116. package/dist/interaction/skillCheck.d.ts +20 -0
  117. package/dist/interaction/skillCheck.js +26 -0
  118. package/dist/inventory/shapedGrid.d.ts +56 -0
  119. package/dist/inventory/shapedGrid.js +176 -0
  120. package/dist/inventory/storageTier.d.ts +52 -0
  121. package/dist/inventory/storageTier.js +77 -0
  122. package/dist/item/affix.d.ts +54 -0
  123. package/dist/item/affix.js +115 -0
  124. package/dist/item/durability.d.ts +51 -0
  125. package/dist/item/durability.js +78 -0
  126. package/dist/item/modularItem.d.ts +46 -0
  127. package/dist/item/modularItem.js +75 -0
  128. package/dist/meta/changelog.d.ts +1 -1
  129. package/dist/meta/changelog.js +71 -1
  130. package/dist/movement/dash.d.ts +37 -0
  131. package/dist/movement/dash.js +65 -0
  132. package/dist/movement/movementModel.d.ts +36 -3
  133. package/dist/movement/movementModel.js +81 -5
  134. package/dist/movement/voxelController.d.ts +61 -0
  135. package/dist/movement/voxelController.js +229 -0
  136. package/dist/multiplayer/chatContract.d.ts +42 -0
  137. package/dist/multiplayer/chatContract.js +57 -0
  138. package/dist/multiplayer/combatSnapshot.d.ts +45 -0
  139. package/dist/multiplayer/combatSnapshot.js +89 -0
  140. package/dist/multiplayer/identity.d.ts +13 -0
  141. package/dist/multiplayer/identity.js +20 -0
  142. package/dist/multiplayer/lagCompensation.d.ts +43 -0
  143. package/dist/multiplayer/lagCompensation.js +154 -0
  144. package/dist/multiplayer/matchmaking.d.ts +33 -0
  145. package/dist/multiplayer/matchmaking.js +60 -0
  146. package/dist/multiplayer/poseSyncGate.d.ts +1 -0
  147. package/dist/multiplayer/poseSyncGate.js +13 -1
  148. package/dist/multiplayer/presenceModel.d.ts +3 -0
  149. package/dist/multiplayer/presenceModel.js +15 -1
  150. package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
  151. package/dist/multiplayer/simultaneousCommit.js +53 -0
  152. package/dist/multiplayer/voiceContract.d.ts +44 -0
  153. package/dist/multiplayer/voiceContract.js +108 -0
  154. package/dist/nav/navConstrain.d.ts +14 -0
  155. package/dist/nav/navConstrain.js +26 -0
  156. package/dist/nav/navFromEnvironment.d.ts +11 -0
  157. package/dist/nav/navFromEnvironment.js +16 -0
  158. package/dist/nav/navGrid.d.ts +56 -0
  159. package/dist/nav/navGrid.js +265 -0
  160. package/dist/nav/pathFollow.d.ts +31 -0
  161. package/dist/nav/pathFollow.js +69 -0
  162. package/dist/physics/ballisticSweep.d.ts +18 -0
  163. package/dist/physics/ballisticSweep.js +49 -0
  164. package/dist/physics/buoyancy.d.ts +49 -0
  165. package/dist/physics/buoyancy.js +112 -0
  166. package/dist/physics/carryable.d.ts +46 -0
  167. package/dist/physics/carryable.js +110 -0
  168. package/dist/physics/damageZones.d.ts +56 -0
  169. package/dist/physics/damageZones.js +107 -0
  170. package/dist/physics/forceVolume.d.ts +48 -0
  171. package/dist/physics/forceVolume.js +119 -0
  172. package/dist/physics/physicsWorld.d.ts +124 -2
  173. package/dist/physics/physicsWorld.js +493 -41
  174. package/dist/physics/ragdoll.d.ts +49 -0
  175. package/dist/physics/ragdoll.js +103 -0
  176. package/dist/physics/spatialGrid.d.ts +41 -0
  177. package/dist/physics/spatialGrid.js +143 -0
  178. package/dist/physics/structure.d.ts +83 -0
  179. package/dist/physics/structure.js +0 -0
  180. package/dist/physics/traversal.d.ts +75 -0
  181. package/dist/physics/traversal.js +138 -0
  182. package/dist/physics/vehicleBody.d.ts +99 -0
  183. package/dist/physics/vehicleBody.js +233 -0
  184. package/dist/puzzle/cellGrid.d.ts +26 -0
  185. package/dist/puzzle/cellGrid.js +110 -0
  186. package/dist/puzzle/fallingPiece.d.ts +24 -0
  187. package/dist/puzzle/fallingPiece.js +51 -0
  188. package/dist/random/nameGen.d.ts +18 -0
  189. package/dist/random/nameGen.js +35 -0
  190. package/dist/random/rng.d.ts +2 -0
  191. package/dist/random/rng.js +23 -0
  192. package/dist/runtime/adapter.d.ts +35 -0
  193. package/dist/runtime/adapter.js +37 -1
  194. package/dist/runtime/cameraDirector.d.ts +10 -0
  195. package/dist/runtime/cameraDirector.js +25 -0
  196. package/dist/runtime/gameContext.d.ts +107 -3
  197. package/dist/runtime/gameContext.js +253 -7
  198. package/dist/runtime/hostPersistence.d.ts +39 -0
  199. package/dist/runtime/hostPersistence.js +54 -0
  200. package/dist/runtime/inputSnapshot.d.ts +7 -0
  201. package/dist/runtime/inputSnapshot.js +12 -0
  202. package/dist/runtime/motionIntents.d.ts +25 -0
  203. package/dist/runtime/motionIntents.js +25 -0
  204. package/dist/runtime/persistenceScope.d.ts +32 -0
  205. package/dist/runtime/persistenceScope.js +64 -0
  206. package/dist/runtime/transport.d.ts +32 -0
  207. package/dist/scene/autoTarget.d.ts +15 -0
  208. package/dist/scene/autoTarget.js +54 -0
  209. package/dist/scene/behaviors.d.ts +13 -1
  210. package/dist/scene/behaviors.js +3 -0
  211. package/dist/scene/captureCheck.d.ts +7 -0
  212. package/dist/scene/captureCheck.js +11 -0
  213. package/dist/scene/entityStore.d.ts +9 -1
  214. package/dist/scene/entityStore.js +25 -2
  215. package/dist/scene/form.d.ts +40 -0
  216. package/dist/scene/form.js +56 -0
  217. package/dist/scene/mount.d.ts +62 -0
  218. package/dist/scene/mount.js +107 -0
  219. package/dist/scene/movementSpeed.d.ts +35 -0
  220. package/dist/scene/movementSpeed.js +20 -0
  221. package/dist/scene/objectQuery.d.ts +18 -0
  222. package/dist/scene/objectQuery.js +108 -0
  223. package/dist/scene/objectStore.d.ts +11 -0
  224. package/dist/scene/objectStore.js +109 -4
  225. package/dist/scene/paintLayer.d.ts +15 -0
  226. package/dist/scene/paintLayer.js +53 -0
  227. package/dist/scene/possession.d.ts +30 -0
  228. package/dist/scene/possession.js +51 -0
  229. package/dist/scene/roster.d.ts +25 -0
  230. package/dist/scene/roster.js +57 -0
  231. package/dist/scene/selection.d.ts +30 -0
  232. package/dist/scene/selection.js +38 -0
  233. package/dist/scene/spatial.d.ts +21 -0
  234. package/dist/scene/spatial.js +63 -1
  235. package/dist/scene/stationClaim.d.ts +43 -0
  236. package/dist/scene/stationClaim.js +109 -0
  237. package/dist/sensor/concealment.d.ts +21 -0
  238. package/dist/sensor/concealment.js +63 -0
  239. package/dist/sensor/freezeMonitor.d.ts +17 -0
  240. package/dist/sensor/freezeMonitor.js +35 -0
  241. package/dist/sensor/frustumSensor.d.ts +60 -0
  242. package/dist/sensor/frustumSensor.js +104 -0
  243. package/dist/sensor/hiddenStateProbe.d.ts +31 -0
  244. package/dist/sensor/hiddenStateProbe.js +40 -0
  245. package/dist/sensor/recordingBuffer.d.ts +29 -0
  246. package/dist/sensor/recordingBuffer.js +61 -0
  247. package/dist/sensor/revealQuery.d.ts +22 -0
  248. package/dist/sensor/revealQuery.js +36 -0
  249. package/dist/session/contestedChannel.d.ts +36 -0
  250. package/dist/session/contestedChannel.js +100 -0
  251. package/dist/session/extraction.d.ts +54 -0
  252. package/dist/session/extraction.js +108 -0
  253. package/dist/session/ring.d.ts +36 -0
  254. package/dist/session/ring.js +88 -0
  255. package/dist/session/roles.d.ts +6 -0
  256. package/dist/session/roles.js +32 -0
  257. package/dist/session/roundState.d.ts +79 -0
  258. package/dist/session/roundState.js +158 -0
  259. package/dist/stats/accumulatorMeter.d.ts +31 -0
  260. package/dist/stats/accumulatorMeter.js +81 -0
  261. package/dist/stats/eventMeter.d.ts +34 -0
  262. package/dist/stats/eventMeter.js +76 -0
  263. package/dist/stats/rollCheck.d.ts +15 -0
  264. package/dist/stats/rollCheck.js +15 -0
  265. package/dist/survival/decayMeter.d.ts +59 -0
  266. package/dist/survival/decayMeter.js +95 -0
  267. package/dist/survival/moodle.d.ts +48 -0
  268. package/dist/survival/moodle.js +91 -0
  269. package/dist/survival/regionHealth.d.ts +72 -0
  270. package/dist/survival/regionHealth.js +129 -0
  271. package/dist/tactics/fallingGrid.d.ts +62 -0
  272. package/dist/tactics/fallingGrid.js +158 -0
  273. package/dist/tactics/predictiveQuery.d.ts +34 -0
  274. package/dist/tactics/predictiveQuery.js +44 -0
  275. package/dist/tactics/snapshot.d.ts +18 -0
  276. package/dist/tactics/snapshot.js +65 -0
  277. package/dist/tactics/surface.d.ts +52 -0
  278. package/dist/tactics/surface.js +135 -0
  279. package/dist/tactics/tacticalGrid.d.ts +68 -0
  280. package/dist/tactics/tacticalGrid.js +261 -0
  281. package/dist/time/beatClock.d.ts +40 -0
  282. package/dist/time/beatClock.js +73 -0
  283. package/dist/time/simClock.d.ts +25 -1
  284. package/dist/time/simClock.js +28 -3
  285. package/dist/turn/commit.d.ts +27 -0
  286. package/dist/turn/commit.js +72 -0
  287. package/dist/turn/intent.d.ts +14 -0
  288. package/dist/turn/intent.js +29 -0
  289. package/dist/turn/turnLoop.d.ts +60 -0
  290. package/dist/turn/turnLoop.js +223 -0
  291. package/dist/world/buildPermissions.d.ts +47 -0
  292. package/dist/world/buildPermissions.js +134 -0
  293. package/dist/world/carve.d.ts +105 -0
  294. package/dist/world/carve.js +175 -0
  295. package/dist/world/connectors.d.ts +40 -0
  296. package/dist/world/connectors.js +67 -0
  297. package/dist/world/envField.d.ts +71 -0
  298. package/dist/world/envField.js +96 -0
  299. package/dist/world/environmentSummary.d.ts +53 -0
  300. package/dist/world/environmentSummary.js +129 -0
  301. package/dist/world/features.d.ts +87 -6
  302. package/dist/world/features.js +52 -2
  303. package/dist/world/fog.d.ts +40 -0
  304. package/dist/world/fog.js +137 -0
  305. package/dist/world/gridInstances.d.ts +13 -0
  306. package/dist/world/gridInstances.js +22 -0
  307. package/dist/world/markers.d.ts +52 -0
  308. package/dist/world/markers.js +96 -0
  309. package/dist/world/minimap.d.ts +42 -0
  310. package/dist/world/minimap.js +72 -0
  311. package/dist/world/placedStructureStore.d.ts +39 -0
  312. package/dist/world/placedStructureStore.js +95 -0
  313. package/dist/world/placementController.d.ts +69 -0
  314. package/dist/world/placementController.js +153 -0
  315. package/dist/world/realm.d.ts +61 -0
  316. package/dist/world/realm.js +66 -0
  317. package/dist/world/support.d.ts +27 -0
  318. package/dist/world/support.js +75 -0
  319. package/dist/world/terraform.d.ts +54 -0
  320. package/dist/world/terraform.js +186 -0
  321. package/dist/world/terrain.d.ts +23 -1
  322. package/dist/world/terrain.js +64 -1
  323. package/dist/world/voxelField.d.ts +54 -0
  324. package/dist/world/voxelField.js +241 -0
  325. package/dist/world/walls.d.ts +61 -0
  326. package/dist/world/walls.js +221 -0
  327. package/dist/world/weather.d.ts +92 -0
  328. package/dist/world/weather.js +169 -0
  329. package/llms.txt +4102 -0
  330. package/package.json +3 -2
@@ -0,0 +1,64 @@
1
+ export function partitionScopes(state, schema) {
2
+ const runKeys = new Set(schema.run);
3
+ const meta = {};
4
+ const run = {};
5
+ for (const key of Object.keys(state)) {
6
+ if (runKeys.has(key))
7
+ run[key] = state[key];
8
+ else
9
+ meta[key] = state[key];
10
+ }
11
+ return { meta, run };
12
+ }
13
+ export function resetRun(scoped) {
14
+ return { meta: scoped.meta, run: {} };
15
+ }
16
+ export function mergeScopes(scoped) {
17
+ return { ...scoped.meta, ...scoped.run };
18
+ }
19
+ const RUN_FIELD_DEFAULTS = {
20
+ inventories: {},
21
+ economy: {},
22
+ unlocks: [],
23
+ quests: undefined,
24
+ social: undefined,
25
+ leaderboard: {},
26
+ session: {},
27
+ };
28
+ export function clearRunFields(player, runFields) {
29
+ const next = { ...player };
30
+ for (const field of runFields) {
31
+ if (field === "userId")
32
+ continue;
33
+ const key = field;
34
+ if (key in RUN_FIELD_DEFAULTS) {
35
+ const template = RUN_FIELD_DEFAULTS[key];
36
+ if (template === undefined)
37
+ delete next[field];
38
+ else
39
+ next[field] = Array.isArray(template) ? [...template] : { ...template };
40
+ }
41
+ else {
42
+ delete next[field];
43
+ }
44
+ }
45
+ return next;
46
+ }
47
+ export function applyRunReset(profile, runFields, now) {
48
+ return {
49
+ ...profile,
50
+ playerState: clearRunFields(profile.playerState, runFields),
51
+ revision: profile.revision + 1,
52
+ updatedAt: now,
53
+ };
54
+ }
55
+ export function planScenarioReset(reset) {
56
+ return {
57
+ gameId: reset.gameId,
58
+ serverId: reset.serverId ?? null,
59
+ wipeChunks: reset.wipeChunks ?? true,
60
+ wipeServerSession: reset.wipeServerSession ?? true,
61
+ resetPlayers: reset.resetPlayers === "run" ? "run" : "none",
62
+ runFields: reset.runFields ?? [],
63
+ };
64
+ }
@@ -1,3 +1,5 @@
1
+ import type { ChatSync } from "../multiplayer/chatContract.js";
2
+ import type { PlayerPose } from "../multiplayer/poseSyncGate.js";
1
3
  import type { PresenceTransport } from "../multiplayer/presenceContract.js";
2
4
  export type JoinServerResult = {
3
5
  serverId: string;
@@ -58,3 +60,33 @@ export type GameBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGa
58
60
  feeds?: GameRuntimeFeeds;
59
61
  presence?: PresenceTransport<TPresenceRow, TPresenceLocation, TGameId>;
60
62
  };
63
+ export type PresencePoseRow = {
64
+ userId: string;
65
+ position: {
66
+ x: number;
67
+ y: number;
68
+ z: number;
69
+ };
70
+ rotationY: number;
71
+ rotationPitch: number;
72
+ lastSeenAt: number;
73
+ };
74
+ export type PresenceSync = {
75
+ subscribe: (serverId: string, onChange: (rows: PresencePoseRow[]) => void) => FeedUnsubscribe;
76
+ syncPose: (serverId: string, pose: PlayerPose) => void;
77
+ };
78
+ export type LiveGameBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string> = GameBackend<TPresenceRow, TPresenceLocation, TGameId> & {
79
+ presenceSync: PresenceSync;
80
+ pushFeedEntry: (args: {
81
+ serverId: string;
82
+ action: string;
83
+ entry: unknown;
84
+ }) => Promise<void>;
85
+ chatSyncFor?: (serverId: string) => ChatSync;
86
+ };
87
+ export type MultiplayerSession = {
88
+ gameId: string;
89
+ userId: string;
90
+ backend: LiveGameBackend;
91
+ feedActions: string[];
92
+ };
@@ -0,0 +1,15 @@
1
+ export type AutoTargetPolicy = "nearest" | "farthest" | "random" | "strongest" | "weakest" | "first" | "last";
2
+ export interface AutoTargetDeps {
3
+ candidates: (fromId: string) => readonly string[];
4
+ distance: (fromId: string, toId: string) => number | null;
5
+ strength?: (toId: string) => number;
6
+ progress?: (toId: string) => number;
7
+ rng?: () => number;
8
+ }
9
+ export declare function selectAutoTarget(policy: AutoTargetPolicy, fromId: string, deps: AutoTargetDeps): string | null;
10
+ export interface AutoTargeter {
11
+ policy(): AutoTargetPolicy;
12
+ setPolicy(policy: AutoTargetPolicy): void;
13
+ pick(fromId: string): string | null;
14
+ }
15
+ export declare function createAutoTargeter(policy: AutoTargetPolicy, deps: AutoTargetDeps): AutoTargeter;
@@ -0,0 +1,54 @@
1
+ function metric(policy, fromId, toId, deps) {
2
+ switch (policy) {
3
+ case "nearest":
4
+ case "farthest":
5
+ return deps.distance(fromId, toId);
6
+ case "strongest":
7
+ case "weakest":
8
+ return deps.strength?.(toId) ?? 0;
9
+ case "first":
10
+ case "last":
11
+ return deps.progress?.(toId) ?? 0;
12
+ case "random":
13
+ return 0;
14
+ }
15
+ }
16
+ function prefersHigher(policy) {
17
+ return policy === "farthest" || policy === "strongest" || policy === "first";
18
+ }
19
+ export function selectAutoTarget(policy, fromId, deps) {
20
+ const candidates = deps.candidates(fromId).filter((id) => id !== fromId);
21
+ if (candidates.length === 0)
22
+ return null;
23
+ if (policy === "random") {
24
+ const rng = deps.rng ?? Math.random;
25
+ return candidates[Math.min(candidates.length - 1, Math.floor(rng() * candidates.length))];
26
+ }
27
+ const higher = prefersHigher(policy);
28
+ let best = null;
29
+ let bestScore = higher ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
30
+ for (const id of candidates) {
31
+ const score = metric(policy, fromId, id, deps);
32
+ if (score === null)
33
+ continue;
34
+ if (best === null || (higher ? score > bestScore : score < bestScore)) {
35
+ best = id;
36
+ bestScore = score;
37
+ }
38
+ }
39
+ return best;
40
+ }
41
+ export function createAutoTargeter(policy, deps) {
42
+ let current = policy;
43
+ return {
44
+ policy() {
45
+ return current;
46
+ },
47
+ setPolicy(next) {
48
+ current = next;
49
+ },
50
+ pick(fromId) {
51
+ return selectAutoTarget(current, fromId, deps);
52
+ },
53
+ };
54
+ }
@@ -1,8 +1,15 @@
1
1
  import { type ProximityPrompt } from "../interaction/proximityPrompt.js";
2
+ import type { Waypoint } from "../nav/pathFollow.js";
2
3
  export interface WanderBehavior {
3
4
  kind: "wander";
4
5
  radius: number;
5
6
  }
7
+ export interface PatrolBehavior {
8
+ kind: "patrol";
9
+ waypoints: readonly Waypoint[];
10
+ speed: number;
11
+ loop: boolean;
12
+ }
6
13
  export interface PromptableBehavior {
7
14
  kind: "promptable";
8
15
  prompt: ProximityPrompt;
@@ -10,10 +17,15 @@ export interface PromptableBehavior {
10
17
  export interface PlayerBehavior {
11
18
  kind: "player";
12
19
  }
13
- export type BehaviorDescriptor = WanderBehavior | PromptableBehavior | PlayerBehavior;
20
+ export type BehaviorDescriptor = WanderBehavior | PatrolBehavior | PromptableBehavior | PlayerBehavior;
14
21
  export declare function wander({ radius }: {
15
22
  radius: number;
16
23
  }): WanderBehavior;
24
+ export declare function patrol({ waypoints, speed, loop, }: {
25
+ waypoints: readonly Waypoint[];
26
+ speed: number;
27
+ loop?: boolean;
28
+ }): PatrolBehavior;
17
29
  export declare function promptable(prompt: ProximityPrompt): PromptableBehavior;
18
30
  export declare function talkable(dialogueId: string): PromptableBehavior;
19
31
  export declare function player(): PlayerBehavior;
@@ -2,6 +2,9 @@ import { command, keybind, proximityPrompt, } from "../interaction/proximityProm
2
2
  export function wander({ radius }) {
3
3
  return { kind: "wander", radius };
4
4
  }
5
+ export function patrol({ waypoints, speed, loop = true, }) {
6
+ return { kind: "patrol", waypoints, speed, loop };
7
+ }
5
8
  export function promptable(prompt) {
6
9
  return { kind: "promptable", prompt };
7
10
  }
@@ -0,0 +1,7 @@
1
+ export interface CaptureCheckInput {
2
+ hpFraction: number;
3
+ catchPower: number;
4
+ difficulty?: number;
5
+ }
6
+ export declare function captureChance(input: CaptureCheckInput): number;
7
+ export declare function rollCapture(input: CaptureCheckInput, rng?: () => number): boolean;
@@ -0,0 +1,11 @@
1
+ function clamp01(value) {
2
+ return Math.min(1, Math.max(0, value));
3
+ }
4
+ export function captureChance(input) {
5
+ const difficulty = input.difficulty ?? 1;
6
+ const hpFactor = 1 - clamp01(input.hpFraction) * 0.6;
7
+ return clamp01((input.catchPower * hpFactor) / Math.max(difficulty, 0.0001));
8
+ }
9
+ export function rollCapture(input, rng = Math.random) {
10
+ return rng() < captureChance(input);
11
+ }
@@ -2,6 +2,7 @@ import type { BehaviorDescriptor } from "./behaviors.js";
2
2
  export type EntityRole = "player" | "npc" | "prop";
3
3
  export interface EntityMovement {
4
4
  walkSpeed?: number;
5
+ frozen?: boolean;
5
6
  }
6
7
  export type EntityPosition = readonly [number, number, number];
7
8
  export type SpawnPositionInput = EntityPosition | {
@@ -25,6 +26,7 @@ export interface SceneEntity<TMeta = undefined> {
25
26
  }
26
27
  /** Ground speed (horizontal magnitude of velocity) in world units per second. Scale to km/h or mph in game code. */
27
28
  export declare function groundSpeed(entity: SceneEntity<unknown>): number;
29
+ export declare function movedWhileFrozen(entity: SceneEntity<unknown>, threshold?: number): boolean;
28
30
  export interface SpawnOptions<TMeta = undefined> {
29
31
  id?: string;
30
32
  position?: SpawnPositionInput;
@@ -44,15 +46,21 @@ export interface EntityPose {
44
46
  /** Seconds since the previous pose; when > 0 the store derives `velocity` from the position delta. Omit for teleports (velocity unchanged). */
45
47
  dt?: number;
46
48
  }
49
+ export interface SpawnPose {
50
+ position: EntityPosition;
51
+ rotationY: number;
52
+ }
47
53
  export interface EntityStore<TMeta = undefined> {
48
54
  spawn(name: string, options?: SpawnOptions<TMeta>): string;
49
55
  despawn(id: string): boolean;
50
- update(id: string, patch: Partial<Pick<SceneEntity<TMeta>, "position" | "rotationY" | "rotationX" | "rotationZ" | "role" | "movement" | "behaviors" | "meta">>): boolean;
56
+ update(id: string, patch: Partial<Pick<SceneEntity<TMeta>, "name" | "position" | "rotationY" | "rotationX" | "rotationZ" | "role" | "movement" | "behaviors" | "meta">>): boolean;
51
57
  setPose(id: string, pose: EntityPose): boolean;
52
58
  get(id: string): SceneEntity<TMeta> | null;
53
59
  list(): readonly SceneEntity<TMeta>[];
54
60
  clear(): void;
55
61
  subscribe(listener: () => void): () => void;
56
62
  snapshot(): readonly SceneEntity<TMeta>[];
63
+ spawnPoseOf(id: string): SpawnPose | null;
64
+ resetToSpawn(id: string): boolean;
57
65
  }
58
66
  export declare function createEntityStore<TMeta = undefined>(): EntityStore<TMeta>;
@@ -3,6 +3,13 @@ import { createObservableKeyedStore } from "../store/observableKeyedStore.js";
3
3
  export function groundSpeed(entity) {
4
4
  return Math.hypot(entity.velocity[0], entity.velocity[2]);
5
5
  }
6
+ const DEFAULT_FROZEN_MOVE_THRESHOLD = 1e-6;
7
+ export function movedWhileFrozen(entity, threshold = DEFAULT_FROZEN_MOVE_THRESHOLD) {
8
+ if (entity.movement.frozen !== true)
9
+ return false;
10
+ const speed = Math.hypot(entity.velocity[0], entity.velocity[1], entity.velocity[2]);
11
+ return speed > threshold;
12
+ }
6
13
  function toEntityPosition(position) {
7
14
  if (position === undefined)
8
15
  return [0, 0, 0];
@@ -12,6 +19,7 @@ function toEntityPosition(position) {
12
19
  }
13
20
  export function createEntityStore() {
14
21
  const store = createObservableKeyedStore();
22
+ const spawnPoses = new Map();
15
23
  let nextCounter = 1;
16
24
  function generateId() {
17
25
  let id = `entity-${nextCounter}`;
@@ -28,11 +36,13 @@ export function createEntityStore() {
28
36
  throw new Error(`Scene entity id "${options.id}" is already spawned.`);
29
37
  }
30
38
  const id = options.id ?? generateId();
39
+ const position = toEntityPosition(options.position);
40
+ const rotationY = options.rotationY ?? 0;
31
41
  store.set(id, {
32
42
  id,
33
43
  name,
34
- position: toEntityPosition(options.position),
35
- rotationY: options.rotationY ?? 0,
44
+ position,
45
+ rotationY,
36
46
  rotationX: options.rotationX ?? 0,
37
47
  rotationZ: options.rotationZ ?? 0,
38
48
  velocity: [0, 0, 0],
@@ -41,11 +51,13 @@ export function createEntityStore() {
41
51
  behaviors: options.behaviors ?? [],
42
52
  meta: options.meta,
43
53
  });
54
+ spawnPoses.set(id, { position, rotationY });
44
55
  return id;
45
56
  },
46
57
  despawn(id) {
47
58
  const existed = store.has(id);
48
59
  store.delete(id);
60
+ spawnPoses.delete(id);
49
61
  return existed;
50
62
  },
51
63
  update(id, patch) {
@@ -94,5 +106,16 @@ export function createEntityStore() {
94
106
  snapshot() {
95
107
  return store.arraySnapshot();
96
108
  },
109
+ spawnPoseOf(id) {
110
+ return spawnPoses.get(id) ?? null;
111
+ },
112
+ resetToSpawn(id) {
113
+ const pose = spawnPoses.get(id);
114
+ const current = store.get(id);
115
+ if (pose === undefined || current === undefined)
116
+ return false;
117
+ store.set(id, { ...current, position: pose.position, rotationY: pose.rotationY, velocity: [0, 0, 0] });
118
+ return true;
119
+ },
97
120
  };
98
121
  }
@@ -0,0 +1,40 @@
1
+ import type { EntityMovement, SceneEntity } from "./entityStore.js";
2
+ export interface FormDef {
3
+ id: string;
4
+ movement?: EntityMovement;
5
+ abilities?: readonly string[];
6
+ model?: string;
7
+ }
8
+ export interface FormChangedEvent {
9
+ instanceId: string;
10
+ formId: string | null;
11
+ }
12
+ export interface FormEntities {
13
+ get(id: string): SceneEntity | null;
14
+ update(id: string, patch: {
15
+ movement?: EntityMovement;
16
+ name?: string;
17
+ }): boolean;
18
+ }
19
+ export interface FormTime {
20
+ after(seconds: number, callback: () => void): () => void;
21
+ }
22
+ export interface FormEvents {
23
+ emit(name: "form.changed", payload: FormChangedEvent): void;
24
+ }
25
+ export interface FormsDeps {
26
+ entities: FormEntities;
27
+ time: FormTime;
28
+ events?: FormEvents;
29
+ }
30
+ export interface Forms {
31
+ register(defs: readonly FormDef[]): void;
32
+ get(formId: string): FormDef | null;
33
+ abilities(instanceId: string): readonly string[] | null;
34
+ active(instanceId: string): string | null;
35
+ shapeshift(instanceId: string, formId: string, durationSeconds?: number): {
36
+ reason: string;
37
+ } | null;
38
+ revert(instanceId: string): void;
39
+ }
40
+ export declare function createForms(deps: FormsDeps): Forms;
@@ -0,0 +1,56 @@
1
+ export function createForms(deps) {
2
+ const defs = new Map();
3
+ const runtime = new Map();
4
+ function emitChange(instanceId, formId) {
5
+ deps.events?.emit("form.changed", { instanceId, formId });
6
+ }
7
+ function revertInternal(instanceId) {
8
+ const state = runtime.get(instanceId);
9
+ if (state === undefined)
10
+ return;
11
+ state.cancelTimer?.();
12
+ runtime.delete(instanceId);
13
+ deps.entities.update(instanceId, { movement: state.baseline.movement, name: state.baseline.name });
14
+ emitChange(instanceId, null);
15
+ }
16
+ return {
17
+ register(list) {
18
+ for (const def of list)
19
+ defs.set(def.id, def);
20
+ },
21
+ get(formId) {
22
+ return defs.get(formId) ?? null;
23
+ },
24
+ abilities(instanceId) {
25
+ const state = runtime.get(instanceId);
26
+ if (state === undefined)
27
+ return null;
28
+ return defs.get(state.formId)?.abilities ?? null;
29
+ },
30
+ active(instanceId) {
31
+ return runtime.get(instanceId)?.formId ?? null;
32
+ },
33
+ shapeshift(instanceId, formId, durationSeconds) {
34
+ const def = defs.get(formId);
35
+ if (def === undefined)
36
+ return { reason: `unknown form "${formId}"` };
37
+ const entity = deps.entities.get(instanceId);
38
+ if (entity === null)
39
+ return { reason: `entity "${instanceId}" is not spawned` };
40
+ const existing = runtime.get(instanceId);
41
+ existing?.cancelTimer?.();
42
+ const baseline = existing?.baseline ?? { movement: entity.movement, name: entity.name };
43
+ deps.entities.update(instanceId, {
44
+ movement: def.movement ?? baseline.movement,
45
+ name: def.model ?? baseline.name,
46
+ });
47
+ const cancelTimer = durationSeconds === undefined ? null : deps.time.after(durationSeconds, () => revertInternal(instanceId));
48
+ runtime.set(instanceId, { baseline, formId, cancelTimer });
49
+ emitChange(instanceId, formId);
50
+ return null;
51
+ },
52
+ revert(instanceId) {
53
+ revertInternal(instanceId);
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,62 @@
1
+ export type MountKind = "ground" | "boat" | "flying" | "swimming";
2
+ export interface MountKit {
3
+ kind: MountKind;
4
+ moveSpeed?: number;
5
+ turnSpeed?: number;
6
+ }
7
+ export interface MountSeat {
8
+ id: string;
9
+ offset: readonly [number, number, number];
10
+ control?: boolean;
11
+ }
12
+ export interface RideableConfig {
13
+ id: string;
14
+ kit: MountKit;
15
+ seats?: readonly MountSeat[];
16
+ }
17
+ export interface SeatRef {
18
+ mountId: string;
19
+ seatId: string;
20
+ }
21
+ export interface Occupant {
22
+ riderId: string;
23
+ seatId: string;
24
+ }
25
+ export type MountResult = {
26
+ ok: true;
27
+ seat: MountSeat;
28
+ } | {
29
+ ok: false;
30
+ reason: string;
31
+ };
32
+ /**
33
+ * Mount / rideable control-transfer (issue #83). Registers rideables (each with one or more seats — a
34
+ * control seat drives, the rest ride) and tracks who is on what. It owns no camera or physics: game code
35
+ * reads `cameraTarget(riderId)` to point the follow camera at the mount, and `driveTarget(riderId)` to
36
+ * route that rider's {@link import("../physics/vehicleBody.js").AxisInput}-driven input at the mount's
37
+ * movement kit — the same seam a horse, a truck, or a shared multi-seat ship all plug into.
38
+ */
39
+ export declare class MountController {
40
+ private readonly mounts;
41
+ private readonly seatOfRider;
42
+ register(config: RideableConfig): void;
43
+ isRegistered(mountId: string): boolean;
44
+ kitOf(mountId: string): MountKit | null;
45
+ seatsOf(mountId: string): readonly MountSeat[];
46
+ private firstFreeSeat;
47
+ mount(riderId: string, mountId: string, seatId?: string): MountResult;
48
+ dismount(riderId: string): string | null;
49
+ seatOf(riderId: string): SeatRef | null;
50
+ isSeated(riderId: string): boolean;
51
+ seatOffset(riderId: string): readonly [number, number, number] | null;
52
+ occupants(mountId: string): readonly Occupant[];
53
+ driver(mountId: string): string | null;
54
+ /** Entity the follow camera should track for this rider: the mount when seated, else the rider. */
55
+ cameraTarget(riderId: string): string;
56
+ /**
57
+ * Entity this rider's input drives: the mount when in a control seat, `null` for a passenger seat
58
+ * (they ride but do not steer), and the rider themselves when not mounted at all.
59
+ */
60
+ driveTarget(riderId: string): string | null;
61
+ }
62
+ export declare function createMountController(): MountController;
@@ -0,0 +1,107 @@
1
+ const DEFAULT_SEAT = { id: "driver", offset: [0, 0, 0], control: true };
2
+ /**
3
+ * Mount / rideable control-transfer (issue #83). Registers rideables (each with one or more seats — a
4
+ * control seat drives, the rest ride) and tracks who is on what. It owns no camera or physics: game code
5
+ * reads `cameraTarget(riderId)` to point the follow camera at the mount, and `driveTarget(riderId)` to
6
+ * route that rider's {@link import("../physics/vehicleBody.js").AxisInput}-driven input at the mount's
7
+ * movement kit — the same seam a horse, a truck, or a shared multi-seat ship all plug into.
8
+ */
9
+ export class MountController {
10
+ mounts = new Map();
11
+ seatOfRider = new Map();
12
+ register(config) {
13
+ const seats = config.seats !== undefined && config.seats.length > 0 ? config.seats : [DEFAULT_SEAT];
14
+ this.mounts.set(config.id, { config, seats, bySeat: new Map() });
15
+ }
16
+ isRegistered(mountId) {
17
+ return this.mounts.has(mountId);
18
+ }
19
+ kitOf(mountId) {
20
+ return this.mounts.get(mountId)?.config.kit ?? null;
21
+ }
22
+ seatsOf(mountId) {
23
+ return this.mounts.get(mountId)?.seats ?? [];
24
+ }
25
+ firstFreeSeat(reg) {
26
+ const control = reg.seats.find((s) => s.control === true && !reg.bySeat.has(s.id));
27
+ if (control !== undefined)
28
+ return control;
29
+ return reg.seats.find((s) => !reg.bySeat.has(s.id)) ?? null;
30
+ }
31
+ mount(riderId, mountId, seatId) {
32
+ const reg = this.mounts.get(mountId);
33
+ if (reg === undefined)
34
+ return { ok: false, reason: "unknown_mount" };
35
+ const seat = seatId !== undefined ? reg.seats.find((s) => s.id === seatId) : this.firstFreeSeat(reg);
36
+ if (seat === undefined)
37
+ return { ok: false, reason: "unknown_seat" };
38
+ if (seat === null)
39
+ return { ok: false, reason: "mount_full" };
40
+ if (reg.bySeat.has(seat.id))
41
+ return { ok: false, reason: "seat_taken" };
42
+ this.dismount(riderId);
43
+ reg.bySeat.set(seat.id, riderId);
44
+ this.seatOfRider.set(riderId, { mountId, seatId: seat.id });
45
+ return { ok: true, seat };
46
+ }
47
+ dismount(riderId) {
48
+ const ref = this.seatOfRider.get(riderId);
49
+ if (ref === undefined)
50
+ return null;
51
+ this.mounts.get(ref.mountId)?.bySeat.delete(ref.seatId);
52
+ this.seatOfRider.delete(riderId);
53
+ return ref.mountId;
54
+ }
55
+ seatOf(riderId) {
56
+ return this.seatOfRider.get(riderId) ?? null;
57
+ }
58
+ isSeated(riderId) {
59
+ return this.seatOfRider.has(riderId);
60
+ }
61
+ seatOffset(riderId) {
62
+ const ref = this.seatOfRider.get(riderId);
63
+ if (ref === undefined)
64
+ return null;
65
+ return this.mounts.get(ref.mountId)?.seats.find((s) => s.id === ref.seatId)?.offset ?? null;
66
+ }
67
+ occupants(mountId) {
68
+ const reg = this.mounts.get(mountId);
69
+ if (reg === undefined)
70
+ return [];
71
+ return reg.seats
72
+ .filter((s) => reg.bySeat.has(s.id))
73
+ .map((s) => ({ riderId: reg.bySeat.get(s.id), seatId: s.id }));
74
+ }
75
+ driver(mountId) {
76
+ const reg = this.mounts.get(mountId);
77
+ if (reg === undefined)
78
+ return null;
79
+ for (const seat of reg.seats) {
80
+ if (seat.control === true) {
81
+ const rider = reg.bySeat.get(seat.id);
82
+ if (rider !== undefined)
83
+ return rider;
84
+ }
85
+ }
86
+ return null;
87
+ }
88
+ /** Entity the follow camera should track for this rider: the mount when seated, else the rider. */
89
+ cameraTarget(riderId) {
90
+ const ref = this.seatOfRider.get(riderId);
91
+ return ref === undefined ? riderId : ref.mountId;
92
+ }
93
+ /**
94
+ * Entity this rider's input drives: the mount when in a control seat, `null` for a passenger seat
95
+ * (they ride but do not steer), and the rider themselves when not mounted at all.
96
+ */
97
+ driveTarget(riderId) {
98
+ const ref = this.seatOfRider.get(riderId);
99
+ if (ref === undefined)
100
+ return riderId;
101
+ const seat = this.mounts.get(ref.mountId)?.seats.find((s) => s.id === ref.seatId);
102
+ return seat?.control === true ? ref.mountId : null;
103
+ }
104
+ }
105
+ export function createMountController() {
106
+ return new MountController();
107
+ }
@@ -0,0 +1,35 @@
1
+ import type { StatValue } from "./entityStats.js";
2
+ export interface MovementSpeedModifiers {
3
+ multiplier?: number;
4
+ flatBonus?: number;
5
+ }
6
+ /** `base * (multiplier ?? 1) + (flatBonus ?? 0)`, clamped to never go negative. */
7
+ export declare function deriveWalkSpeed(base: number, modifiers: MovementSpeedModifiers): number;
8
+ export interface MovementSpeedDeps {
9
+ stats: {
10
+ get(instanceId: string, statId: string): StatValue | null;
11
+ };
12
+ entities: {
13
+ get(id: string): {
14
+ movement?: {
15
+ walkSpeed?: number;
16
+ };
17
+ } | null;
18
+ update(id: string, patch: {
19
+ movement: {
20
+ walkSpeed: number;
21
+ };
22
+ }): boolean;
23
+ };
24
+ }
25
+ export interface MovementSpeedConfig {
26
+ baseSpeed: number;
27
+ multiplierStat?: string;
28
+ flatBonusStat?: string;
29
+ }
30
+ /**
31
+ * Recomputes `movement.walkSpeed` for an entity from its stats and writes it back via `deps.entities.update`.
32
+ * Either stat id is optional; an omitted or unresolved stat contributes its neutral value (multiplier 1,
33
+ * flat bonus 0) rather than failing. Returns false only when the entity itself doesn't exist.
34
+ */
35
+ export declare function applyStatDrivenSpeed(deps: MovementSpeedDeps, instanceId: string, config: MovementSpeedConfig): boolean;
@@ -0,0 +1,20 @@
1
+ /** `base * (multiplier ?? 1) + (flatBonus ?? 0)`, clamped to never go negative. */
2
+ export function deriveWalkSpeed(base, modifiers) {
3
+ const multiplier = modifiers.multiplier ?? 1;
4
+ const flatBonus = modifiers.flatBonus ?? 0;
5
+ return Math.max(0, base * multiplier + flatBonus);
6
+ }
7
+ /**
8
+ * Recomputes `movement.walkSpeed` for an entity from its stats and writes it back via `deps.entities.update`.
9
+ * Either stat id is optional; an omitted or unresolved stat contributes its neutral value (multiplier 1,
10
+ * flat bonus 0) rather than failing. Returns false only when the entity itself doesn't exist.
11
+ */
12
+ export function applyStatDrivenSpeed(deps, instanceId, config) {
13
+ const entity = deps.entities.get(instanceId);
14
+ if (entity === null)
15
+ return false;
16
+ const multiplier = config.multiplierStat !== undefined ? deps.stats.get(instanceId, config.multiplierStat)?.current : undefined;
17
+ const flatBonus = config.flatBonusStat !== undefined ? deps.stats.get(instanceId, config.flatBonusStat)?.current : undefined;
18
+ const walkSpeed = deriveWalkSpeed(config.baseSpeed, { multiplier, flatBonus });
19
+ return deps.entities.update(instanceId, { movement: { ...entity.movement, walkSpeed } });
20
+ }