@jgengine/core 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +2 -0
  3. package/dist/ai/flock.d.ts +36 -0
  4. package/dist/ai/flock.js +182 -0
  5. package/dist/ai/groupAssist.d.ts +19 -0
  6. package/dist/ai/groupAssist.js +59 -0
  7. package/dist/ai/laneSelect.d.ts +21 -0
  8. package/dist/ai/laneSelect.js +37 -0
  9. package/dist/ai/mobBrain.d.ts +68 -0
  10. package/dist/ai/mobBrain.js +115 -0
  11. package/dist/ai/spawnDirector.d.ts +5 -0
  12. package/dist/ai/spawnDirector.js +14 -2
  13. package/dist/ai/threat.d.ts +2 -0
  14. package/dist/ai/threat.js +34 -0
  15. package/dist/anim/easing.d.ts +14 -0
  16. package/dist/anim/easing.js +55 -0
  17. package/dist/behaviour/behaviour.d.ts +136 -0
  18. package/dist/behaviour/behaviour.js +388 -0
  19. package/dist/cards/cardPile.d.ts +1 -0
  20. package/dist/cards/cardPile.js +12 -3
  21. package/dist/cartridge/runtime.d.ts +15 -0
  22. package/dist/cartridge/runtime.js +556 -0
  23. package/dist/cartridge/spec.d.ts +188 -0
  24. package/dist/cartridge/spec.js +19 -0
  25. package/dist/cartridge/validate.d.ts +2 -0
  26. package/dist/cartridge/validate.js +78 -0
  27. package/dist/combat/abilityKit.d.ts +22 -2
  28. package/dist/combat/abilityKit.js +72 -13
  29. package/dist/combat/castRunner.d.ts +40 -0
  30. package/dist/combat/castRunner.js +56 -0
  31. package/dist/combat/comboPoints.d.ts +14 -0
  32. package/dist/combat/comboPoints.js +52 -0
  33. package/dist/combat/defensiveWindow.d.ts +1 -1
  34. package/dist/combat/defensiveWindow.js +5 -3
  35. package/dist/combat/effects.d.ts +1 -1
  36. package/dist/combat/effects.js +9 -6
  37. package/dist/combat/projectiles.d.ts +64 -6
  38. package/dist/combat/projectiles.js +232 -74
  39. package/dist/combat/resistance.d.ts +16 -4
  40. package/dist/combat/resistance.js +43 -7
  41. package/dist/combat/resourcePool.d.ts +20 -0
  42. package/dist/combat/resourcePool.js +55 -0
  43. package/dist/combat/shotOrigin.d.ts +40 -0
  44. package/dist/combat/shotOrigin.js +57 -0
  45. package/dist/combat/telegraph.d.ts +30 -1
  46. package/dist/combat/telegraph.js +61 -0
  47. package/dist/commands/commandRegistry.d.ts +1 -1
  48. package/dist/commands/commandRegistry.js +2 -1
  49. package/dist/crafting/recipe.js +4 -1
  50. package/dist/data/dataSource.d.ts +26 -0
  51. package/dist/data/dataSource.js +93 -0
  52. package/dist/data/devProxy.d.ts +11 -0
  53. package/dist/data/devProxy.js +38 -0
  54. package/dist/data/fetchJson.d.ts +20 -0
  55. package/dist/data/fetchJson.js +43 -0
  56. package/dist/data/jsonDataSource.d.ts +4 -0
  57. package/dist/data/jsonDataSource.js +6 -0
  58. package/dist/devtools/devtools.d.ts +205 -0
  59. package/dist/devtools/devtools.js +832 -0
  60. package/dist/devtools/transformTunables.d.ts +14 -0
  61. package/dist/devtools/transformTunables.js +38 -0
  62. package/dist/devtools/tunableSchema.d.ts +140 -0
  63. package/dist/devtools/tunableSchema.js +476 -0
  64. package/dist/faction/factions.d.ts +33 -0
  65. package/dist/faction/factions.js +76 -0
  66. package/dist/faction/reputation.d.ts +32 -0
  67. package/dist/faction/reputation.js +82 -0
  68. package/dist/game/chat.d.ts +68 -0
  69. package/dist/game/chat.js +173 -0
  70. package/dist/game/chatFilter.d.ts +15 -0
  71. package/dist/game/chatFilter.js +55 -0
  72. package/dist/game/controlGate.d.ts +4 -0
  73. package/dist/game/controlGate.js +7 -0
  74. package/dist/game/defineGame.d.ts +10 -2
  75. package/dist/game/defineGame.js +2 -1
  76. package/dist/game/events.d.ts +35 -0
  77. package/dist/game/gamePhase.d.ts +20 -0
  78. package/dist/game/gamePhase.js +21 -0
  79. package/dist/game/levelSequence.d.ts +44 -0
  80. package/dist/game/levelSequence.js +78 -0
  81. package/dist/game/lootTable.d.ts +10 -1
  82. package/dist/game/lootTable.js +27 -7
  83. package/dist/game/playableGame.d.ts +286 -6
  84. package/dist/game/playableGame.js +6 -1
  85. package/dist/game/progression.d.ts +1 -0
  86. package/dist/game/progression.js +23 -2
  87. package/dist/game/quest.d.ts +7 -3
  88. package/dist/game/quest.js +17 -8
  89. package/dist/game/race.d.ts +44 -1
  90. package/dist/game/race.js +116 -11
  91. package/dist/game/recordBook.d.ts +29 -0
  92. package/dist/game/recordBook.js +86 -0
  93. package/dist/game/social.d.ts +42 -0
  94. package/dist/game/social.js +122 -0
  95. package/dist/game/spawnPoints.d.ts +19 -0
  96. package/dist/game/spawnPoints.js +23 -0
  97. package/dist/game/talents.d.ts +47 -0
  98. package/dist/game/talents.js +140 -0
  99. package/dist/game/worldItem.d.ts +3 -0
  100. package/dist/game/worldItem.js +12 -6
  101. package/dist/index.d.ts +1 -0
  102. package/dist/index.js +1 -0
  103. package/dist/input/actionBindings.d.ts +10 -0
  104. package/dist/input/actionBindings.js +14 -0
  105. package/dist/input/axisInput.d.ts +26 -3
  106. package/dist/input/axisInput.js +44 -4
  107. package/dist/input/bindingOverrides.d.ts +25 -0
  108. package/dist/input/bindingOverrides.js +95 -0
  109. package/dist/input/gestureSurface.d.ts +31 -0
  110. package/dist/input/gestureSurface.js +120 -0
  111. package/dist/input/pointer.d.ts +45 -0
  112. package/dist/input/pointer.js +48 -0
  113. package/dist/input/pointerAxis.d.ts +30 -0
  114. package/dist/input/pointerAxis.js +31 -0
  115. package/dist/input/touchScheme.d.ts +89 -0
  116. package/dist/input/touchScheme.js +153 -0
  117. package/dist/item/affix.d.ts +1 -1
  118. package/dist/item/affix.js +1 -19
  119. package/dist/meta/changelog.d.ts +1 -1
  120. package/dist/meta/changelog.js +59 -1
  121. package/dist/movement/dash.d.ts +3 -0
  122. package/dist/movement/dash.js +19 -5
  123. package/dist/movement/glideModel.d.ts +44 -0
  124. package/dist/movement/glideModel.js +54 -0
  125. package/dist/movement/grappleSwing.d.ts +34 -0
  126. package/dist/movement/grappleSwing.js +56 -0
  127. package/dist/movement/leaderTrail.d.ts +29 -0
  128. package/dist/movement/leaderTrail.js +67 -0
  129. package/dist/movement/movementModel.d.ts +36 -3
  130. package/dist/movement/movementModel.js +81 -5
  131. package/dist/movement/steering.d.ts +27 -0
  132. package/dist/movement/steering.js +40 -0
  133. package/dist/movement/voxelController.d.ts +61 -0
  134. package/dist/movement/voxelController.js +229 -0
  135. package/dist/multiplayer/chatContract.d.ts +42 -0
  136. package/dist/multiplayer/chatContract.js +57 -0
  137. package/dist/multiplayer/feedWriteGate.d.ts +10 -0
  138. package/dist/multiplayer/feedWriteGate.js +12 -0
  139. package/dist/multiplayer/identity.d.ts +13 -0
  140. package/dist/multiplayer/identity.js +20 -0
  141. package/dist/multiplayer/poseSyncGate.d.ts +1 -0
  142. package/dist/multiplayer/poseSyncGate.js +13 -1
  143. package/dist/multiplayer/presenceModel.d.ts +17 -2
  144. package/dist/multiplayer/presenceModel.js +30 -4
  145. package/dist/multiplayer/voiceContract.d.ts +44 -0
  146. package/dist/multiplayer/voiceContract.js +108 -0
  147. package/dist/nav/corridors.d.ts +30 -0
  148. package/dist/nav/corridors.js +69 -0
  149. package/dist/nav/navConstrain.d.ts +14 -0
  150. package/dist/nav/navConstrain.js +26 -0
  151. package/dist/nav/navFromEnvironment.d.ts +11 -0
  152. package/dist/nav/navFromEnvironment.js +16 -0
  153. package/dist/nav/navGrid.d.ts +10 -0
  154. package/dist/nav/navGrid.js +48 -5
  155. package/dist/nav/pathFollow.d.ts +4 -2
  156. package/dist/nav/pathFollow.js +5 -3
  157. package/dist/nav/railGraph.d.ts +66 -0
  158. package/dist/nav/railGraph.js +129 -0
  159. package/dist/nav/timetable.d.ts +41 -0
  160. package/dist/nav/timetable.js +94 -0
  161. package/dist/physics/ballisticSweep.d.ts +18 -0
  162. package/dist/physics/ballisticSweep.js +49 -0
  163. package/dist/physics/buoyancy.d.ts +6 -0
  164. package/dist/physics/buoyancy.js +14 -0
  165. package/dist/physics/flowTube.d.ts +32 -0
  166. package/dist/physics/flowTube.js +64 -0
  167. package/dist/physics/forceVolume.d.ts +27 -0
  168. package/dist/physics/forceVolume.js +43 -5
  169. package/dist/physics/kinematicVehicle.d.ts +52 -0
  170. package/dist/physics/kinematicVehicle.js +81 -0
  171. package/dist/physics/physicsWorld.d.ts +54 -8
  172. package/dist/physics/physicsWorld.js +400 -79
  173. package/dist/physics/ragdoll.js +1 -1
  174. package/dist/physics/spatialGrid.d.ts +1 -1
  175. package/dist/physics/spatialGrid.js +5 -0
  176. package/dist/physics/vehicleBody.d.ts +6 -0
  177. package/dist/physics/vehicleBody.js +18 -18
  178. package/dist/puzzle/cellGrid.d.ts +26 -0
  179. package/dist/puzzle/cellGrid.js +110 -0
  180. package/dist/puzzle/fallingPiece.d.ts +24 -0
  181. package/dist/puzzle/fallingPiece.js +51 -0
  182. package/dist/random/nameGen.d.ts +18 -0
  183. package/dist/random/nameGen.js +35 -0
  184. package/dist/random/rng.d.ts +2 -0
  185. package/dist/random/rng.js +23 -0
  186. package/dist/random/seedLink.d.ts +5 -0
  187. package/dist/random/seedLink.js +35 -0
  188. package/dist/runtime/adapter.d.ts +35 -0
  189. package/dist/runtime/adapter.js +37 -1
  190. package/dist/runtime/cameraDirector.d.ts +15 -0
  191. package/dist/runtime/cameraDirector.js +31 -0
  192. package/dist/runtime/commandRunner.d.ts +2 -2
  193. package/dist/runtime/commandRunner.js +2 -2
  194. package/dist/runtime/gameContext.d.ts +65 -2
  195. package/dist/runtime/gameContext.js +280 -9
  196. package/dist/runtime/gameRuntime.d.ts +5 -1
  197. package/dist/runtime/gameRuntime.js +13 -12
  198. package/dist/runtime/hostPersistence.d.ts +30 -1
  199. package/dist/runtime/hostPersistence.js +70 -5
  200. package/dist/runtime/inputSnapshot.d.ts +12 -0
  201. package/dist/runtime/inputSnapshot.js +17 -0
  202. package/dist/runtime/motionIntents.d.ts +33 -0
  203. package/dist/runtime/motionIntents.js +43 -0
  204. package/dist/runtime/snapshot.d.ts +1 -0
  205. package/dist/runtime/snapshot.js +1 -1
  206. package/dist/runtime/transport.d.ts +32 -0
  207. package/dist/runtime/visibility.d.ts +74 -0
  208. package/dist/runtime/visibility.js +127 -0
  209. package/dist/scene/colliders.d.ts +44 -0
  210. package/dist/scene/colliders.js +77 -0
  211. package/dist/scene/entityStore.d.ts +30 -5
  212. package/dist/scene/entityStore.js +83 -13
  213. package/dist/scene/movementSpeed.d.ts +35 -0
  214. package/dist/scene/movementSpeed.js +20 -0
  215. package/dist/scene/objectQuery.d.ts +29 -0
  216. package/dist/scene/objectQuery.js +140 -0
  217. package/dist/scene/objectStore.d.ts +13 -0
  218. package/dist/scene/objectStore.js +130 -6
  219. package/dist/scene/paintLayer.d.ts +15 -0
  220. package/dist/scene/paintLayer.js +53 -0
  221. package/dist/scene/sceneRaycast.d.ts +80 -0
  222. package/dist/scene/sceneRaycast.js +284 -0
  223. package/dist/scene/spatial.d.ts +17 -1
  224. package/dist/scene/spatial.js +140 -17
  225. package/dist/sensor/concealment.d.ts +21 -0
  226. package/dist/sensor/concealment.js +63 -0
  227. package/dist/sensor/freezeMonitor.d.ts +17 -0
  228. package/dist/sensor/freezeMonitor.js +35 -0
  229. package/dist/sensor/recordingBuffer.d.ts +6 -0
  230. package/dist/sensor/recordingBuffer.js +7 -0
  231. package/dist/sensor/replayLoop.d.ts +39 -0
  232. package/dist/sensor/replayLoop.js +64 -0
  233. package/dist/sensor/visionCone.d.ts +31 -0
  234. package/dist/sensor/visionCone.js +72 -0
  235. package/dist/session/extraction.d.ts +2 -2
  236. package/dist/session/extraction.js +13 -2
  237. package/dist/session/roles.d.ts +6 -0
  238. package/dist/session/roles.js +32 -0
  239. package/dist/session/roundState.d.ts +41 -17
  240. package/dist/session/roundState.js +67 -38
  241. package/dist/settings/settingsModel.d.ts +97 -0
  242. package/dist/settings/settingsModel.js +100 -0
  243. package/dist/stats/statModifiers.d.ts +5 -2
  244. package/dist/stats/statModifiers.js +32 -14
  245. package/dist/store/observableKeyedStore.js +8 -2
  246. package/dist/survival/decayMeter.d.ts +1 -1
  247. package/dist/survival/decayMeter.js +0 -1
  248. package/dist/tactics/fallingGrid.d.ts +62 -0
  249. package/dist/tactics/fallingGrid.js +158 -0
  250. package/dist/tactics/tacticalGrid.d.ts +11 -0
  251. package/dist/tactics/tacticalGrid.js +17 -0
  252. package/dist/time/beatClock.d.ts +18 -0
  253. package/dist/time/beatClock.js +24 -0
  254. package/dist/time/idleProgress.d.ts +29 -0
  255. package/dist/time/idleProgress.js +31 -0
  256. package/dist/time/serverTick.d.ts +7 -1
  257. package/dist/time/serverTick.js +27 -4
  258. package/dist/time/simClock.d.ts +25 -1
  259. package/dist/time/simClock.js +28 -3
  260. package/dist/time/stateSchedule.d.ts +57 -0
  261. package/dist/time/stateSchedule.js +121 -0
  262. package/dist/turn/commit.d.ts +2 -2
  263. package/dist/turn/commit.js +2 -2
  264. package/dist/turn/intent.d.ts +14 -0
  265. package/dist/turn/intent.js +29 -0
  266. package/dist/turn/turnLoop.d.ts +2 -0
  267. package/dist/turn/turnLoop.js +8 -1
  268. package/dist/ui/hudLayout.d.ts +58 -0
  269. package/dist/ui/hudLayout.js +207 -0
  270. package/dist/ui/hudScale.d.ts +44 -0
  271. package/dist/ui/hudScale.js +55 -0
  272. package/dist/visibility/assetStreaming.d.ts +66 -0
  273. package/dist/visibility/assetStreaming.js +201 -0
  274. package/dist/visibility/bounds.d.ts +66 -0
  275. package/dist/visibility/bounds.js +113 -0
  276. package/dist/visibility/camera.d.ts +21 -0
  277. package/dist/visibility/camera.js +3 -0
  278. package/dist/visibility/config.d.ts +21 -0
  279. package/dist/visibility/config.js +1 -0
  280. package/dist/visibility/diagnostics.d.ts +26 -0
  281. package/dist/visibility/diagnostics.js +36 -0
  282. package/dist/visibility/distance.d.ts +11 -0
  283. package/dist/visibility/distance.js +30 -0
  284. package/dist/visibility/frustum.d.ts +47 -0
  285. package/dist/visibility/frustum.js +186 -0
  286. package/dist/visibility/occlusion.d.ts +36 -0
  287. package/dist/visibility/occlusion.js +45 -0
  288. package/dist/visibility/settings.d.ts +75 -0
  289. package/dist/visibility/settings.js +49 -0
  290. package/dist/visibility/simulationCulling.d.ts +34 -0
  291. package/dist/visibility/simulationCulling.js +36 -0
  292. package/dist/visibility/spatialIndex.d.ts +45 -0
  293. package/dist/visibility/spatialIndex.js +198 -0
  294. package/dist/visibility/visibilitySystem.d.ts +90 -0
  295. package/dist/visibility/visibilitySystem.js +257 -0
  296. package/dist/world/buildings.d.ts +6 -0
  297. package/dist/world/buildings.js +152 -0
  298. package/dist/world/carve.d.ts +7 -2
  299. package/dist/world/carve.js +17 -7
  300. package/dist/world/cellStates.d.ts +45 -0
  301. package/dist/world/cellStates.js +103 -0
  302. package/dist/world/environmentSummary.d.ts +61 -0
  303. package/dist/world/environmentSummary.js +138 -0
  304. package/dist/world/features.d.ts +138 -9
  305. package/dist/world/features.js +73 -2
  306. package/dist/world/gridInstances.d.ts +12 -0
  307. package/dist/world/gridInstances.js +160 -0
  308. package/dist/world/lod.d.ts +17 -0
  309. package/dist/world/lod.js +66 -0
  310. package/dist/world/mapLayers.d.ts +60 -0
  311. package/dist/world/mapLayers.js +44 -0
  312. package/dist/world/massing.d.ts +45 -0
  313. package/dist/world/massing.js +581 -0
  314. package/dist/world/minimap.d.ts +13 -1
  315. package/dist/world/minimap.js +21 -2
  316. package/dist/world/placementController.d.ts +22 -1
  317. package/dist/world/placementController.js +56 -0
  318. package/dist/world/terrain.d.ts +62 -1
  319. package/dist/world/terrain.js +253 -9
  320. package/dist/world/voxelField.d.ts +54 -0
  321. package/dist/world/voxelField.js +241 -0
  322. package/dist/world/water.d.ts +7 -0
  323. package/dist/world/water.js +19 -4
  324. package/dist/world/weather.d.ts +3 -18
  325. package/dist/world/weather.js +4 -17
  326. package/dist/world/windZones.d.ts +47 -0
  327. package/dist/world/windZones.js +59 -0
  328. package/llms.txt +4983 -0
  329. package/package.json +14 -3
