@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,52 @@
1
+ import type { Tile } from "./tacticalGrid.js";
2
+ export interface SurfaceKindDef {
3
+ id: string;
4
+ duration?: number;
5
+ }
6
+ export interface SurfaceReaction {
7
+ when: readonly [string, string];
8
+ result: string | null;
9
+ }
10
+ export interface SurfaceLayerConfig {
11
+ kinds: readonly SurfaceKindDef[];
12
+ reactions?: readonly SurfaceReaction[];
13
+ }
14
+ export interface SurfacePatch {
15
+ duration?: number;
16
+ }
17
+ export type SurfaceEvent = {
18
+ kind: "apply";
19
+ tile: Tile;
20
+ surface: string;
21
+ } | {
22
+ kind: "react";
23
+ tile: Tile;
24
+ consumed: readonly [string, string];
25
+ produced: string | null;
26
+ } | {
27
+ kind: "expire";
28
+ tile: Tile;
29
+ surface: string;
30
+ };
31
+ export interface SurfaceCellKind {
32
+ surface: string;
33
+ remaining: number | null;
34
+ }
35
+ export interface SurfaceCell {
36
+ tile: Tile;
37
+ kinds: SurfaceCellKind[];
38
+ }
39
+ export type SurfaceLayerSnapshot = Record<string, Record<string, number | null>>;
40
+ export interface SurfaceLayer {
41
+ apply(tile: Tile, surface: string, patch?: SurfacePatch): SurfaceEvent[];
42
+ has(tile: Tile, surface: string): boolean;
43
+ kindsAt(tile: Tile): string[];
44
+ remove(tile: Tile, surface: string): void;
45
+ clearTile(tile: Tile): void;
46
+ clear(): void;
47
+ tick(dt: number): SurfaceEvent[];
48
+ cells(): SurfaceCell[];
49
+ capture(): SurfaceLayerSnapshot;
50
+ restore(snapshot: SurfaceLayerSnapshot): void;
51
+ }
52
+ export declare function createSurfaceLayer(config: SurfaceLayerConfig): SurfaceLayer;
@@ -0,0 +1,135 @@
1
+ function key(tile) {
2
+ return `${tile[0]},${tile[1]}`;
3
+ }
4
+ function parseKey(k) {
5
+ const [c, r] = k.split(",");
6
+ return [Number(c), Number(r)];
7
+ }
8
+ function pairKey(a, b) {
9
+ return `${a}|${b}`;
10
+ }
11
+ export function createSurfaceLayer(config) {
12
+ const durations = new Map();
13
+ for (const k of config.kinds)
14
+ durations.set(k.id, k.duration);
15
+ const reactions = new Map();
16
+ for (const reaction of config.reactions ?? []) {
17
+ const [a, b] = reaction.when;
18
+ reactions.set(pairKey(a, b), reaction.result);
19
+ reactions.set(pairKey(b, a), reaction.result);
20
+ }
21
+ const cells = new Map();
22
+ function cellOf(tile) {
23
+ const k = key(tile);
24
+ let cell = cells.get(k);
25
+ if (cell === undefined) {
26
+ cell = new Map();
27
+ cells.set(k, cell);
28
+ }
29
+ return cell;
30
+ }
31
+ function initialRemaining(surface, patch) {
32
+ if (patch?.duration !== undefined)
33
+ return patch.duration;
34
+ const configured = durations.get(surface);
35
+ return configured === undefined ? null : configured;
36
+ }
37
+ function resolveReactions(tile, events) {
38
+ const cell = cellOf(tile);
39
+ for (;;) {
40
+ const active = [...cell.keys()];
41
+ let matched = null;
42
+ outer: for (let i = 0; i < active.length; i += 1) {
43
+ for (let j = i + 1; j < active.length; j += 1) {
44
+ const result = reactions.get(pairKey(active[i], active[j]));
45
+ if (result !== undefined) {
46
+ matched = { a: active[i], b: active[j], result };
47
+ break outer;
48
+ }
49
+ }
50
+ }
51
+ if (matched === null)
52
+ return;
53
+ cell.delete(matched.a);
54
+ cell.delete(matched.b);
55
+ if (matched.result !== null)
56
+ cell.set(matched.result, initialRemaining(matched.result));
57
+ events.push({ kind: "react", tile, consumed: [matched.a, matched.b], produced: matched.result });
58
+ }
59
+ }
60
+ return {
61
+ apply: (tile, surface, patch) => {
62
+ const events = [];
63
+ const cell = cellOf(tile);
64
+ cell.set(surface, initialRemaining(surface, patch));
65
+ events.push({ kind: "apply", tile, surface });
66
+ resolveReactions(tile, events);
67
+ if (cells.get(key(tile))?.size === 0)
68
+ cells.delete(key(tile));
69
+ return events;
70
+ },
71
+ has: (tile, surface) => cells.get(key(tile))?.has(surface) ?? false,
72
+ kindsAt: (tile) => [...(cells.get(key(tile))?.keys() ?? [])],
73
+ remove: (tile, surface) => {
74
+ const cell = cells.get(key(tile));
75
+ if (cell === undefined)
76
+ return;
77
+ cell.delete(surface);
78
+ if (cell.size === 0)
79
+ cells.delete(key(tile));
80
+ },
81
+ clearTile: (tile) => {
82
+ cells.delete(key(tile));
83
+ },
84
+ clear: () => cells.clear(),
85
+ tick: (dt) => {
86
+ const events = [];
87
+ for (const [k, cell] of [...cells]) {
88
+ for (const [surface, remaining] of [...cell]) {
89
+ if (remaining === null)
90
+ continue;
91
+ const next = remaining - dt;
92
+ if (next <= 0) {
93
+ cell.delete(surface);
94
+ events.push({ kind: "expire", tile: parseKey(k), surface });
95
+ }
96
+ else {
97
+ cell.set(surface, next);
98
+ }
99
+ }
100
+ if (cell.size === 0)
101
+ cells.delete(k);
102
+ }
103
+ return events;
104
+ },
105
+ cells: () => {
106
+ const out = [];
107
+ for (const [k, cell] of cells) {
108
+ out.push({
109
+ tile: parseKey(k),
110
+ kinds: [...cell].map(([surface, remaining]) => ({ surface, remaining })),
111
+ });
112
+ }
113
+ return out;
114
+ },
115
+ capture: () => {
116
+ const snapshot = {};
117
+ for (const [k, cell] of cells) {
118
+ const entry = {};
119
+ for (const [surface, remaining] of cell)
120
+ entry[surface] = remaining;
121
+ snapshot[k] = entry;
122
+ }
123
+ return snapshot;
124
+ },
125
+ restore: (snapshot) => {
126
+ cells.clear();
127
+ for (const [k, entry] of Object.entries(snapshot)) {
128
+ const cell = new Map();
129
+ for (const [surface, remaining] of Object.entries(entry))
130
+ cell.set(surface, remaining);
131
+ cells.set(k, cell);
132
+ }
133
+ },
134
+ };
135
+ }
@@ -0,0 +1,68 @@
1
+ export type Tile = readonly [number, number];
2
+ /** Maps abstract `[col, row]` tiles onto world-space `[x, z]`, mirroring `navGrid`'s bounds+cellSize convention. */
3
+ export interface TacticalGridWorldConfig {
4
+ /** World-space `[x, z]` of the min corner of tile `[0, 0]`. */
5
+ origin: readonly [number, number];
6
+ tileSize: number;
7
+ }
8
+ export interface TacticalGridConfig {
9
+ width: number;
10
+ height: number;
11
+ blocked?: readonly Tile[];
12
+ diagonal?: boolean;
13
+ world?: TacticalGridWorldConfig;
14
+ }
15
+ export interface ReachableOptions {
16
+ passThrough?: readonly string[];
17
+ diagonal?: boolean;
18
+ }
19
+ export interface ReachableTile {
20
+ tile: Tile;
21
+ cost: number;
22
+ }
23
+ export type PushObstacle = "wall" | "edge";
24
+ export interface PushCollision {
25
+ mover: string;
26
+ into: string | PushObstacle;
27
+ at: Tile;
28
+ }
29
+ export interface PushMove {
30
+ id: string;
31
+ from: Tile;
32
+ to: Tile;
33
+ }
34
+ export interface PushResult {
35
+ moves: PushMove[];
36
+ collisions: PushCollision[];
37
+ }
38
+ export interface PushOptions {
39
+ distance?: number;
40
+ chain?: boolean;
41
+ }
42
+ export interface TacticalGridSnapshot {
43
+ blocked: string[];
44
+ occupants: Record<string, string>;
45
+ }
46
+ export interface TacticalGrid {
47
+ readonly width: number;
48
+ readonly height: number;
49
+ inBounds(tile: Tile): boolean;
50
+ isBlocked(tile: Tile): boolean;
51
+ setBlocked(tile: Tile, blocked: boolean): void;
52
+ occupantAt(tile: Tile): string | null;
53
+ tileOf(id: string): Tile | null;
54
+ place(id: string, tile: Tile): boolean;
55
+ remove(id: string): void;
56
+ move(id: string, tile: Tile): boolean;
57
+ neighbors(tile: Tile, diagonal?: boolean): Tile[];
58
+ reachable(from: Tile, budget: number, options?: ReachableOptions): ReachableTile[];
59
+ path(from: Tile, to: Tile, options?: ReachableOptions): Tile[] | null;
60
+ push(id: string, direction: Tile, options?: PushOptions): PushResult;
61
+ capture(): TacticalGridSnapshot;
62
+ restore(snapshot: TacticalGridSnapshot): void;
63
+ /** World `[x, z]` to the containing tile, or `null` when outside the grid. Requires `config.world`. */
64
+ worldToTile(x: number, z: number): Tile | null;
65
+ /** A tile's world-space center. Requires `config.world`. */
66
+ tileToWorld(tile: Tile): readonly [number, number];
67
+ }
68
+ export declare function createTacticalGrid(config: TacticalGridConfig): TacticalGrid;
@@ -0,0 +1,261 @@
1
+ function key(tile) {
2
+ return `${tile[0]},${tile[1]}`;
3
+ }
4
+ function parseKey(k) {
5
+ const [c, r] = k.split(",");
6
+ return [Number(c), Number(r)];
7
+ }
8
+ export function createTacticalGrid(config) {
9
+ const width = config.width;
10
+ const height = config.height;
11
+ const defaultDiagonal = config.diagonal ?? false;
12
+ const blocked = new Set((config.blocked ?? []).map(key));
13
+ const occupants = new Map();
14
+ const tiles = new Map();
15
+ const world = config.world;
16
+ function inBounds(tile) {
17
+ return tile[0] >= 0 && tile[0] < width && tile[1] >= 0 && tile[1] < height;
18
+ }
19
+ function requireWorld() {
20
+ if (world === undefined)
21
+ throw new Error("tacticalGrid: worldToTile/tileToWorld require config.world.");
22
+ return world;
23
+ }
24
+ function worldToTile(x, z) {
25
+ const { origin, tileSize } = requireWorld();
26
+ const tile = [Math.floor((x - origin[0]) / tileSize), Math.floor((z - origin[1]) / tileSize)];
27
+ return inBounds(tile) ? tile : null;
28
+ }
29
+ function tileToWorld(tile) {
30
+ const { origin, tileSize } = requireWorld();
31
+ return [origin[0] + (tile[0] + 0.5) * tileSize, origin[1] + (tile[1] + 0.5) * tileSize];
32
+ }
33
+ function isBlocked(tile) {
34
+ return blocked.has(key(tile));
35
+ }
36
+ function occupantAt(tile) {
37
+ return occupants.get(key(tile)) ?? null;
38
+ }
39
+ function tileOf(id) {
40
+ const t = tiles.get(id);
41
+ return t === undefined ? null : [t[0], t[1]];
42
+ }
43
+ function place(id, tile) {
44
+ if (!inBounds(tile) || isBlocked(tile))
45
+ return false;
46
+ if (occupants.has(key(tile)))
47
+ return false;
48
+ const prev = tiles.get(id);
49
+ if (prev !== undefined)
50
+ occupants.delete(key(prev));
51
+ occupants.set(key(tile), id);
52
+ tiles.set(id, [tile[0], tile[1]]);
53
+ return true;
54
+ }
55
+ function remove(id) {
56
+ const t = tiles.get(id);
57
+ if (t === undefined)
58
+ return;
59
+ occupants.delete(key(t));
60
+ tiles.delete(id);
61
+ }
62
+ function move(id, tile) {
63
+ if (!tiles.has(id))
64
+ return false;
65
+ if (!inBounds(tile) || isBlocked(tile))
66
+ return false;
67
+ const holder = occupants.get(key(tile));
68
+ if (holder !== undefined && holder !== id)
69
+ return false;
70
+ const prev = tiles.get(id);
71
+ occupants.delete(key(prev));
72
+ occupants.set(key(tile), id);
73
+ tiles.set(id, [tile[0], tile[1]]);
74
+ return true;
75
+ }
76
+ function neighbors(tile, diagonal = defaultDiagonal) {
77
+ const orthogonal = [
78
+ [tile[0] + 1, tile[1]],
79
+ [tile[0] - 1, tile[1]],
80
+ [tile[0], tile[1] + 1],
81
+ [tile[0], tile[1] - 1],
82
+ ];
83
+ const diagonals = [
84
+ [tile[0] + 1, tile[1] + 1],
85
+ [tile[0] + 1, tile[1] - 1],
86
+ [tile[0] - 1, tile[1] + 1],
87
+ [tile[0] - 1, tile[1] - 1],
88
+ ];
89
+ return (diagonal ? [...orthogonal, ...diagonals] : orthogonal).filter(inBounds);
90
+ }
91
+ function enterable(tile, passThrough) {
92
+ if (!inBounds(tile) || isBlocked(tile))
93
+ return false;
94
+ const holder = occupants.get(key(tile));
95
+ return holder === undefined || passThrough.has(holder);
96
+ }
97
+ function reachable(from, budget, options) {
98
+ const diagonal = options?.diagonal ?? defaultDiagonal;
99
+ const passThrough = new Set(options?.passThrough ?? []);
100
+ const mover = occupantAt(from);
101
+ if (mover !== null)
102
+ passThrough.add(mover);
103
+ const best = new Map([[key(from), 0]]);
104
+ const frontier = [from];
105
+ while (frontier.length > 0) {
106
+ const current = frontier.shift();
107
+ const cost = best.get(key(current));
108
+ if (cost >= budget)
109
+ continue;
110
+ for (const next of neighbors(current, diagonal)) {
111
+ if (!enterable(next, passThrough))
112
+ continue;
113
+ const nextCost = cost + 1;
114
+ const known = best.get(key(next));
115
+ if (known !== undefined && known <= nextCost)
116
+ continue;
117
+ best.set(key(next), nextCost);
118
+ frontier.push(next);
119
+ }
120
+ }
121
+ const result = [];
122
+ for (const [k, cost] of best) {
123
+ if (k === key(from))
124
+ continue;
125
+ result.push({ tile: parseKey(k), cost });
126
+ }
127
+ return result.sort((a, b) => a.cost - b.cost || a.tile[0] - b.tile[0] || a.tile[1] - b.tile[1]);
128
+ }
129
+ function path(from, to, options) {
130
+ if (!inBounds(from) || !inBounds(to))
131
+ return null;
132
+ const diagonal = options?.diagonal ?? defaultDiagonal;
133
+ const passThrough = new Set(options?.passThrough ?? []);
134
+ const mover = occupantAt(from);
135
+ if (mover !== null)
136
+ passThrough.add(mover);
137
+ if (key(from) === key(to))
138
+ return [[from[0], from[1]]];
139
+ const cameFrom = new Map();
140
+ const visited = new Set([key(from)]);
141
+ const frontier = [from];
142
+ while (frontier.length > 0) {
143
+ const current = frontier.shift();
144
+ for (const next of neighbors(current, diagonal)) {
145
+ if (visited.has(key(next)))
146
+ continue;
147
+ const isGoal = key(next) === key(to);
148
+ if (!isGoal && !enterable(next, passThrough))
149
+ continue;
150
+ if (!isGoal && isBlocked(next))
151
+ continue;
152
+ visited.add(key(next));
153
+ cameFrom.set(key(next), key(current));
154
+ if (isGoal) {
155
+ const chain = [to];
156
+ let step = key(to);
157
+ while (step !== key(from)) {
158
+ step = cameFrom.get(step);
159
+ chain.push(parseKey(step));
160
+ }
161
+ return chain.reverse();
162
+ }
163
+ frontier.push(next);
164
+ }
165
+ }
166
+ return null;
167
+ }
168
+ function push(id, direction, options) {
169
+ const distance = options?.distance ?? 1;
170
+ const chain = options?.chain ?? false;
171
+ const result = { moves: [], collisions: [] };
172
+ const origins = new Map();
173
+ function record(unitId) {
174
+ const from = origins.get(unitId);
175
+ const to = tileOf(unitId);
176
+ if (from === undefined || to === null)
177
+ return;
178
+ if (from[0] === to[0] && from[1] === to[1])
179
+ return;
180
+ const existing = result.moves.find((m) => m.id === unitId);
181
+ if (existing !== undefined)
182
+ existing.to = to;
183
+ else
184
+ result.moves.push({ id: unitId, from, to });
185
+ }
186
+ function pushUnit(unitId, steps, depth) {
187
+ const start = tileOf(unitId);
188
+ if (start === null)
189
+ return;
190
+ if (!origins.has(unitId))
191
+ origins.set(unitId, start);
192
+ let taken = 0;
193
+ while (taken < steps) {
194
+ const cur = tileOf(unitId);
195
+ const next = [cur[0] + direction[0], cur[1] + direction[1]];
196
+ if (!inBounds(next)) {
197
+ result.collisions.push({ mover: unitId, into: "edge", at: cur });
198
+ break;
199
+ }
200
+ if (isBlocked(next)) {
201
+ result.collisions.push({ mover: unitId, into: "wall", at: cur });
202
+ break;
203
+ }
204
+ const occ = occupantAt(next);
205
+ if (occ !== null) {
206
+ result.collisions.push({ mover: unitId, into: occ, at: cur });
207
+ if (!chain || depth >= width * height)
208
+ break;
209
+ pushUnit(occ, steps - taken, depth + 1);
210
+ if (occupantAt(next) !== null)
211
+ break;
212
+ }
213
+ move(unitId, next);
214
+ taken += 1;
215
+ }
216
+ record(unitId);
217
+ }
218
+ pushUnit(id, distance, 0);
219
+ return result;
220
+ }
221
+ return {
222
+ width,
223
+ height,
224
+ inBounds,
225
+ isBlocked,
226
+ setBlocked: (tile, value) => {
227
+ if (value)
228
+ blocked.add(key(tile));
229
+ else
230
+ blocked.delete(key(tile));
231
+ },
232
+ occupantAt,
233
+ tileOf,
234
+ place,
235
+ remove,
236
+ move,
237
+ neighbors,
238
+ reachable,
239
+ path,
240
+ push,
241
+ worldToTile,
242
+ tileToWorld,
243
+ capture: () => {
244
+ const occ = {};
245
+ for (const [k, unitId] of occupants)
246
+ occ[k] = unitId;
247
+ return { blocked: [...blocked], occupants: occ };
248
+ },
249
+ restore: (snapshot) => {
250
+ blocked.clear();
251
+ for (const k of snapshot.blocked)
252
+ blocked.add(k);
253
+ occupants.clear();
254
+ tiles.clear();
255
+ for (const [k, unitId] of Object.entries(snapshot.occupants)) {
256
+ occupants.set(k, unitId);
257
+ tiles.set(unitId, parseKey(k));
258
+ }
259
+ },
260
+ };
261
+ }
@@ -0,0 +1,40 @@
1
+ export interface BeatClockConfig {
2
+ bpm: number;
3
+ /** Beats per bar/measure, for bar-relative UI (rhythm-game combo windows). Default 4. */
4
+ beatsPerBar?: number;
5
+ }
6
+ export interface BeatSnapshot {
7
+ /** Fractional beat count since the clock started. */
8
+ beat: number;
9
+ beatIndex: number;
10
+ bar: number;
11
+ beatInBar: number;
12
+ /** 0..1 fractional position within the current beat. */
13
+ phase: number;
14
+ }
15
+ export interface BeatClock {
16
+ /** Advance by a game-time delta (already scaled by ctx.time — never wall-clock). Returns the resulting snapshot and fires onBeat once per newly crossed integer beat. */
17
+ advance(gameDt: number): BeatSnapshot;
18
+ snapshot(): BeatSnapshot;
19
+ /** Elapsed game-seconds since the clock started. */
20
+ now(): number;
21
+ beatDurationSec(): number;
22
+ bpm(): number;
23
+ }
24
+ export declare function createBeatClock(config: BeatClockConfig, onBeat?: (beatIndex: number) => void): BeatClock;
25
+ /** The absolute beat-time (game-seconds) that a press at `nowSec` quantizes to: the same instant if it lands on a beat boundary, else the next one. */
26
+ export declare function nextBeatTime(nowSec: number, beatDurationSec: number, epsilon?: number): number;
27
+ export interface BufferedAction<T> {
28
+ action: T;
29
+ bufferedAtSec: number;
30
+ fireAtSec: number;
31
+ }
32
+ export interface BeatInputBuffer<T> {
33
+ /** Buffer an action; it quantizes to fire on the next beat tick (or immediately if `nowSec` is already on a beat). Returns the resolved fire time. */
34
+ buffer(action: T, nowSec: number): number;
35
+ /** Drain and return every action whose fire time has arrived, in the order they were buffered. */
36
+ advance(nowSec: number): T[];
37
+ pendingCount(): number;
38
+ clear(): void;
39
+ }
40
+ export declare function createBeatInputBuffer<T>(beatDurationSec: number): BeatInputBuffer<T>;
@@ -0,0 +1,73 @@
1
+ const MAX_BEAT_FIRES = 10_000;
2
+ export function createBeatClock(config, onBeat) {
3
+ const beatsPerBar = config.beatsPerBar !== undefined && config.beatsPerBar > 0 ? config.beatsPerBar : 4;
4
+ const beatDurationSec = 60 / config.bpm;
5
+ let elapsedSec = 0;
6
+ let lastBeatIndex = -1;
7
+ function toSnapshot() {
8
+ const beat = elapsedSec / beatDurationSec;
9
+ const beatIndex = Math.floor(beat);
10
+ return {
11
+ beat,
12
+ beatIndex,
13
+ bar: Math.floor(beatIndex / beatsPerBar),
14
+ beatInBar: ((beatIndex % beatsPerBar) + beatsPerBar) % beatsPerBar,
15
+ phase: beat - beatIndex,
16
+ };
17
+ }
18
+ function advance(gameDt) {
19
+ if (gameDt > 0)
20
+ elapsedSec += gameDt;
21
+ const snapshot = toSnapshot();
22
+ let fires = 0;
23
+ while (lastBeatIndex < snapshot.beatIndex && fires < MAX_BEAT_FIRES) {
24
+ lastBeatIndex += 1;
25
+ onBeat?.(lastBeatIndex);
26
+ fires += 1;
27
+ }
28
+ return snapshot;
29
+ }
30
+ return {
31
+ advance,
32
+ snapshot: toSnapshot,
33
+ now: () => elapsedSec,
34
+ beatDurationSec: () => beatDurationSec,
35
+ bpm: () => config.bpm,
36
+ };
37
+ }
38
+ const QUANTIZE_EPSILON = 1e-6;
39
+ /** The absolute beat-time (game-seconds) that a press at `nowSec` quantizes to: the same instant if it lands on a beat boundary, else the next one. */
40
+ export function nextBeatTime(nowSec, beatDurationSec, epsilon = QUANTIZE_EPSILON) {
41
+ const beatIndex = nowSec / beatDurationSec;
42
+ const rounded = Math.round(beatIndex);
43
+ if (Math.abs(beatIndex - rounded) <= epsilon)
44
+ return rounded * beatDurationSec;
45
+ return Math.ceil(beatIndex) * beatDurationSec;
46
+ }
47
+ export function createBeatInputBuffer(beatDurationSec) {
48
+ const pending = [];
49
+ return {
50
+ buffer(action, nowSec) {
51
+ const fireAtSec = nextBeatTime(nowSec, beatDurationSec);
52
+ pending.push({ action, bufferedAtSec: nowSec, fireAtSec });
53
+ return fireAtSec;
54
+ },
55
+ advance(nowSec) {
56
+ const due = [];
57
+ const remaining = [];
58
+ for (const entry of pending) {
59
+ if (entry.fireAtSec <= nowSec + QUANTIZE_EPSILON)
60
+ due.push(entry.action);
61
+ else
62
+ remaining.push(entry);
63
+ }
64
+ pending.length = 0;
65
+ pending.push(...remaining);
66
+ return due;
67
+ },
68
+ pendingCount: () => pending.length,
69
+ clear: () => {
70
+ pending.length = 0;
71
+ },
72
+ };
73
+ }
@@ -9,6 +9,10 @@ export interface TimeConfig {
9
9
  start?: number;
10
10
  /** Boot with the clock paused. Default false. */
11
11
  startPaused?: boolean;
12
+ /** In-world days per year, driving `calendar().year`/`dayOfYear`/`yearFraction`. Default 365. */
13
+ daysPerYear?: number;
14
+ /** Named season segments spanning the year in equal shares; when set, `calendar().season` names the current one. */
15
+ seasons?: readonly string[];
12
16
  }
