@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
@@ -1,5 +1,25 @@
1
1
  import { applyPoolDelta } from "../scene/entityStats.js";
2
2
  import { distanceBetween } from "../scene/spatial.js";
3
+ export function resolveAreaTargets(spatial, input, accept) {
4
+ const falloff = input.falloff ?? "none";
5
+ const los = input.los ?? true;
6
+ const targets = [];
7
+ for (const instanceId of spatial.inRadius(input.at, input.radius)) {
8
+ if (los && !spatial.hasLineOfSight(input.at, instanceId))
9
+ continue;
10
+ let scale = 1;
11
+ if (falloff === "linear") {
12
+ const position = spatial.positionOf(instanceId);
13
+ if (position === undefined)
14
+ continue;
15
+ scale = Math.max(0, 1 - distanceBetween(input.at, position) / input.radius);
16
+ }
17
+ if (accept !== undefined && !accept(instanceId))
18
+ continue;
19
+ targets.push({ instanceId, scale });
20
+ }
21
+ return targets;
22
+ }
3
23
  const DEFAULT_DRAIN_STAT = "damage";
4
24
  export function createEffectSystem(deps) {
5
25
  function resolveRule(instanceId, effect) {
@@ -20,18 +40,21 @@ export function createEffectSystem(deps) {
20
40
  }
21
41
  return result;
22
42
  }
23
- function canReceive(instanceId, effect) {
43
+ function canReceive(instanceId, effect, magnitude) {
24
44
  const rule = resolveRule(instanceId, effect);
25
45
  if (rule === null)
26
46
  return "not-receivable";
27
47
  const stats = deps.resolveStats(instanceId);
28
48
  if (stats === undefined)
29
49
  return "unknown-instance";
30
- const anyPoolAboveMin = rule.order.some((statId) => {
50
+ const restorative = magnitude !== undefined && magnitude < 0;
51
+ const anyPoolHasHeadroom = rule.order.some((statId) => {
31
52
  const stat = stats[statId];
32
- return stat !== undefined && stat.current > stat.min;
53
+ if (stat === undefined)
54
+ return false;
55
+ return restorative ? stat.current < stat.max : stat.current > stat.min;
33
56
  });
34
- if (!anyPoolAboveMin)
57
+ if (!anyPoolHasHeadroom)
35
58
  return "pools-depleted";
36
59
  return null;
37
60
  }
@@ -60,13 +83,13 @@ export function createEffectSystem(deps) {
60
83
  return { instanceId, effect, applied, lethal };
61
84
  }
62
85
  function applyTo(instanceId, effect, via, from, scale) {
63
- if (canReceive(instanceId, effect) !== null)
64
- return null;
65
86
  const rule = resolveRule(instanceId, effect);
66
87
  const stats = deps.resolveStats(instanceId);
67
88
  if (rule === null || stats === undefined)
68
89
  return null;
69
90
  const drainMagnitude = modifiedDrainMagnitude(baseDrainMagnitude(effect, via) * scale, rule);
91
+ if (canReceive(instanceId, effect, drainMagnitude) !== null)
92
+ return null;
70
93
  const result = drainPools(instanceId, effect, rule, stats, drainMagnitude);
71
94
  if (result.lethal)
72
95
  deps.onLethal?.(instanceId, { from, via, effect });
@@ -85,20 +108,9 @@ export function createEffectSystem(deps) {
85
108
  const result = applyTo(input.to, input.effect, input.via, input.from, 1);
86
109
  return result === null ? [] : [result];
87
110
  }
88
- const falloff = input.falloff ?? "none";
89
- const los = input.los ?? true;
90
111
  const results = [];
91
- for (const instanceId of deps.spatial.inRadius(input.at, input.radius)) {
92
- if (los && !deps.spatial.hasLineOfSight(input.at, instanceId))
93
- continue;
94
- let scale = 1;
95
- if (falloff === "linear") {
96
- const position = deps.spatial.positionOf(instanceId);
97
- if (position === undefined)
98
- continue;
99
- scale = Math.max(0, 1 - distanceBetween(input.at, position) / input.radius);
100
- }
101
- const result = applyTo(instanceId, input.effect, input.via, input.from, scale);
112
+ for (const target of resolveAreaTargets(deps.spatial, input)) {
113
+ const result = applyTo(target.instanceId, input.effect, input.via, input.from, target.scale);
102
114
  if (result !== null)
103
115
  results.push(result);
104
116
  }
@@ -0,0 +1,23 @@
1
+ import type { EntityPosition } from "../scene/entityStore.js";
2
+ export interface CameraShake {
3
+ amplitude: number;
4
+ decay: number;
5
+ }
6
+ export interface HitReactionConfig {
7
+ hitstopMs: number;
8
+ knockback: number;
9
+ vertical?: number;
10
+ shake?: CameraShake;
11
+ }
12
+ export interface HitReactionInput {
13
+ attackerPos: EntityPosition;
14
+ targetPos: EntityPosition;
15
+ power?: number;
16
+ }
17
+ export interface HitReaction {
18
+ hitstopMs: number;
19
+ impulse: [number, number, number];
20
+ shake: CameraShake | null;
21
+ }
22
+ export declare function resolveHitReaction(config: HitReactionConfig, input: HitReactionInput): HitReaction;
23
+ export declare function applyImpulse(position: EntityPosition, impulse: [number, number, number]): EntityPosition;
@@ -0,0 +1,20 @@
1
+ export function resolveHitReaction(config, input) {
2
+ const power = input.power ?? 1;
3
+ const dx = input.targetPos[0] - input.attackerPos[0];
4
+ const dz = input.targetPos[2] - input.attackerPos[2];
5
+ const planar = Math.hypot(dx, dz);
6
+ const nx = planar === 0 ? 0 : dx / planar;
7
+ const nz = planar === 0 ? 0 : dz / planar;
8
+ const magnitude = config.knockback * power;
9
+ const shake = config.shake === undefined
10
+ ? null
11
+ : { amplitude: config.shake.amplitude * power, decay: config.shake.decay };
12
+ return {
13
+ hitstopMs: config.hitstopMs,
14
+ impulse: [nx * magnitude, (config.vertical ?? 0) * power, nz * magnitude],
15
+ shake,
16
+ };
17
+ }
18
+ export function applyImpulse(position, impulse) {
19
+ return [position[0] + impulse[0], position[1] + impulse[1], position[2] + impulse[2]];
20
+ }
@@ -1,3 +1,4 @@
1
+ import type { BallisticSweep } from "../physics/ballisticSweep.js";
1
2
  import type { EntityPosition } from "../scene/entityStore.js";
2
3
  import type { Aim } from "../scene/spatial.js";
3
4
  import type { CombatSpatialDeps, EffectResult, EffectSystem, EffectVia } from "./effects.js";
@@ -7,11 +8,22 @@ export interface ProjectileShotInput {
7
8
  aim: Aim;
8
9
  effect: string;
9
10
  }
10
- export interface RaycastHit {
11
+ /** A raycast hit against a scene entity — the ray reached `instanceId` at `distance` along its length. */
12
+ export interface EntityRaycastHit {
13
+ kind: "entity";
11
14
  instanceId: string;
12
15
  distance: number;
13
16
  at: EntityPosition;
14
17
  }
18
+ /** A raycast hit against a placed scene object's bounding box — see `ProjectileObjectsDeps.halfExtents`. */
19
+ export interface ObjectRaycastHit {
20
+ kind: "object";
21
+ instanceId: string;
22
+ catalogId: string;
23
+ distance: number;
24
+ at: EntityPosition;
25
+ }
26
+ export type RaycastHit = EntityRaycastHit | ObjectRaycastHit;
15
27
  export type Raycast = (from: string, aim: Aim, range: number) => RaycastHit[];
16
28
  export interface ProjectileSettleReport {
17
29
  from: string;
@@ -20,19 +32,44 @@ export interface ProjectileSettleReport {
20
32
  effect: string;
21
33
  hit: boolean;
22
34
  }
35
+ /** Read-only scene object access for the default `raycast`; matches `ObjectStore.list()`/a catalog lookup structurally. */
36
+ export interface ProjectileObjectsDeps {
37
+ list(): readonly {
38
+ instanceId: string;
39
+ catalogId: string;
40
+ position: [number, number, number];
41
+ }[];
42
+ /** Half-extents of the object's axis-aligned bounding box; `null`/omitted falls back to `[0.5, 0.5, 0.5]`. */
43
+ halfExtents?(catalogId: string): [number, number, number] | null;
44
+ }
23
45
  export interface ProjectileSystemDeps {
24
46
  effects: EffectSystem;
25
47
  spatial: CombatSpatialDeps;
26
48
  getStat(itemId: string, stat: string): number | null;
27
49
  raycast?: Raycast;
50
+ /** Optional object awareness for the default `raycast`; when set, placed objects can block or absorb a shot. */
51
+ objects?: ProjectileObjectsDeps;
52
+ /**
53
+ * Optional collision-aware arc test for ballistic shots (see `createBallisticSweep` in
54
+ * `physics/ballisticSweep`). A hit settles the shot at the impact point; `null` or omission
55
+ * falls back to the closed-form landing.
56
+ */
57
+ sweepBallistic?: BallisticSweep;
28
58
  now?: () => number;
29
59
  onSettle?(report: ProjectileSettleReport): void;
30
60
  }
61
+ export type ProjectileHit = {
62
+ kind: "entity";
63
+ instanceId: string;
64
+ distance: number;
65
+ } | {
66
+ kind: "object";
67
+ instanceId: string;
68
+ catalogId: string;
69
+ distance: number;
70
+ };
31
71
  export interface ProjectilePrediction {
32
- hits: {
33
- instanceId: string;
34
- distance: number;
35
- }[];
72
+ hits: ProjectileHit[];
36
73
  blocked?: boolean;
37
74
  }
38
75
  export type SettleResult = {
@@ -2,6 +2,38 @@ const DEFAULT_RANGE = 100;
2
2
  const DEFAULT_PROJECTILE_SPEED = 15;
3
3
  const GRAVITY = 9.8;
4
4
  const BASE_HIT_RADIUS = 0.5;
5
+ const DEFAULT_OBJECT_HALF_EXTENTS = [0.5, 0.5, 0.5];
6
+ function isEntityHit(hit) {
7
+ return hit.kind === "entity";
8
+ }
9
+ function isObjectHit(hit) {
10
+ return hit.kind === "object";
11
+ }
12
+ function raySegmentAabbDistance(origin, direction, range, min, max) {
13
+ let tMin = 0;
14
+ let tMax = range;
15
+ for (let axis = 0; axis < 3; axis += 1) {
16
+ const o = origin[axis];
17
+ const d = direction[axis];
18
+ const lo = min[axis];
19
+ const hi = max[axis];
20
+ if (Math.abs(d) < 1e-9) {
21
+ if (o < lo || o > hi)
22
+ return null;
23
+ continue;
24
+ }
25
+ const inv = 1 / d;
26
+ let t1 = (lo - o) * inv;
27
+ let t2 = (hi - o) * inv;
28
+ if (t1 > t2)
29
+ [t1, t2] = [t2, t1];
30
+ tMin = Math.max(tMin, t1);
31
+ tMax = Math.min(tMax, t2);
32
+ if (tMin > tMax)
33
+ return null;
34
+ }
35
+ return tMin;
36
+ }
5
37
  function normalize(vector) {
6
38
  const length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
7
39
  if (length === 0)
@@ -42,7 +74,7 @@ export function createProjectileSystem(deps) {
42
74
  if (direction === null)
43
75
  return [];
44
76
  const spreadRad = (aimSpreadDeg(aim) * Math.PI) / 180;
45
- const hits = [];
77
+ const entityHits = [];
46
78
  for (const instanceId of deps.spatial.inRadius(origin, range)) {
47
79
  if (instanceId === from)
48
80
  continue;
@@ -61,8 +93,42 @@ export function createProjectileSystem(deps) {
61
93
  const perpendicular = Math.sqrt(px * px + py * py + pz * pz);
62
94
  if (perpendicular > BASE_HIT_RADIUS + Math.tan(spreadRad) * along)
63
95
  continue;
64
- hits.push({ instanceId, distance: along, at: position });
96
+ entityHits.push({ kind: "entity", instanceId, distance: along, at: position });
65
97
  }
98
+ let nearestObject = null;
99
+ for (const object of deps.objects?.list() ?? []) {
100
+ const half = deps.objects?.halfExtents?.(object.catalogId) ?? DEFAULT_OBJECT_HALF_EXTENTS;
101
+ const min = [
102
+ object.position[0] - half[0],
103
+ object.position[1] - half[1],
104
+ object.position[2] - half[2],
105
+ ];
106
+ const max = [
107
+ object.position[0] + half[0],
108
+ object.position[1] + half[1],
109
+ object.position[2] + half[2],
110
+ ];
111
+ const distance = raySegmentAabbDistance(origin, direction, range, min, max);
112
+ if (distance === null)
113
+ continue;
114
+ if (nearestObject === null || distance < nearestObject.distance) {
115
+ nearestObject = {
116
+ kind: "object",
117
+ instanceId: object.instanceId,
118
+ catalogId: object.catalogId,
119
+ distance,
120
+ at: [
121
+ origin[0] + direction[0] * distance,
122
+ origin[1] + direction[1] * distance,
123
+ origin[2] + direction[2] * distance,
124
+ ],
125
+ };
126
+ }
127
+ }
128
+ const blockDistance = nearestObject?.distance ?? Infinity;
129
+ const hits = entityHits.filter((hit) => hit.distance <= blockDistance);
130
+ if (nearestObject !== null)
131
+ hits.push(nearestObject);
66
132
  return hits.sort((a, b) => a.distance - b.distance);
67
133
  });
68
134
  function resolveRange(via) {
@@ -71,7 +137,7 @@ export function createProjectileSystem(deps) {
71
137
  function isBallistic(via) {
72
138
  return itemStat(via, "projectile.fuseTime") !== null || itemStat(via, "explosion.radius") !== null;
73
139
  }
74
- function ballisticSettlePoint(input) {
140
+ function ballisticArc(input) {
75
141
  const origin = shotOrigin(input.from, input.aim) ?? [0, 0, 0];
76
142
  const direction = aimDirection(input.aim) ?? [0, 0, 1];
77
143
  const speed = itemStat(input.via, "projectile.speed") ?? DEFAULT_PROJECTILE_SPEED;
@@ -85,16 +151,32 @@ export function createProjectileSystem(deps) {
85
151
  : flightCap;
86
152
  const flightTime = Math.min(impactTime, flightCap);
87
153
  const settledY = Math.max(0, origin[1] + verticalSpeed * flightTime - 0.5 * gravity * flightTime * flightTime);
88
- return [
89
- origin[0] + direction[0] * speed * flightTime,
90
- settledY,
91
- origin[2] + direction[2] * speed * flightTime,
92
- ];
154
+ return {
155
+ origin,
156
+ velocity: [direction[0] * speed, verticalSpeed, direction[2] * speed],
157
+ gravity,
158
+ flightTime,
159
+ landing: [
160
+ origin[0] + direction[0] * speed * flightTime,
161
+ settledY,
162
+ origin[2] + direction[2] * speed * flightTime,
163
+ ],
164
+ };
165
+ }
166
+ function ballisticSettlePoint(input) {
167
+ const arc = ballisticArc(input);
168
+ const sweep = deps.sweepBallistic;
169
+ if (sweep !== undefined) {
170
+ const impact = sweep(arc.origin, arc.velocity, arc.gravity, arc.flightTime);
171
+ if (impact !== null)
172
+ return impact.point;
173
+ }
174
+ return arc.landing;
93
175
  }
94
176
  function predictHits(input) {
95
177
  const aim = withWeaponSpread(input.via, input.aim);
96
178
  const rawHits = raycast(input.from, aim, resolveRange(input.via));
97
- const visible = rawHits.filter((hit) => deps.spatial.hasLineOfSight(input.from, hit.instanceId));
179
+ const visible = rawHits.filter((hit) => isObjectHit(hit) || deps.spatial.hasLineOfSight(input.from, hit.instanceId));
98
180
  return { rawHits, visible };
99
181
  }
100
182
  function settledAt(input, hits) {
@@ -112,7 +194,9 @@ export function createProjectileSystem(deps) {
112
194
  willHitProjectile(input) {
113
195
  const { rawHits, visible } = predictHits(input);
114
196
  const prediction = {
115
- hits: visible.map(({ instanceId, distance }) => ({ instanceId, distance })),
197
+ hits: visible.map((hit) => isObjectHit(hit)
198
+ ? { kind: "object", instanceId: hit.instanceId, catalogId: hit.catalogId, distance: hit.distance }
199
+ : { kind: "entity", instanceId: hit.instanceId, distance: hit.distance }),
116
200
  };
117
201
  if (rawHits.length > 0 && visible.length === 0)
118
202
  prediction.blocked = true;
@@ -139,7 +223,9 @@ export function createProjectileSystem(deps) {
139
223
  return { status: "settled", shotId, at, hits: [] };
140
224
  }
141
225
  const { visible } = predictHits(input);
142
- const receivable = visible.filter((hit) => deps.effects.canReceive(hit.instanceId, input.effect) === null);
226
+ const entityHits = visible.filter(isEntityHit);
227
+ const objectHits = visible.filter(isObjectHit);
228
+ const receivable = entityHits.filter((hit) => deps.effects.canReceive(hit.instanceId, input.effect) === null);
143
229
  const pellets = Math.max(1, Math.round(itemStat(input.via, "pellets") ?? 1));
144
230
  const hits = [];
145
231
  if (receivable.length > 0) {
@@ -153,7 +239,7 @@ export function createProjectileSystem(deps) {
153
239
  }));
154
240
  }
155
241
  }
156
- const at = settledAt(input, receivable);
242
+ const at = settledAt(input, receivable.length > 0 ? receivable : objectHits);
157
243
  deps.onSettle?.({ from: input.from, origin, at, effect: input.effect, hit: receivable.length > 0 });
158
244
  return { status: "settled", shotId, at, hits };
159
245
  },
@@ -0,0 +1,13 @@
1
+ export type ResistVerdict = "immune" | "resist" | "normal" | "vulnerable";
2
+ export interface ResistanceMatrix {
3
+ categories: Record<string, Record<string, ResistVerdict>>;
4
+ multipliers?: Partial<Record<ResistVerdict, number>>;
5
+ default?: ResistVerdict;
6
+ }
7
+ export interface ResistanceResult {
8
+ verdict: ResistVerdict;
9
+ multiplier: number;
10
+ immune: boolean;
11
+ }
12
+ export declare function resolveResistance(matrix: ResistanceMatrix, category: string, targetProperties: readonly string[]): ResistanceResult;
13
+ export declare function resistanceScale(matrix: ResistanceMatrix, category: string, targetProperties: readonly string[]): number;
@@ -0,0 +1,46 @@
1
+ const DEFAULT_MULTIPLIERS = {
2
+ immune: 0,
3
+ resist: 0.5,
4
+ normal: 1,
5
+ vulnerable: 2,
6
+ };
7
+ function multiplierFor(matrix, verdict) {
8
+ return matrix.multipliers?.[verdict] ?? DEFAULT_MULTIPLIERS[verdict];
9
+ }
10
+ function summaryVerdict(matrix, immune, net) {
11
+ if (immune)
12
+ return "immune";
13
+ const normal = multiplierFor(matrix, "normal");
14
+ if (net > normal)
15
+ return "vulnerable";
16
+ if (net < normal)
17
+ return "resist";
18
+ return "normal";
19
+ }
20
+ export function resolveResistance(matrix, category, targetProperties) {
21
+ const byProperty = matrix.categories[category] ?? {};
22
+ const fallback = matrix.default ?? "normal";
23
+ let net = 1;
24
+ let matched = false;
25
+ let immune = false;
26
+ for (const property of targetProperties) {
27
+ const verdict = byProperty[property];
28
+ if (verdict === undefined)
29
+ continue;
30
+ matched = true;
31
+ if (verdict === "immune")
32
+ immune = true;
33
+ net *= multiplierFor(matrix, verdict);
34
+ }
35
+ if (!matched) {
36
+ if (fallback === "immune")
37
+ return { verdict: "immune", multiplier: 0, immune: true };
38
+ const multiplier = multiplierFor(matrix, fallback);
39
+ return { verdict: fallback, multiplier, immune: false };
40
+ }
41
+ const multiplier = immune ? 0 : net;
42
+ return { verdict: summaryVerdict(matrix, immune, multiplier), multiplier, immune };
43
+ }
44
+ export function resistanceScale(matrix, category, targetProperties) {
45
+ return resolveResistance(matrix, category, targetProperties).multiplier;
46
+ }
@@ -0,0 +1,31 @@
1
+ import type { EntityPosition } from "../scene/entityStore.js";
2
+ export type TelegraphShape = {
3
+ kind: "circle";
4
+ radius: number;
5
+ } | {
6
+ kind: "ring";
7
+ radius: number;
8
+ innerRadius: number;
9
+ } | {
10
+ kind: "cone";
11
+ radius: number;
12
+ angle: number;
13
+ } | {
14
+ kind: "line";
15
+ length: number;
16
+ width: number;
17
+ };
18
+ export interface TelegraphConfig {
19
+ shape: TelegraphShape;
20
+ at: EntityPosition;
21
+ dir?: number;
22
+ windupMs?: number;
23
+ turns?: number;
24
+ kind?: string;
25
+ }
26
+ export declare function telegraphProgress(windupMs: number, startedAtMs: number, nowMs: number): number;
27
+ export declare function telegraphFired(windupMs: number, startedAtMs: number, nowMs: number): boolean;
28
+ export declare function telegraphTurnProgress(config: TelegraphConfig, startedTurn: number, currentTurn: number): number;
29
+ export declare function telegraphFiredAtTurn(config: TelegraphConfig, startedTurn: number, currentTurn: number): boolean;
30
+ export declare function telegraphTurnsRemaining(config: TelegraphConfig, startedTurn: number, currentTurn: number): number;
31
+ export declare function pointInTelegraph(config: TelegraphConfig, point: EntityPosition): boolean;
@@ -0,0 +1,51 @@
1
+ export function telegraphProgress(windupMs, startedAtMs, nowMs) {
2
+ if (windupMs <= 0)
3
+ return 1;
4
+ return Math.max(0, Math.min(1, (nowMs - startedAtMs) / windupMs));
5
+ }
6
+ export function telegraphFired(windupMs, startedAtMs, nowMs) {
7
+ return nowMs - startedAtMs >= windupMs;
8
+ }
9
+ export function telegraphTurnProgress(config, startedTurn, currentTurn) {
10
+ const turns = config.turns ?? 0;
11
+ if (turns <= 0)
12
+ return 1;
13
+ return Math.max(0, Math.min(1, (currentTurn - startedTurn) / turns));
14
+ }
15
+ export function telegraphFiredAtTurn(config, startedTurn, currentTurn) {
16
+ const turns = config.turns ?? 0;
17
+ return currentTurn - startedTurn >= turns;
18
+ }
19
+ export function telegraphTurnsRemaining(config, startedTurn, currentTurn) {
20
+ const turns = config.turns ?? 0;
21
+ return Math.max(0, turns - (currentTurn - startedTurn));
22
+ }
23
+ function planarDelta(config, point) {
24
+ return { dx: point[0] - config.at[0], dz: point[2] - config.at[2] };
25
+ }
26
+ export function pointInTelegraph(config, point) {
27
+ const { shape } = config;
28
+ const { dx, dz } = planarDelta(config, point);
29
+ const dist = Math.hypot(dx, dz);
30
+ const dir = config.dir ?? 0;
31
+ if (shape.kind === "circle")
32
+ return dist <= shape.radius;
33
+ if (shape.kind === "ring")
34
+ return dist >= shape.innerRadius && dist <= shape.radius;
35
+ if (shape.kind === "cone") {
36
+ if (dist > shape.radius || dist === 0)
37
+ return dist === 0;
38
+ const angleTo = Math.atan2(dx, dz);
39
+ let delta = angleTo - dir;
40
+ while (delta > Math.PI)
41
+ delta -= Math.PI * 2;
42
+ while (delta < -Math.PI)
43
+ delta += Math.PI * 2;
44
+ return Math.abs(delta) <= shape.angle / 2;
45
+ }
46
+ const forwardX = Math.sin(dir);
47
+ const forwardZ = Math.cos(dir);
48
+ const along = dx * forwardX + dz * forwardZ;
49
+ const lateral = Math.abs(dx * forwardZ - dz * forwardX);
50
+ return along >= 0 && along <= shape.length && lateral <= shape.width / 2;
51
+ }
@@ -3,7 +3,7 @@ export interface CommandRejection {
3
3
  }
4
4
  export interface CommandDefinition<TState, TInput> {
5
5
  validate?(state: TState, input: TInput): CommandRejection | null;
6
- apply(state: TState, input: TInput): TState;
6
+ apply(state: TState, input: TInput): TState | void;
7
7
  }
8
8
  export type CommandResult<TState> = {
9
9
  status: "applied";
@@ -20,7 +20,8 @@ export function createCommandRegistry() {
20
20
  const rejection = definition.validate?.(state, input) ?? null;
21
21
  if (rejection)
22
22
  return { status: "rejected", reason: rejection.reason };
23
- return { status: "applied", state: definition.apply(state, input) };
23
+ const next = definition.apply(state, input);
24
+ return { status: "applied", state: next === undefined ? state : next };
24
25
  },
25
26
  };
26
27
  }
@@ -0,0 +1,59 @@
1
+ import type { RecipeItem } from "./recipe.js";
2
+ export type SoilState = "untilled" | "tilled";
3
+ export interface CropDef {
4
+ id: string;
5
+ stages: readonly number[];
6
+ regrowDays?: number;
7
+ needsDailyWater?: boolean;
8
+ harvest?: RecipeItem;
9
+ }
10
+ export interface CropTileState {
11
+ soil: SoilState;
12
+ watered: boolean;
13
+ cropId: string | null;
14
+ stage: number;
15
+ stageProgress: number;
16
+ harvestable: boolean;
17
+ }
18
+ export type CropCatalog = (cropId: string) => CropDef | null;
19
+ export declare function emptyTile(): CropTileState;
20
+ export declare function canTill(state: CropTileState): boolean;
21
+ export declare function tillTile(state: CropTileState): CropTileState;
22
+ export declare function canPlant(state: CropTileState): boolean;
23
+ export declare function plantCrop(state: CropTileState, cropId: string): CropTileState;
24
+ export declare function waterTile(state: CropTileState): CropTileState;
25
+ export declare function maturityDays(def: CropDef): number;
26
+ export declare function advanceCropDay(def: CropDef, state: CropTileState): CropTileState;
27
+ export interface HarvestResult {
28
+ state: CropTileState;
29
+ yield: RecipeItem | null;
30
+ }
31
+ export declare function harvestCrop(def: CropDef, state: CropTileState): HarvestResult;
32
+ export type TileCoord = readonly [number, number];
33
+ export type TilePattern = readonly TileCoord[];
34
+ export declare function tileKey(coord: TileCoord): string;
35
+ export declare function singleTile(): TilePattern;
36
+ export declare function squarePattern(radius: number): TilePattern;
37
+ export declare function diamondPattern(radius: number): TilePattern;
38
+ export declare function rectPattern(width: number, depth: number): TilePattern;
39
+ export declare function patternAt(center: TileCoord, pattern: TilePattern): TileCoord[];
40
+ export interface ApplyToolResult {
41
+ tiles: Map<string, CropTileState>;
42
+ changed: TileCoord[];
43
+ }
44
+ export declare function applyToolToTiles(tiles: ReadonlyMap<string, CropTileState>, center: TileCoord, pattern: TilePattern, apply: (state: CropTileState, coord: TileCoord) => CropTileState, fill?: () => CropTileState): ApplyToolResult;
45
+ export interface DayTicker {
46
+ tick(currentDay: number): number;
47
+ day(): number;
48
+ }
49
+ export declare function createDayTicker(startDay?: number): DayTicker;
50
+ export interface CropField {
51
+ get(coord: TileCoord): CropTileState;
52
+ till(coord: TileCoord, pattern?: TilePattern): TileCoord[];
53
+ plant(coord: TileCoord, cropId: string): boolean;
54
+ water(coord: TileCoord, pattern?: TilePattern): TileCoord[];
55
+ harvest(coord: TileCoord): RecipeItem | null;
56
+ advanceDay(days?: number): void;
57
+ tiles(): ReadonlyMap<string, CropTileState>;
58
+ }
59
+ export declare function createCropField(catalog?: CropCatalog): CropField;