@@ -0,0 +1,188 @@
1
+ import type { SpawnDirectorConfig } from "../ai/spawnDirector.js";
2
+ import type { Curve } from "../game/progression.js";
3
+ import type { ActionCodesMap } from "../input/actionBindings.js";
4
+ import type { GameContext } from "../runtime/gameContext.js";
5
+ export type Leveled = number | {
6
+ base: number;
7
+ perLevel: number;
8
+ min?: number;
9
+ max?: number;
10
+ } | {
11
+ table: readonly number[];
12
+ };
13
+ export declare function leveled(value: Leveled, level: number): number;
14
+ export interface CartridgePlayer {
15
+ kind: string;
16
+ health: number;
17
+ walkSpeed: number;
18
+ spawnAt?: readonly [number, number, number];
19
+ }
20
+ export interface CartridgeEnemy {
21
+ label: string;
22
+ health: number;
23
+ walkSpeed: number;
24
+ xp: number;
25
+ contact: {
26
+ damage: number;
27
+ intervalSeconds: number;
28
+ };
29
+ behavior?: "chase" | "none";
30
+ }
31
+ export interface CartridgeFlow {
32
+ start?: "auto" | "gate";
33
+ countdownSeconds?: number;
34
+ restart?: boolean;
35
+ }
36
+ export interface WeaponCommon {
37
+ label: string;
38
+ damage: Leveled;
39
+ cooldownMs: Leveled;
40
+ maxLevel: number;
41
+ fxColor?: string;
42
+ fxEmissive?: string;
43
+ }
44
+ export type CartridgeWeapon = (WeaponCommon & {
45
+ kind: "projectile";
46
+ range: number;
47
+ speed: number;
48
+ }) | (WeaponCommon & {
49
+ kind: "orbit";
50
+ blades: Leveled;
51
+ radius: Leveled;
52
+ hitRadius: number;
53
+ angularSpeed: number;
54
+ }) | (WeaponCommon & {
55
+ kind: "pulse";
56
+ radius: Leveled;
57
+ durationSeconds: number;
58
+ }) | (WeaponCommon & {
59
+ kind: "custom";
60
+ fire(ctx: GameContext, run: CartridgeRun, args: WeaponFireArgs): void;
61
+ });
62
+ export interface WeaponFireArgs {
63
+ weaponId: string;
64
+ level: number;
65
+ damage: number;
66
+ playerId: string;
67
+ playerPosition: readonly [number, number, number];
68
+ enemyIds: readonly string[];
69
+ dt: number;
70
+ }
71
+ export type UpgradeEffect = {
72
+ kind: "weaponLevel";
73
+ weapon: string;
74
+ } | {
75
+ kind: "statBonus";
76
+ stat: string;
77
+ amount: number;
78
+ } | {
79
+ kind: "fieldAdd";
80
+ field: string;
81
+ amount: number;
82
+ } | {
83
+ kind: "fieldMultiply";
84
+ field: string;
85
+ factor: number;
86
+ } | {
87
+ kind: "custom";
88
+ apply(ctx: GameContext, run: CartridgeRun, stacks: number): void;
89
+ };
90
+ export interface CartridgeUpgrade {
91
+ id: string;
92
+ label: string;
93
+ weight: number;
94
+ maxStacks: number;
95
+ effect: UpgradeEffect;
96
+ }
97
+ export interface CartridgeProgression {
98
+ xp: Curve;
99
+ maxLevel: number;
100
+ draft: {
101
+ choices: number;
102
+ upgrades: readonly CartridgeUpgrade[];
103
+ };
104
+ }
105
+ export interface CartridgeSpawning {
106
+ director: SpawnDirectorConfig;
107
+ placement: {
108
+ kind: "ring";
109
+ radius: number;
110
+ } | {
111
+ kind: "custom";
112
+ position(ctx: GameContext, run: CartridgeRun): readonly [number, number, number];
113
+ };
114
+ }
115
+ export interface CartridgeXpGems {
116
+ collectRadius: number;
117
+ pullSpeed: number;
118
+ rarityThresholds: readonly (readonly [number, string])[];
119
+ defaultRarity: string;
120
+ }
121
+ export interface CartridgeRules {
122
+ win?: {
123
+ kind: "survive";
124
+ seconds: number;
125
+ } | {
126
+ kind: "custom";
127
+ check(ctx: GameContext, run: CartridgeRun): boolean;
128
+ };
129
+ lose?: {
130
+ kind: "playerDeath";
131
+ } | {
132
+ kind: "custom";
133
+ check(ctx: GameContext, run: CartridgeRun): boolean;
134
+ };
135
+ killLeaderboardStat?: string;
136
+ }
137
+ export interface CartridgeSpec {
138
+ seed?: string | number;
139
+ flow?: CartridgeFlow;
140
+ player: CartridgePlayer;
141
+ enemies: Record<string, CartridgeEnemy>;
142
+ combat: {
143
+ contactRadius: number;
144
+ };
145
+ spawning: CartridgeSpawning;
146
+ weapons: Record<string, CartridgeWeapon>;
147
+ progression: CartridgeProgression;
148
+ xpGems: CartridgeXpGems;
149
+ rules: CartridgeRules;
150
+ fields?: Record<string, number>;
151
+ input?: ActionCodesMap;
152
+ systems?: readonly ((ctx: GameContext, run: CartridgeRun, dt: number) => void)[];
153
+ }
154
+ export interface BoltFx {
155
+ id: number;
156
+ weaponId: string;
157
+ origin: readonly [number, number, number];
158
+ target: readonly [number, number, number];
159
+ firedAt: number;
160
+ travelSeconds: number;
161
+ }
162
+ export interface PulseFx {
163
+ id: number;
164
+ weaponId: string;
165
+ at: readonly [number, number, number];
166
+ firedAt: number;
167
+ durationSeconds: number;
168
+ maxRadius: number;
169
+ }
170
+ export type CartridgePhase = "start" | "countdown" | "playing" | "won" | "lost";
171
+ export interface CartridgeRun {
172
+ phase: CartridgePhase;
173
+ playingSeconds: number;
174
+ countdownRemaining: number;
175
+ kills: number;
176
+ weaponLevel(weaponId: string): number;
177
+ field(name: string): number;
178
+ pendingOffers: readonly {
179
+ id: string;
180
+ label: string;
181
+ }[] | null;
182
+ bolts: readonly BoltFx[];
183
+ pulses: readonly PulseFx[];
184
+ subscribe(listener: () => void): () => void;
185
+ }
186
+ export declare const WASD_KEYBINDS: ActionCodesMap;
187
+ export declare const XP_GEM_BASE_TYPE = "xp_gem";
188
+ export declare const HIT_EFFECT = "hit";
@@ -0,0 +1,19 @@
1
+ export function leveled(value, level) {
2
+ if (typeof value === "number")
3
+ return value;
4
+ if ("table" in value) {
5
+ const index = Math.min(value.table.length - 1, Math.max(0, level - 1));
6
+ return value.table[index] ?? 0;
7
+ }
8
+ const raw = value.base + value.perLevel * (level - 1);
9
+ const floored = value.min === undefined ? raw : Math.max(value.min, raw);
10
+ return value.max === undefined ? floored : Math.min(value.max, floored);
11
+ }
12
+ export const WASD_KEYBINDS = {
13
+ moveForward: ["KeyW"],
14
+ moveBack: ["KeyS"],
15
+ moveLeft: ["KeyA"],
16
+ moveRight: ["KeyD"],
17
+ };
18
+ export const XP_GEM_BASE_TYPE = "xp_gem";
19
+ export const HIT_EFFECT = "hit";
@@ -0,0 +1,2 @@
1
+ import { type CartridgeSpec } from "./spec.js";
2
+ export declare function validateCartridge(spec: CartridgeSpec): string[];
@@ -0,0 +1,78 @@
1
+ import { leveled } from "./spec.js";
2
+ export function validateCartridge(spec) {
3
+ const problems = [];
4
+ const enemyIds = new Set(Object.keys(spec.enemies));
5
+ const weaponIds = new Set(Object.keys(spec.weapons));
6
+ if (spec.player.health <= 0)
7
+ problems.push(`player "${spec.player.kind}": health must be positive`);
8
+ if (spec.player.walkSpeed <= 0)
9
+ problems.push(`player "${spec.player.kind}": walkSpeed must be positive`);
10
+ if (enemyIds.has(spec.player.kind))
11
+ problems.push(`player kind "${spec.player.kind}" collides with an enemy id`);
12
+ if (spec.flow?.countdownSeconds !== undefined && spec.flow.countdownSeconds < 0) {
13
+ problems.push("flow.countdownSeconds must not be negative");
14
+ }
15
+ for (const [id, enemy] of Object.entries(spec.enemies)) {
16
+ if (enemy.health <= 0)
17
+ problems.push(`enemy "${id}": health must be positive`);
18
+ if (enemy.walkSpeed <= 0)
19
+ problems.push(`enemy "${id}": walkSpeed must be positive`);
20
+ if (enemy.xp <= 0)
21
+ problems.push(`enemy "${id}": xp must be positive`);
22
+ if (enemy.contact.damage <= 0)
23
+ problems.push(`enemy "${id}": contact.damage must be positive`);
24
+ if (enemy.contact.intervalSeconds <= 0)
25
+ problems.push(`enemy "${id}": contact.intervalSeconds must be positive`);
26
+ }
27
+ for (const wave of spec.spawning.director.waves) {
28
+ for (const entry of wave.entries) {
29
+ if (!enemyIds.has(entry.id))
30
+ problems.push(`spawning wave references unknown enemy "${entry.id}"`);
31
+ }
32
+ }
33
+ for (const [id, weapon] of Object.entries(spec.weapons)) {
34
+ for (let level = 1; level <= weapon.maxLevel; level += 1) {
35
+ if (leveled(weapon.damage, level) <= 0)
36
+ problems.push(`weapon "${id}": damage at level ${level} must be positive`);
37
+ if (leveled(weapon.cooldownMs, level) <= 0)
38
+ problems.push(`weapon "${id}": cooldownMs at level ${level} must be positive`);
39
+ }
40
+ }
41
+ const seenUpgrades = new Set();
42
+ for (const upgrade of spec.progression.draft.upgrades) {
43
+ if (seenUpgrades.has(upgrade.id))
44
+ problems.push(`upgrade "${upgrade.id}": duplicate id`);
45
+ seenUpgrades.add(upgrade.id);
46
+ if (upgrade.maxStacks <= 0)
47
+ problems.push(`upgrade "${upgrade.id}": maxStacks must be positive`);
48
+ const effect = upgrade.effect;
49
+ if (effect.kind === "weaponLevel") {
50
+ if (!weaponIds.has(effect.weapon)) {
51
+ problems.push(`upgrade "${upgrade.id}": references unknown weapon "${effect.weapon}"`);
52
+ }
53
+ else {
54
+ const weapon = spec.weapons[effect.weapon];
55
+ if (1 + upgrade.maxStacks > weapon.maxLevel) {
56
+ problems.push(`upgrade "${upgrade.id}": maxStacks ${upgrade.maxStacks} exceeds weapon "${effect.weapon}" maxLevel ${weapon.maxLevel}`);
57
+ }
58
+ }
59
+ }
60
+ if ((effect.kind === "fieldAdd" || effect.kind === "fieldMultiply") && spec.fields?.[effect.field] === undefined) {
61
+ problems.push(`upgrade "${upgrade.id}": references undeclared field "${effect.field}"`);
62
+ }
63
+ }
64
+ if (spec.progression.draft.choices <= 0)
65
+ problems.push("progression.draft.choices must be positive");
66
+ if (spec.progression.maxLevel <= 1)
67
+ problems.push("progression.maxLevel must exceed 1");
68
+ const thresholds = spec.xpGems.rarityThresholds;
69
+ for (let i = 1; i < thresholds.length; i += 1) {
70
+ if (thresholds[i][0] >= thresholds[i - 1][0]) {
71
+ problems.push("xpGems.rarityThresholds must be sorted by descending value");
72
+ }
73
+ }
74
+ if (spec.rules.win?.kind === "survive" && spec.rules.win.seconds <= 0) {
75
+ problems.push("rules.win.seconds must be positive");
76
+ }
77
+ return problems;
78
+ }
@@ -7,6 +7,16 @@ export interface AbilitySlotConfig {
7
7
  resourceCost?: number;
8
8
  castType?: AbilityCastType;
9
9
  flashMs?: number;
10
+ /** Shared cooldown groups this slot participates in — ids declared in `AbilityKitOptions.groups`. */
11
+ groups?: readonly string[];
12
+ }
13
+ /** A cooldown shared across every member slot: casting any member starts it, and no member can cast while it runs. One group that every slot joins is an MMO global cooldown. */
14
+ export interface AbilityCooldownGroup {
15
+ id: string;
16
+ cooldownMs: number;
17
+ }
18
+ export interface AbilityKitOptions {
19
+ groups?: readonly AbilityCooldownGroup[];
10
20
  }