13
17
  export interface CalendarTime {
14
18
  /** Total game-seconds since the world started. */
@@ -20,14 +24,24 @@ export interface CalendarTime {
20
24
  second: number;
21
25
  /** Progress through the current day, 0..1. */
22
26
  dayFraction: number;
27
+ /** 0-based year index, using `daysPerYear` (default 365). */
28
+ year: number;
29
+ /** 0-based day index within the current year. */
30
+ dayOfYear: number;
31
+ /** Progress through the current year, 0..1. */
32
+ yearFraction: number;
33
+ /** Current season name, present only when `TimeConfig.seasons` is configured. */
34
+ season?: string;
23
35
  }
24
36
  export interface ClockSnapshot {
25
37
  now: number;
26
38
  paused: boolean;
27
- /** Effective multiplier applied to real time — 0 while paused. */
39
+ /** Speed-control multiplier — 0 while paused. The full game-time rate is `speed × timescale × scale`. */
28
40
  speed: number;
29
41
  /** The non-zero multiplier `play()` resumes to. */
30
42
  playSpeed: number;
43
+ /** Global slow-motion/fast-forward multiplier; 1 unless `setTimescale` was called. */
44
+ timescale: number;
31
45
  scale: number;
32
46
  speeds: readonly number[];
33
47
  calendar: CalendarTime;
@@ -47,6 +61,16 @@ export interface SimClock {
47
61
  setSpeed(multiplier: number): void;
48
62
  /** Step to the next configured speed (wraps); always unpauses. */
49
63
  cycleSpeed(): void;
64
+ /** Current global timescale multiplier; 1 by default. */
65
+ timescale(): number;
66
+ /**
67
+ * Global slow-motion/fast-forward multiplier composed with the speed control —
68
+ * `advance` returns `realDt × scale × speed × timescale`. `0` freezes game time
69
+ * without flipping pause state (`0.08` = bullet time, `2` = fast-forward); negative
70
+ * or non-finite values clamp to 0. Timescale is host-authoritative: in multiplayer
71
+ * it belongs to the host simulation, never to an individual client.
72
+ */
73
+ setTimescale(multiplier: number): void;
50
74
  /** Run `callback` once, `seconds` of game-time from now. Returns a cancel handle. */
51
75
  after(seconds: number, callback: () => void): () => void;
52
76
  /** Run `callback` every `seconds` of game-time. Returns a cancel handle. */