@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,176 @@
1
+ function cellKey(cell) {
2
+ return `${cell[0]},${cell[1]}`;
3
+ }
4
+ export function normalizeFootprint(footprint) {
5
+ if (footprint.length === 0)
6
+ return [];
7
+ let minC = Infinity;
8
+ let minR = Infinity;
9
+ for (const [c, r] of footprint) {
10
+ if (c < minC)
11
+ minC = c;
12
+ if (r < minR)
13
+ minR = r;
14
+ }
15
+ const shifted = footprint.map(([c, r]) => [c - minC, r - minR]);
16
+ shifted.sort((a, b) => (a[1] === b[1] ? a[0] - b[0] : a[1] - b[1]));
17
+ const seen = new Set();
18
+ const out = [];
19
+ for (const cell of shifted) {
20
+ const key = cellKey(cell);
21
+ if (!seen.has(key)) {
22
+ seen.add(key);
23
+ out.push(cell);
24
+ }
25
+ }
26
+ return out;
27
+ }
28
+ export function rotateFootprint(footprint, rotation) {
29
+ const turns = ((rotation % 4) + 4) % 4;
30
+ let cells = footprint.map(([c, r]) => [c, r]);
31
+ for (let i = 0; i < turns; i++) {
32
+ cells = cells.map(([c, r]) => [-r, c]);
33
+ }
34
+ return normalizeFootprint(cells);
35
+ }
36
+ export function occupiedCells(footprint, origin, rotation) {
37
+ const rotated = rotateFootprint(footprint, rotation);
38
+ return rotated.map(([c, r]) => [c + origin[0], r + origin[1]]);
39
+ }
40
+ export function createShapedGrid(width, height) {
41
+ if (width <= 0 || height <= 0)
42
+ throw new Error("shaped grid needs positive dimensions");
43
+ return { width, height, placements: [] };
44
+ }
45
+ function inBounds(grid, cells) {
46
+ return cells.every(([c, r]) => c >= 0 && r >= 0 && c < grid.width && r < grid.height);
47
+ }
48
+ function occupancyMap(grid, ignoreId) {
49
+ const map = new Map();
50
+ for (const placement of grid.placements) {
51
+ if (placement.id === ignoreId)
52
+ continue;
53
+ for (const cell of occupiedCells(placement.footprint, placement.origin, placement.rotation)) {
54
+ map.set(cellKey(cell), placement.id);
55
+ }
56
+ }
57
+ return map;
58
+ }
59
+ export function canPlace(grid, footprint, origin, rotation, ignoreId) {
60
+ const cells = occupiedCells(footprint, origin, rotation);
61
+ if (!inBounds(grid, cells))
62
+ return "out-of-bounds";
63
+ const occupied = occupancyMap(grid, ignoreId);
64
+ for (const cell of cells) {
65
+ if (occupied.has(cellKey(cell)))
66
+ return "overlap";
67
+ }
68
+ return null;
69
+ }
70
+ export function placeShaped(grid, item, origin, rotation = 0) {
71
+ if (grid.placements.some((p) => p.id === item.id)) {
72
+ return { status: "rejected", reason: "duplicate-id", detail: item.id };
73
+ }
74
+ const rejection = canPlace(grid, item.footprint, origin, rotation);
75
+ if (rejection !== null)
76
+ return { status: "rejected", reason: rejection };
77
+ const placement = {
78
+ id: item.id,
79
+ value: item.value,
80
+ footprint: item.footprint,
81
+ origin,
82
+ rotation,
83
+ };
84
+ return { status: "ok", grid: { ...grid, placements: [...grid.placements, placement] } };
85
+ }
86
+ export function removeShaped(grid, id) {
87
+ if (!grid.placements.some((p) => p.id === id)) {
88
+ return { status: "rejected", reason: "unknown-id", detail: id };
89
+ }
90
+ return { status: "ok", grid: { ...grid, placements: grid.placements.filter((p) => p.id !== id) } };
91
+ }
92
+ export function moveShaped(grid, id, origin, rotation) {
93
+ const existing = grid.placements.find((p) => p.id === id);
94
+ if (existing === undefined)
95
+ return { status: "rejected", reason: "unknown-id", detail: id };
96
+ const nextRotation = rotation ?? existing.rotation;
97
+ const rejection = canPlace(grid, existing.footprint, origin, nextRotation, id);
98
+ if (rejection !== null)
99
+ return { status: "rejected", reason: rejection };
100
+ return {
101
+ status: "ok",
102
+ grid: {
103
+ ...grid,
104
+ placements: grid.placements.map((p) => p.id === id ? { ...p, origin, rotation: nextRotation } : p),
105
+ },
106
+ };
107
+ }
108
+ export function cellOccupant(grid, cell) {
109
+ return occupancyMap(grid).get(cellKey(cell)) ?? null;
110
+ }
111
+ const ORTHOGONAL = [
112
+ [1, 0],
113
+ [-1, 0],
114
+ [0, 1],
115
+ [0, -1],
116
+ ];
117
+ const DIAGONAL = [
118
+ [1, 1],
119
+ [1, -1],
120
+ [-1, 1],
121
+ [-1, -1],
122
+ ];
123
+ export function gridAdjacencyQuery(grid, options = {}) {
124
+ const directions = options.diagonal ? [...ORTHOGONAL, ...DIAGONAL] : ORTHOGONAL;
125
+ const cellsById = new Map();
126
+ const owner = new Map();
127
+ for (const placement of grid.placements) {
128
+ const cells = occupiedCells(placement.footprint, placement.origin, placement.rotation);
129
+ cellsById.set(placement.id, cells);
130
+ for (const cell of cells)
131
+ owner.set(cellKey(cell), placement.id);
132
+ }
133
+ const cellsOf = (id) => cellsById.get(id) ?? [];
134
+ const neighborsOf = (id) => {
135
+ const found = new Set();
136
+ for (const cell of cellsOf(id)) {
137
+ for (const [dc, dr] of directions) {
138
+ const key = cellKey([cell[0] + dc, cell[1] + dr]);
139
+ const other = owner.get(key);
140
+ if (other !== undefined && other !== id)
141
+ found.add(other);
142
+ }
143
+ }
144
+ return [...found];
145
+ };
146
+ return {
147
+ cellsOf,
148
+ neighborsOf,
149
+ touching: (idA, idB) => neighborsOf(idA).includes(idB),
150
+ adjacentCells(cells) {
151
+ const own = new Set(cells.map(cellKey));
152
+ const found = new Set();
153
+ const out = [];
154
+ for (const cell of cells) {
155
+ for (const [dc, dr] of directions) {
156
+ const target = [cell[0] + dc, cell[1] + dr];
157
+ const key = cellKey(target);
158
+ if (own.has(key) || found.has(key))
159
+ continue;
160
+ if (target[0] < 0 || target[1] < 0 || target[0] >= grid.width || target[1] >= grid.height) {
161
+ continue;
162
+ }
163
+ found.add(key);
164
+ out.push(target);
165
+ }
166
+ }
167
+ return out;
168
+ },
169
+ };
170
+ }
171
+ export function cellFromPoint(point, cellSize, gridOrigin = { x: 0, y: 0 }) {
172
+ return [
173
+ Math.floor((point.x - gridOrigin.x) / cellSize),
174
+ Math.floor((point.y - gridOrigin.y) / cellSize),
175
+ ];
176
+ }
@@ -0,0 +1,52 @@
1
+ export type StorageTier = "carried" | "banked";
2
+ export interface ItemStack {
3
+ itemId: string;
4
+ count: number;
5
+ }
6
+ export interface ContainerSnapshot {
7
+ inventoryId: string;
8
+ tier: StorageTier;
9
+ items: readonly ItemStack[];
10
+ }
11
+ export interface DeathPartition {
12
+ kept: readonly ItemStack[];
13
+ lost: readonly ItemStack[];
14
+ }
15
+ export declare function tierOf(tiers: Record<string, StorageTier>, inventoryId: string): StorageTier;
16
+ export declare function partitionOnDeath(containers: readonly ContainerSnapshot[]): DeathPartition;
17
+ export interface DeliveryEntry {
18
+ id: string;
19
+ userId: string;
20
+ inventoryId: string;
21
+ items: readonly ItemStack[];
22
+ deliverAt: number;
23
+ }
24
+ export interface ScheduledDelivery {
25
+ userId: string;
26
+ inventoryId: string;
27
+ items: readonly ItemStack[];
28
+ deliverAt: number;
29
+ }
30
+ export interface DeliveryQueue {
31
+ schedule(entry: ScheduledDelivery & {
32
+ id?: string;
33
+ }): DeliveryEntry;
34
+ due(now: number): readonly DeliveryEntry[];
35
+ claimDue(now: number): readonly DeliveryEntry[];
36
+ pending(userId?: string): readonly DeliveryEntry[];
37
+ cancel(id: string): boolean;
38
+ }
39
+ export declare function createDeliveryQueue(): DeliveryQueue;
40
+ export interface InsurancePolicy {
41
+ isInsured(itemId: string): boolean;
42
+ returnInventoryId: string;
43
+ delaySeconds: number | [number, number];
44
+ }
45
+ export declare function insureLost(lost: readonly ItemStack[], policy: InsurancePolicy, userId: string, now: number, rng?: () => number): ScheduledDelivery | null;
46
+ export interface ConsolationPolicy {
47
+ loadoutId: string;
48
+ when?: "always" | "if-carried-empty";
49
+ }
50
+ export declare function resolveConsolation(policy: ConsolationPolicy, partition: DeathPartition): {
51
+ loadoutId: string;
52
+ } | null;
@@ -0,0 +1,77 @@
1
+ export function tierOf(tiers, inventoryId) {
2
+ return tiers[inventoryId] ?? "carried";
3
+ }
4
+ function mergeStacks(stacks) {
5
+ const totals = new Map();
6
+ for (const stack of stacks)
7
+ totals.set(stack.itemId, (totals.get(stack.itemId) ?? 0) + stack.count);
8
+ const out = [];
9
+ for (const [itemId, count] of totals)
10
+ if (count > 0)
11
+ out.push({ itemId, count });
12
+ return out;
13
+ }
14
+ export function partitionOnDeath(containers) {
15
+ const kept = [];
16
+ const lost = [];
17
+ for (const container of containers) {
18
+ (container.tier === "banked" ? kept : lost).push(...container.items);
19
+ }
20
+ return { kept: mergeStacks(kept), lost: mergeStacks(lost) };
21
+ }
22
+ export function createDeliveryQueue() {
23
+ const entries = new Map();
24
+ let counter = 0;
25
+ return {
26
+ schedule(entry) {
27
+ const id = entry.id ?? `delivery_${(counter += 1)}`;
28
+ const stored = {
29
+ id,
30
+ userId: entry.userId,
31
+ inventoryId: entry.inventoryId,
32
+ items: entry.items,
33
+ deliverAt: entry.deliverAt,
34
+ };
35
+ entries.set(id, stored);
36
+ return stored;
37
+ },
38
+ due(now) {
39
+ return [...entries.values()].filter((e) => e.deliverAt <= now);
40
+ },
41
+ claimDue(now) {
42
+ const ready = [...entries.values()].filter((e) => e.deliverAt <= now);
43
+ for (const entry of ready)
44
+ entries.delete(entry.id);
45
+ return ready;
46
+ },
47
+ pending(userId) {
48
+ const all = [...entries.values()];
49
+ return userId === undefined ? all : all.filter((e) => e.userId === userId);
50
+ },
51
+ cancel(id) {
52
+ return entries.delete(id);
53
+ },
54
+ };
55
+ }
56
+ function resolveDelay(delay, rng) {
57
+ if (typeof delay === "number")
58
+ return delay;
59
+ const [min, max] = delay;
60
+ return min + rng() * (max - min);
61
+ }
62
+ export function insureLost(lost, policy, userId, now, rng = Math.random) {
63
+ const items = lost.filter((stack) => policy.isInsured(stack.itemId));
64
+ if (items.length === 0)
65
+ return null;
66
+ return {
67
+ userId,
68
+ inventoryId: policy.returnInventoryId,
69
+ items,
70
+ deliverAt: now + resolveDelay(policy.delaySeconds, rng),
71
+ };
72
+ }
73
+ export function resolveConsolation(policy, partition) {
74
+ if (policy.when === "if-carried-empty" && partition.lost.length > 0)
75
+ return null;
76
+ return { loadoutId: policy.loadoutId };
77
+ }
@@ -0,0 +1,54 @@
1
+ export type AffixOp = "add" | "mul";
2
+ export interface AffixDef {
3
+ id: string;
4
+ stat: string;
5
+ op?: AffixOp;
6
+ roll: number | [number, number];
7
+ weight: number;
8
+ namePart?: {
9
+ position: "prefix" | "suffix";
10
+ text: string;
11
+ };
12
+ }
13
+ export interface AffixPool {
14
+ id: string;
15
+ affixes: readonly AffixDef[];
16
+ }
17
+ export interface RarityTier {
18
+ id: string;
19
+ weight: number;
20
+ affixCount: number | [number, number];
21
+ statScale?: number;
22
+ namePart?: string;
23
+ pools?: readonly string[];
24
+ }
25
+ export interface ItemBaseDef {
26
+ id: string;
27
+ name: string;
28
+ baseStats: Record<string, number>;
29
+ pools: readonly string[];
30
+ }
31
+ export interface RollerConfig {
32
+ pools: readonly AffixPool[];
33
+ rarities: readonly RarityTier[];
34
+ }
35
+ export interface RolledAffix {
36
+ id: string;
37
+ stat: string;
38
+ op: AffixOp;
39
+ value: number;
40
+ }
41
+ export interface RolledItem {
42
+ baseId: string;
43
+ rarity: string;
44
+ name: string;
45
+ affixes: readonly RolledAffix[];
46
+ stats: Record<string, number>;
47
+ }
48
+ export { seededRng } from "../random/rng.js";
49
+ export interface AffixRoller {
50
+ rollRarity(rng: () => number): RarityTier;
51
+ roll(base: ItemBaseDef, rarityId: string, rng: () => number): RolledItem;
52
+ rollRandom(base: ItemBaseDef, rng: () => number): RolledItem;
53
+ }
54
+ export declare function createAffixRoller(config: RollerConfig): AffixRoller;
@@ -0,0 +1,115 @@
1
+ import { pickWeighted } from "../world/scatterItems.js";
2
+ export { seededRng } from "../random/rng.js";
3
+ function resolveRoll(roll, rng) {
4
+ if (typeof roll === "number")
5
+ return roll;
6
+ const [min, max] = roll;
7
+ return min + rng() * (max - min);
8
+ }
9
+ function resolveCount(count, rng) {
10
+ if (typeof count === "number")
11
+ return count;
12
+ const [min, max] = count;
13
+ return min + Math.floor(rng() * (max - min + 1));
14
+ }
15
+ function assertConfig(config) {
16
+ if (config.rarities.length === 0)
17
+ throw new Error("affix roller must have at least one rarity tier");
18
+ for (const tier of config.rarities) {
19
+ if (!(tier.weight > 0))
20
+ throw new Error(`rarity "${tier.id}" weight must be positive`);
21
+ }
22
+ }
23
+ export function createAffixRoller(config) {
24
+ assertConfig(config);
25
+ const poolById = new Map(config.pools.map((p) => [p.id, p]));
26
+ const rarityById = new Map(config.rarities.map((r) => [r.id, r]));
27
+ const rarityEntries = config.rarities.map((value) => ({ value, weight: value.weight }));
28
+ function candidatesFor(base, rarity) {
29
+ const allowed = rarity.pools;
30
+ const out = [];
31
+ for (const poolId of base.pools) {
32
+ if (allowed !== undefined && !allowed.includes(poolId))
33
+ continue;
34
+ const pool = poolById.get(poolId);
35
+ if (pool === undefined)
36
+ continue;
37
+ for (const affix of pool.affixes)
38
+ out.push(affix);
39
+ }
40
+ return out;
41
+ }
42
+ function drawAffixes(candidates, count, rng) {
43
+ const remaining = candidates.slice();
44
+ const drawn = [];
45
+ const target = Math.min(count, remaining.length);
46
+ for (let i = 0; i < target; i++) {
47
+ const picked = pickWeighted(remaining.map((value) => ({ value, weight: value.weight })), rng());
48
+ if (picked === null)
49
+ break;
50
+ remaining.splice(remaining.indexOf(picked), 1);
51
+ drawn.push({ id: picked.id, stat: picked.stat, op: picked.op ?? "add", value: resolveRoll(picked.roll, rng) });
52
+ }
53
+ return drawn;
54
+ }
55
+ function computeStats(base, rarity, affixes) {
56
+ const scale = rarity.statScale ?? 1;
57
+ const stats = {};
58
+ for (const [key, value] of Object.entries(base.baseStats))
59
+ stats[key] = value * scale;
60
+ for (const affix of affixes) {
61
+ if (affix.op === "add")
62
+ stats[affix.stat] = (stats[affix.stat] ?? 0) + affix.value;
63
+ }
64
+ for (const affix of affixes) {
65
+ if (affix.op === "mul")
66
+ stats[affix.stat] = (stats[affix.stat] ?? 0) * affix.value;
67
+ }
68
+ return stats;
69
+ }
70
+ function composeName(base, rarity, affixes) {
71
+ const prefix = affixes.find((a) => affixNameFor(a)?.position === "prefix");
72
+ const suffix = affixes.find((a) => affixNameFor(a)?.position === "suffix");
73
+ const parts = [];
74
+ if (rarity.namePart !== undefined)
75
+ parts.push(rarity.namePart);
76
+ if (prefix !== undefined)
77
+ parts.push(affixNameFor(prefix).text);
78
+ parts.push(base.name);
79
+ if (suffix !== undefined)
80
+ parts.push(affixNameFor(suffix).text);
81
+ return parts.join(" ");
82
+ }
83
+ const nameByAffixId = new Map();
84
+ for (const pool of config.pools) {
85
+ for (const affix of pool.affixes)
86
+ if (affix.namePart !== undefined)
87
+ nameByAffixId.set(affix.id, affix.namePart);
88
+ }
89
+ function affixNameFor(affix) {
90
+ return nameByAffixId.get(affix.id);
91
+ }
92
+ return {
93
+ rollRarity(rng) {
94
+ const picked = pickWeighted(rarityEntries, rng());
95
+ return picked ?? config.rarities[config.rarities.length - 1];
96
+ },
97
+ roll(base, rarityId, rng) {
98
+ const rarity = rarityById.get(rarityId);
99
+ if (rarity === undefined)
100
+ throw new Error(`unknown rarity tier: ${rarityId}`);
101
+ const count = resolveCount(rarity.affixCount, rng);
102
+ const affixes = drawAffixes(candidatesFor(base, rarity), count, rng);
103
+ return {
104
+ baseId: base.id,
105
+ rarity: rarity.id,
106
+ name: composeName(base, rarity, affixes),
107
+ affixes,
108
+ stats: computeStats(base, rarity, affixes),
109
+ };
110
+ },
111
+ rollRandom(base, rng) {
112
+ return this.roll(base, this.rollRarity(rng).id, rng);
113
+ },
114
+ };
115
+ }
@@ -0,0 +1,51 @@
1
+ export interface RepairMaterial {
2
+ item: string;
3
+ perPoint: number;
4
+ }
5
+ export interface RepairSpec {
6
+ materials: readonly RepairMaterial[];
7
+ station?: string;
8
+ qualityLossPerRepair?: number;
9
+ }
10
+ export interface DurabilitySpec {
11
+ max: number;
12
+ wearPerUse?: number;
13
+ wearPerHit?: number;
14
+ disableAtZero?: boolean;
15
+ repair?: RepairSpec;
16
+ }
17
+ export interface DurabilityState {
18
+ current: number;
19
+ max: number;
20
+ }
21
+ export type WearKind = "use" | "hit";
22
+ export interface RepairCost {
23
+ item: string;
24
+ count: number;
25
+ }
26
+ export interface RepairQuote {
27
+ materials: readonly RepairCost[];
28
+ restored: number;
29
+ state: DurabilityState;
30
+ }
31
+ export declare function createDurability(spec: DurabilitySpec): DurabilityState;
32
+ export declare function durabilityFraction(state: DurabilityState): number;
33
+ export declare function isBroken(state: DurabilityState): boolean;
34
+ export declare function isDisabled(spec: DurabilitySpec, state: DurabilityState): boolean;
35
+ export declare function wearAmount(spec: DurabilitySpec, kind: WearKind): number;
36
+ export declare function applyWear(state: DurabilityState, amount: number): DurabilityState;
37
+ export declare function wear(spec: DurabilitySpec, state: DurabilityState, kind: WearKind, times?: number): DurabilityState;
38
+ export declare function canRepairAt(spec: DurabilitySpec, stationId?: string): boolean;
39
+ export declare function repairQuote(spec: DurabilitySpec, state: DurabilityState, options?: {
40
+ to?: number;
41
+ station?: string;
42
+ }): RepairQuote | null;
43
+ export interface DurabilityTracker {
44
+ init(instanceId: string, spec: DurabilitySpec): DurabilityState;
45
+ get(instanceId: string): DurabilityState | null;
46
+ set(instanceId: string, state: DurabilityState): void;
47
+ wear(instanceId: string, spec: DurabilitySpec, kind: WearKind, times?: number): DurabilityState | null;
48
+ isDisabled(instanceId: string, spec: DurabilitySpec): boolean;
49
+ remove(instanceId: string): void;
50
+ }
51
+ export declare function createDurabilityTracker(): DurabilityTracker;
@@ -0,0 +1,78 @@
1
+ function clamp(value, min, max) {
2
+ return value < min ? min : value > max ? max : value;
3
+ }
4
+ export function createDurability(spec) {
5
+ const max = Math.max(0, spec.max);
6
+ return { current: max, max };
7
+ }
8
+ export function durabilityFraction(state) {
9
+ return state.max <= 0 ? 0 : clamp(state.current / state.max, 0, 1);
10
+ }
11
+ export function isBroken(state) {
12
+ return state.current <= 0;
13
+ }
14
+ export function isDisabled(spec, state) {
15
+ return isBroken(state) && spec.disableAtZero !== false;
16
+ }
17
+ export function wearAmount(spec, kind) {
18
+ return kind === "use" ? spec.wearPerUse ?? 0 : spec.wearPerHit ?? 0;
19
+ }
20
+ export function applyWear(state, amount) {
21
+ if (amount <= 0)
22
+ return state;
23
+ return { current: clamp(state.current - amount, 0, state.max), max: state.max };
24
+ }
25
+ export function wear(spec, state, kind, times = 1) {
26
+ return applyWear(state, wearAmount(spec, kind) * Math.max(0, times));
27
+ }
28
+ export function canRepairAt(spec, stationId) {
29
+ const repair = spec.repair;
30
+ if (repair === undefined)
31
+ return false;
32
+ return repair.station === undefined || repair.station === stationId;
33
+ }
34
+ export function repairQuote(spec, state, options) {
35
+ const repair = spec.repair;
36
+ if (repair === undefined)
37
+ return null;
38
+ if (repair.station !== undefined && repair.station !== options?.station)
39
+ return null;
40
+ const loss = repair.qualityLossPerRepair ?? 0;
41
+ const newMax = Math.max(1, state.max - loss);
42
+ const capped = Math.min(state.current, newMax);
43
+ const target = clamp(options?.to ?? newMax, capped, newMax);
44
+ const restored = Math.max(0, target - capped);
45
+ const materials = repair.materials.map((m) => ({ item: m.item, count: Math.ceil(m.perPoint * restored) }));
46
+ return { materials, restored, state: { current: target, max: newMax } };
47
+ }
48
+ export function createDurabilityTracker() {
49
+ const states = new Map();
50
+ return {
51
+ init(instanceId, spec) {
52
+ const state = createDurability(spec);
53
+ states.set(instanceId, state);
54
+ return state;
55
+ },
56
+ get(instanceId) {
57
+ return states.get(instanceId) ?? null;
58
+ },
59
+ set(instanceId, state) {
60
+ states.set(instanceId, state);
61
+ },
62
+ wear(instanceId, spec, kind, times) {
63
+ const current = states.get(instanceId);
64
+ if (current === undefined)
65
+ return null;
66
+ const next = wear(spec, current, kind, times);
67
+ states.set(instanceId, next);
68
+ return next;
69
+ },
70
+ isDisabled(instanceId, spec) {
71
+ const state = states.get(instanceId);
72
+ return state !== undefined && isDisabled(spec, state);
73
+ },
74
+ remove(instanceId) {
75
+ states.delete(instanceId);
76
+ },
77
+ };
78
+ }
@@ -0,0 +1,46 @@
1
+ export interface MountSlotDef {
2
+ id: string;
3
+ accepts: string | readonly string[];
4
+ required?: boolean;
5
+ }
6
+ export interface ModularItemDef {
7
+ id: string;
8
+ baseStats: Record<string, number>;
9
+ slots: readonly MountSlotDef[];
10
+ }
11
+ export interface PartDef {
12
+ id: string;
13
+ category: string;
14
+ stats?: Record<string, number>;
15
+ multipliers?: Record<string, number>;
16
+ }
17
+ export interface InstalledPart {
18
+ slotId: string;
19
+ part: PartDef;
20
+ }
21
+ export type InstallResult = {
22
+ status: "ok";
23
+ installed: readonly InstalledPart[];
24
+ } | {
25
+ status: "rejected";
26
+ reason: "unknown-slot" | "wrong-category" | "slot-occupied";
27
+ };
28
+ export declare function slotById(def: ModularItemDef, slotId: string): MountSlotDef | null;
29
+ export declare function slotAccepts(slot: MountSlotDef, category: string): boolean;
30
+ export declare function install(def: ModularItemDef, installed: readonly InstalledPart[], slotId: string, part: PartDef): InstallResult;
31
+ export declare function uninstall(installed: readonly InstalledPart[], slotId: string): readonly InstalledPart[];
32
+ export declare function partInSlot(installed: readonly InstalledPart[], slotId: string): PartDef | null;
33
+ export declare function computeEffectiveStats(def: ModularItemDef, installed: readonly InstalledPart[]): Record<string, number>;
34
+ export declare function missingRequiredSlots(def: ModularItemDef, installed: readonly InstalledPart[]): string[];
35
+ export declare function isComplete(def: ModularItemDef, installed: readonly InstalledPart[]): boolean;
36
+ export interface ModularItem {
37
+ readonly def: ModularItemDef;
38
+ parts(): readonly InstalledPart[];
39
+ install(slotId: string, part: PartDef): InstallResult;
40
+ uninstall(slotId: string): readonly InstalledPart[];
41
+ partInSlot(slotId: string): PartDef | null;
42
+ effectiveStats(): Record<string, number>;
43
+ missingRequired(): string[];
44
+ isComplete(): boolean;
45
+ }
46
+ export declare function createModularItem(def: ModularItemDef, initial?: readonly InstalledPart[]): ModularItem;