11
21
  export interface AbilitySlotSnapshot {
12
22
  id: string;
@@ -15,12 +25,19 @@ export interface AbilitySlotSnapshot {
15
25
  resourceCost: number;
16
26
  charges: number;
17
27
  chargesMax: number;
28
+ /** Max of the slot's own recharge and any blocking group cooldown. */
18
29
  cooldownRemainingMs: number;
19
30
  cooldownFraction: number;
31
+ /** Remaining ms on the slowest of the slot's shared cooldown groups; 0 when unblocked. */
32
+ groupRemainingMs: number;
20
33
  justCast: boolean;
21
34
  ready: boolean;
22
35
  }
23
- export type AbilityCastReason = "unknown-slot" | "cooldown" | "no-resource";
36
+ export type AbilityCastReason = "unknown-slot" | "cooldown" | "group-cooldown" | "no-resource";
37
+ export interface AbilitySlotRetune {
38
+ cooldownMs?: number;
39
+ resourceCost?: number;
40
+ }
24
41
  export type AbilityCastResult = {
25
42
  ok: true;
26
43
  slot: AbilitySlotSnapshot;
@@ -37,6 +54,9 @@ export interface AbilityKit {
37
54
  canCast(slotId: string, resourceAvailable?: number): AbilityCastResult;
38
55
  cast(slotId: string, resourceAvailable?: number): AbilityCastResult;
39
56
  tick(dtSeconds: number): void;
57
+ /** Remaining ms on a shared cooldown group; 0 when ready, null for an unknown group. */
58
+ groupRemaining(groupId: string): number | null;
40
59
  reset(slotId?: string): void;
60
+ retuneSlot(slotId: string, patch: AbilitySlotRetune): boolean;
41
61
  }
42
- export declare function createAbilityKit(configs: readonly AbilitySlotConfig[]): AbilityKit;
62
+ export declare function createAbilityKit(configs: readonly AbilitySlotConfig[], options?: AbilityKitOptions): AbilityKit;
@@ -7,21 +7,24 @@ function normalizeConfig(config) {
7
7
  resourceCost: Math.max(0, config.resourceCost ?? 0),
8
8
  castType: config.castType ?? "instant",
9
9
  flashMs: Math.max(0, config.flashMs ?? DEFAULT_FLASH_MS),
10
+ groups: config.groups ?? [],
10
11
  };
11
12
  }
12
- function deriveState(runtime, resourceAvailable) {
13
+ function deriveState(runtime, resourceAvailable, group) {
13
14
  if (runtime.flashRemainingMs > 0)
14
15
  return "just-cast";
15
16
  if (runtime.charges <= 0)
16
17
  return "cooldown";
18
+ if (group.remainingMs > 0)
19
+ return "cooldown";
17
20
  if (resourceAvailable < runtime.config.resourceCost)
18
21
  return "no-resource";
19
22
  return "ready";
20
23
  }
21
- function snapshotOf(runtime, resourceAvailable) {
24
+ function snapshotOf(runtime, resourceAvailable, group) {
22
25
  const { config } = runtime;
23
- const state = deriveState(runtime, resourceAvailable);
24
- const cooldownFraction = config.cooldownMs <= 0 ? 0 : Math.max(0, Math.min(1, runtime.rechargeRemainingMs / config.cooldownMs));
26
+ const state = deriveState(runtime, resourceAvailable, group);
27
+ const ownFraction = config.cooldownMs <= 0 ? 0 : Math.max(0, Math.min(1, runtime.rechargeRemainingMs / config.cooldownMs));
25
28
  return {
26
29
  id: config.id,
27
30
  state,
@@ -29,19 +32,33 @@ function snapshotOf(runtime, resourceAvailable) {
29
32
  resourceCost: config.resourceCost,
30
33
  charges: runtime.charges,
31
34
  chargesMax: config.chargesMax,
32
- cooldownRemainingMs: runtime.rechargeRemainingMs,
33
- cooldownFraction,
35
+ cooldownRemainingMs: Math.max(runtime.rechargeRemainingMs, group.remainingMs),
36
+ cooldownFraction: Math.max(ownFraction, group.fraction),
37
+ groupRemainingMs: group.remainingMs,
34
38
  justCast: runtime.flashRemainingMs > 0,
35
- ready: runtime.charges > 0 && resourceAvailable >= config.resourceCost,
39
+ ready: runtime.charges > 0 && group.remainingMs <= 0 && resourceAvailable >= config.resourceCost,
36
40
  };
37
41
  }
38
- export function createAbilityKit(configs) {
42
+ export function createAbilityKit(configs, options = {}) {
43
+ const groupCooldowns = new Map();
44
+ const groupRemaining = new Map();
45
+ for (const group of options.groups ?? []) {
46
+ if (groupCooldowns.has(group.id))
47
+ throw new Error(`duplicate cooldown group: ${group.id}`);
48
+ groupCooldowns.set(group.id, Math.max(0, group.cooldownMs));
49
+ groupRemaining.set(group.id, 0);
50
+ }
39
51
  const runtimes = new Map();
40
52
  const order = [];
41
53
  for (const config of configs) {
42
54
  const normalized = normalizeConfig(config);
43
55
  if (runtimes.has(normalized.id))
44
56
  throw new Error(`duplicate ability slot: ${normalized.id}`);
57
+ for (const groupId of normalized.groups) {
58
+ if (!groupCooldowns.has(groupId)) {
59
+ throw new Error(`ability slot "${normalized.id}" references unknown cooldown group "${groupId}"`);
60
+ }
61
+ }
45
62
  runtimes.set(normalized.id, {
46
63
  config: normalized,
47
64
  charges: normalized.chargesMax,
@@ -50,9 +67,22 @@ export function createAbilityKit(configs) {
50
67
  });
51
68
  order.push(normalized.id);
52
69
  }
70
+ function groupBlockOf(runtime) {
71
+ let remainingMs = 0;
72
+ let fraction = 0;
73
+ for (const groupId of runtime.config.groups) {
74
+ const remaining = groupRemaining.get(groupId) ?? 0;
75
+ if (remaining <= remainingMs)
76
+ continue;
77
+ remainingMs = remaining;
78
+ const cooldownMs = groupCooldowns.get(groupId) ?? 0;
79
+ fraction = cooldownMs <= 0 ? 0 : Math.max(0, Math.min(1, remaining / cooldownMs));
80
+ }
81
+ return { remainingMs, fraction };
82
+ }
53
83
  function fail(slotId, reason, resourceAvailable) {
54
84
  const runtime = runtimes.get(slotId);
55
- return { ok: false, reason, slot: runtime ? snapshotOf(runtime, resourceAvailable) : null };
85
+ return { ok: false, reason, slot: runtime ? snapshotOf(runtime, resourceAvailable, groupBlockOf(runtime)) : null };
56
86
  }
57
87
  function evaluate(slotId, resourceAvailable) {
58
88
  const runtime = runtimes.get(slotId);
@@ -60,9 +90,11 @@ export function createAbilityKit(configs) {
60
90
  return fail(slotId, "unknown-slot", resourceAvailable);
61
91
  if (runtime.charges <= 0)
62
92
  return fail(slotId, "cooldown", resourceAvailable);
93
+ if (groupBlockOf(runtime).remainingMs > 0)
94
+ return fail(slotId, "group-cooldown", resourceAvailable);
63
95
  if (resourceAvailable < runtime.config.resourceCost)
64
96
  return fail(slotId, "no-resource", resourceAvailable);
65
- return { ok: true, slot: snapshotOf(runtime, resourceAvailable) };
97
+ return { ok: true, slot: snapshotOf(runtime, resourceAvailable, groupBlockOf(runtime)) };
66
98
  }
67
99
  return {
68
100
  slots() {
@@ -73,10 +105,13 @@ export function createAbilityKit(configs) {
73
105
  },
74
106
  state(slotId, resourceAvailable = Number.POSITIVE_INFINITY) {
75
107
  const runtime = runtimes.get(slotId);
76
- return runtime === undefined ? null : snapshotOf(runtime, resourceAvailable);
108
+ return runtime === undefined ? null : snapshotOf(runtime, resourceAvailable, groupBlockOf(runtime));
77
109
  },
78
110
  snapshot(resourceAvailable = Number.POSITIVE_INFINITY) {
79
- return order.map((slotId) => snapshotOf(runtimes.get(slotId), resourceAvailable));
111
+ return order.map((slotId) => {
112
+ const runtime = runtimes.get(slotId);
113
+ return snapshotOf(runtime, resourceAvailable, groupBlockOf(runtime));
114
+ });
80
115
  },
81
116
  canCast(slotId, resourceAvailable = Number.POSITIVE_INFINITY) {
82
117
  return evaluate(slotId, resourceAvailable);
@@ -90,12 +125,19 @@ export function createAbilityKit(configs) {
90
125
  if (runtime.rechargeRemainingMs <= 0)
91
126
  runtime.rechargeRemainingMs = runtime.config.cooldownMs;
92
127
  runtime.flashRemainingMs = runtime.config.flashMs;
93
- return { ok: true, slot: snapshotOf(runtime, resourceAvailable) };
128
+ for (const groupId of runtime.config.groups) {
129
+ groupRemaining.set(groupId, groupCooldowns.get(groupId) ?? 0);
130
+ }
131
+ return { ok: true, slot: snapshotOf(runtime, resourceAvailable, groupBlockOf(runtime)) };
94
132
  },
95
133
  tick(dtSeconds) {
96
134
  if (dtSeconds <= 0)
97
135
  return;
98
136
  const dtMs = dtSeconds * 1000;
137
+ for (const [groupId, remaining] of groupRemaining) {
138
+ if (remaining > 0)
139
+ groupRemaining.set(groupId, Math.max(0, remaining - dtMs));
140
+ }
99
141
  for (const runtime of runtimes.values()) {
100
142
  if (runtime.flashRemainingMs > 0)
101
143
  runtime.flashRemainingMs = Math.max(0, runtime.flashRemainingMs - dtMs);
@@ -117,6 +159,9 @@ export function createAbilityKit(configs) {
117
159
  runtime.rechargeRemainingMs = runtime.charges >= runtime.config.chargesMax ? 0 : remaining;
118
160
  }
119
161
  },
162
+ groupRemaining(groupId) {
163
+ return groupCooldowns.has(groupId) ? groupRemaining.get(groupId) ?? 0 : null;
164
+ },
120
165
  reset(slotId) {
121
166
  const targets = slotId === undefined ? runtimes.values() : [runtimes.get(slotId)].filter((r) => r !== undefined);
122
167
  for (const runtime of targets) {
@@ -124,6 +169,20 @@ export function createAbilityKit(configs) {
124
169
  runtime.rechargeRemainingMs = 0;
125
170
  runtime.flashRemainingMs = 0;
126
171
  }
172
+ if (slotId === undefined) {
173
+ for (const groupId of groupRemaining.keys())
174
+ groupRemaining.set(groupId, 0);
175
+ }
176
+ },
177
+ retuneSlot(slotId, patch) {
178
+ const runtime = runtimes.get(slotId);
179
+ if (runtime === undefined)
180
+ return false;
181
+ if (patch.cooldownMs !== undefined)
182
+ runtime.config.cooldownMs = Math.max(0, patch.cooldownMs);
183
+ if (patch.resourceCost !== undefined)
184
+ runtime.config.resourceCost = Math.max(0, patch.resourceCost);
185
+ return true;
127
186
  },
128
187
  };
129
188
  }
@@ -0,0 +1,40 @@
1
+ export interface CastConfig {
2
+ abilityId: string;
3
+ castTimeMs: number;
4
+ /** Total movement distance tolerated before the cast auto-interrupts; default `0.04`. `Infinity` allows casting on the move. */
5
+ moveTolerance?: number;
6
+ }
7
+ export interface CastBarSnapshot {
8
+ abilityId: string;
9
+ castTimeMs: number;
10
+ elapsedMs: number;
11
+ remainingMs: number;
12
+ /** Fill fraction in `[0, 1]` for the cast bar. */
13
+ fraction: number;
14
+ }
15
+ export type CastInterruptReason = "moved" | "cancelled" | "replaced";
16
+ export type CastEvent = {
17
+ kind: "completed";
18
+ abilityId: string;
19
+ } | {
20
+ kind: "interrupted";
21
+ abilityId: string;
22
+ reason: CastInterruptReason;
23
+ };
24
+ /**
25
+ * Per-entity cast-time state machine — begin, tick with game-time `dt` plus how far the caster
26
+ * moved, and act on the returned event. The runner owns timing and move-interruption only; the
27
+ * caller spends resources, starts cooldowns, and executes the ability when `completed` fires
28
+ * (compose with `abilityKit` — check readiness before `begin`, `cast` on completion).
29
+ */
30
+ export interface CastRunner {
31
+ /** Start a cast; `false` while one is already in flight (interrupt or let it finish first). */
32
+ begin(config: CastConfig): boolean;
33
+ casting(): boolean;
34
+ state(): CastBarSnapshot | null;
35
+ /** Interrupt the in-flight cast now; returns the interruption event, or `null` when idle. */
36
+ interrupt(reason?: CastInterruptReason): CastEvent | null;
37
+ /** Advance by `dtSeconds`; `movedDistance` accumulates toward the config's `moveTolerance`. */
38
+ tick(dtSeconds: number, movedDistance?: number): CastEvent | null;
39
+ }
40
+ export declare function createCastRunner(): CastRunner;
@@ -0,0 +1,56 @@
1
+ const DEFAULT_MOVE_TOLERANCE = 0.04;
2
+ export function createCastRunner() {
3
+ let active = null;
4
+ function snapshot() {
5
+ if (active === null)
6
+ return null;
7
+ const fraction = active.castTimeMs <= 0 ? 1 : Math.min(1, active.elapsedMs / active.castTimeMs);
8
+ return {
9
+ abilityId: active.abilityId,
10
+ castTimeMs: active.castTimeMs,
11
+ elapsedMs: active.elapsedMs,
12
+ remainingMs: Math.max(0, active.castTimeMs - active.elapsedMs),
13
+ fraction,
14
+ };
15
+ }
16
+ return {
17
+ begin(config) {
18
+ if (active !== null)
19
+ return false;
20
+ active = {
21
+ abilityId: config.abilityId,
22
+ castTimeMs: Math.max(0, config.castTimeMs),
23
+ moveTolerance: config.moveTolerance ?? DEFAULT_MOVE_TOLERANCE,
24
+ elapsedMs: 0,
25
+ movedTotal: 0,
26
+ };
27
+ return true;
28
+ },
29
+ casting: () => active !== null,
30
+ state: snapshot,
31
+ interrupt(reason = "cancelled") {
32
+ if (active === null)
33
+ return null;
34
+ const abilityId = active.abilityId;
35
+ active = null;
36
+ return { kind: "interrupted", abilityId, reason };
37
+ },
38
+ tick(dtSeconds, movedDistance = 0) {
39
+ if (active === null)
40
+ return null;
41
+ active.movedTotal += Math.max(0, movedDistance);
42
+ if (active.movedTotal > active.moveTolerance) {
43
+ const abilityId = active.abilityId;
44
+ active = null;
45
+ return { kind: "interrupted", abilityId, reason: "moved" };
46
+ }
47
+ active.elapsedMs += Math.max(0, dtSeconds) * 1000;
48
+ if (active.elapsedMs >= active.castTimeMs) {
49
+ const abilityId = active.abilityId;
50
+ active = null;
51
+ return { kind: "completed", abilityId };
52
+ }
53
+ return null;
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,14 @@
1
+ export interface ComboPointsConfig {
2
+ max: number;
3
+ expireAfterSeconds?: number;
4
+ }
5
+ export interface ComboPoints {
6
+ points(): number;
7
+ gain(amount?: number): number;
8
+ spend(amount: number): boolean;
9
+ spendAll(): number;
10
+ tick(dtSeconds: number): void;
11
+ expiresIn(): number | null;
12
+ clear(): void;
13
+ }
14
+ export declare function createComboPoints(config: ComboPointsConfig): ComboPoints;
@@ -0,0 +1,52 @@
1
+ export function createComboPoints(config) {
2
+ const max = config.max;
3
+ const expireAfterSeconds = config.expireAfterSeconds;
4
+ let current = 0;
5
+ let remaining = null;
6
+ return {
7
+ points() {
8
+ return current;
9
+ },
10
+ gain(amount = 1) {
11
+ if (amount <= 0)
12
+ return current;
13
+ current = Math.min(max, current + amount);
14
+ remaining = expireAfterSeconds !== undefined ? expireAfterSeconds : null;
15
+ return current;
16
+ },
17
+ spend(amount) {
18
+ if (amount <= 0)
19
+ return true;
20
+ if (amount > current)
21
+ return false;
22
+ current -= amount;
23
+ if (current <= 0)
24
+ remaining = null;
25
+ return true;
26
+ },
27
+ spendAll() {
28
+ const spent = current;
29
+ current = 0;
30
+ remaining = null;
31
+ return spent;
32
+ },
33
+ tick(dtSeconds) {
34
+ if (dtSeconds <= 0 || remaining === null)
35
+ return;
36
+ remaining -= dtSeconds;
37
+ if (remaining <= 0) {
38
+ current = 0;
39
+ remaining = null;
40
+ }
41
+ },
42
+ expiresIn() {
43
+ if (current <= 0 || remaining === null)
44
+ return null;
45
+ return remaining;
46
+ },
47
+ clear() {
48
+ current = 0;
49
+ remaining = null;
50
+ },
51
+ };
52
+ }
@@ -27,7 +27,7 @@ export declare function resolveDefense(input: ResolveDefenseInput): DefenseResol
27
27
  export interface DefensiveWindow {
28
28
  open(nowMs: number): void;
29
29
  close(): void;
30
- isOpen(): boolean;
30
+ isOpen(nowMs: number): boolean;
31
31
  evaluate(nowMs: number, attack: AttackMeta): DefenseResolution;
32
32
  isInvulnerable(nowMs: number): boolean;
33
33
  }