@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,121 @@
1
+ export function createStateSchedule(config) {
2
+ if (config.phases.length === 0)
3
+ throw new Error("state schedule needs at least one phase");
4
+ const phases = config.phases.map((phase) => {
5
+ if (!(phase.durationSeconds > 0)) {
6
+ throw new Error(`schedule phase duration must be positive, got ${phase.durationSeconds}`);
7
+ }
8
+ return phase;
9
+ });
10
+ const loop = config.loop ?? true;
11
+ const offset = config.offsetSeconds ?? 0;
12
+ const starts = [];
13
+ let cycleSeconds = 0;
14
+ for (const phase of phases) {
15
+ starts.push(cycleSeconds);
16
+ cycleSeconds += phase.durationSeconds;
17
+ }
18
+ function localTime(t) {
19
+ const shifted = t + offset;
20
+ if (loop)
21
+ return ((shifted % cycleSeconds) + cycleSeconds) % cycleSeconds;
22
+ return Math.max(0, shifted);
23
+ }
24
+ function sampleAt(t) {
25
+ const local = localTime(t);
26
+ if (!loop && local >= cycleSeconds) {
27
+ const index = phases.length - 1;
28
+ const phase = phases[index];
29
+ return {
30
+ state: phase.state,
31
+ index,
32
+ elapsedSeconds: local - starts[index],
33
+ remainingSeconds: Number.POSITIVE_INFINITY,
34
+ fraction: 1,
35
+ };
36
+ }
37
+ let index = phases.length - 1;
38
+ for (let i = 0; i < phases.length; i += 1) {
39
+ if (local < starts[i] + phases[i].durationSeconds) {
40
+ index = i;
41
+ break;
42
+ }
43
+ }
44
+ const phase = phases[index];
45
+ const elapsed = local - starts[index];
46
+ return {
47
+ state: phase.state,
48
+ index,
49
+ elapsedSeconds: elapsed,
50
+ remainingSeconds: phase.durationSeconds - elapsed,
51
+ fraction: Math.max(0, Math.min(1, elapsed / phase.durationSeconds)),
52
+ };
53
+ }
54
+ function nextTransitionAt(t) {
55
+ const sample = sampleAt(t);
56
+ if (!Number.isFinite(sample.remainingSeconds))
57
+ return Number.POSITIVE_INFINITY;
58
+ return t + sample.remainingSeconds;
59
+ }
60
+ function windowsOf(match, fromT, horizonSeconds) {
61
+ const windows = [];
62
+ const end = fromT + horizonSeconds;
63
+ let cursor = fromT;
64
+ let open = null;
65
+ let guard = phases.length * (Math.ceil(horizonSeconds / cycleSeconds) + 2) + 2;
66
+ while (cursor < end && guard > 0) {
67
+ guard -= 1;
68
+ const sample = sampleAt(cursor);
69
+ const matches = match(sample.state);
70
+ if (matches && open === null)
71
+ open = cursor;
72
+ const boundary = Number.isFinite(sample.remainingSeconds) ? cursor + sample.remainingSeconds : end;
73
+ const stop = Math.min(boundary, end);
74
+ if (!matches && open !== null) {
75
+ windows.push({ start: open, end: cursor });
76
+ open = null;
77
+ }
78
+ cursor = stop;
79
+ if (!Number.isFinite(sample.remainingSeconds))
80
+ break;
81
+ }
82
+ if (open !== null)
83
+ windows.push({ start: open, end: Math.min(cursor, end) });
84
+ return windows;
85
+ }
86
+ return {
87
+ cycleSeconds,
88
+ stateAt: (t) => sampleAt(t).state,
89
+ sampleAt,
90
+ nextTransitionAt,
91
+ windowsOf,
92
+ nextWindow(match, fromT, horizonSeconds = cycleSeconds) {
93
+ const windows = windowsOf(match, fromT, horizonSeconds);
94
+ return windows.length > 0 ? windows[0] : null;
95
+ },
96
+ };
97
+ }
98
+ /**
99
+ * Forward-scan any predicate-of-time — a timetable mover's "is the crossing clear at `t`" — for the
100
+ * next open window. Sampling-based: pick `stepSeconds` at or below half the shortest gap that matters.
101
+ */
102
+ export function nextClearWindow(isClear, scan) {
103
+ const minDuration = scan.minDurationSeconds ?? 0;
104
+ const end = scan.fromSeconds + scan.horizonSeconds;
105
+ let open = null;
106
+ for (let t = scan.fromSeconds; t <= end; t += scan.stepSeconds) {
107
+ if (isClear(t)) {
108
+ if (open === null)
109
+ open = t;
110
+ continue;
111
+ }
112
+ if (open !== null) {
113
+ if (t - open >= minDuration)
114
+ return { start: open, end: t };
115
+ open = null;
116
+ }
117
+ }
118
+ if (open !== null && end - open >= minDuration)
119
+ return { start: open, end };
120
+ return null;
121
+ }
@@ -1,4 +1,4 @@
1
- export type CommitMode = "immediate" | "simultaneous" | "rewind";
1
+ export type CommitMode = "immediate" | "simultaneous" | "resealable";
2
2
  export interface SubmittedAction<TAction> {
3
3
  participant: string;
4
4
  action: TAction;
@@ -21,7 +21,7 @@ export interface CommitController<TAction> {
21
21
  pending(): SubmittedAction<TAction>[];
22
22
  reveal(): SubmittedAction<TAction>[];
23
23
  commit(): SubmittedAction<TAction>[];
24
- rewind(): SubmittedAction<TAction>[];
24
+ discard(): SubmittedAction<TAction>[];
25
25
  clear(): void;
26
26
  }
27
27
  export declare function createCommitController<TAction>(config: CommitControllerConfig): CommitController<TAction>;
@@ -31,7 +31,7 @@ export function createCommitController(config) {
31
31
  if (mode === "immediate") {
32
32
  return { status: "committed", committed: [{ participant, action }] };
33
33
  }
34
- if (sealed.has(participant) && !(mode === "rewind")) {
34
+ if (sealed.has(participant) && mode !== "resealable") {
35
35
  return { status: "rejected", committed: [], reason: "already-submitted" };
36
36
  }
37
37
  sealed.set(participant, action);
@@ -62,7 +62,7 @@ export function createCommitController(config) {
62
62
  clear();
63
63
  return out;
64
64
  },
65
- rewind() {
65
+ discard() {
66
66
  const discarded = orderedSubmissions();
67
67
  clear();
68
68
  return discarded;
@@ -0,0 +1,14 @@
1
+ export interface DeclaredIntent<TKind extends string = string> {
2
+ kind: TKind;
3
+ magnitude?: number;
4
+ targetId?: string;
5
+ note?: string;
6
+ }
7
+ export interface IntentBoard<TKind extends string = string> {
8
+ declare(participantId: string, intent: DeclaredIntent<TKind>): void;
9
+ peek(participantId: string): DeclaredIntent<TKind> | null;
10
+ all(): readonly [string, DeclaredIntent<TKind>][];
11
+ consume(participantId: string): DeclaredIntent<TKind> | null;
12
+ clear(participantId?: string): void;
13
+ }
14
+ export declare function createIntentBoard<TKind extends string = string>(): IntentBoard<TKind>;
@@ -0,0 +1,29 @@
1
+ export function createIntentBoard() {
2
+ const intents = new Map();
3
+ return {
4
+ declare(participantId, intent) {
5
+ intents.set(participantId, { ...intent });
6
+ },
7
+ peek(participantId) {
8
+ const intent = intents.get(participantId);
9
+ return intent === undefined ? null : { ...intent };
10
+ },
11
+ all() {
12
+ return [...intents.entries()].map(([participantId, intent]) => [participantId, { ...intent }]);
13
+ },
14
+ consume(participantId) {
15
+ const intent = intents.get(participantId);
16
+ if (intent === undefined)
17
+ return null;
18
+ intents.delete(participantId);
19
+ return intent;
20
+ },
21
+ clear(participantId) {
22
+ if (participantId === undefined) {
23
+ intents.clear();
24
+ return;
25
+ }
26
+ intents.delete(participantId);
27
+ },
28
+ };
29
+ }
@@ -12,6 +12,8 @@ export interface TurnLoopConfig {
12
12
  commit?: {
13
13
  mode: CommitMode;
14
14
  };
15
+ onTurnStart?(participantId: string): void;
16
+ onTurnEnd?(participantId: string): void;
15
17
  }
16
18
  export interface PoolState {
17
19
  id: string;
@@ -27,6 +27,8 @@ export function createTurnLoop(config) {
27
27
  }
28
28
  for (const id of order)
29
29
  resetPools(id);
30
+ if (activeIndex >= 0)
31
+ config.onTurnStart?.(order[activeIndex]);
30
32
  function active() {
31
33
  return activeIndex >= 0 && activeIndex < order.length ? order[activeIndex] : null;
32
34
  }
@@ -48,6 +50,7 @@ export function createTurnLoop(config) {
48
50
  activeIndex = -1;
49
51
  return state();
50
52
  }
53
+ const previous = active();
51
54
  phaseIndex = 0;
52
55
  if (activeIndex < 0) {
53
56
  activeIndex = 0;
@@ -59,9 +62,13 @@ export function createTurnLoop(config) {
59
62
  else {
60
63
  activeIndex += 1;
61
64
  }
65
+ if (previous !== null)
66
+ config.onTurnEnd?.(previous);
62
67
  const current = active();
63
- if (current !== null)
68
+ if (current !== null) {
64
69
  resetPools(current);
70
+ config.onTurnStart?.(current);
71
+ }
65
72
  return state();
66
73
  }
67
74
  function advancePhase() {
@@ -0,0 +1,58 @@
1
+ export type HudAnchor = "top-left" | "top" | "top-right" | "left" | "center" | "right" | "bottom-left" | "bottom" | "bottom-right";
2
+ export interface HudPlacement {
3
+ anchor: HudAnchor;
4
+ dx: number;
5
+ dy: number;
6
+ }
7
+ export interface HudRect {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ }
13
+ export interface HudSize {
14
+ width: number;
15
+ height: number;
16
+ }
17
+ export interface HudPanelState {
18
+ id: string;
19
+ placement: HudPlacement;
20
+ z: number;
21
+ moved: boolean;
22
+ }
23
+ export interface HudLayoutState {
24
+ panels: Record<string, HudPanelState>;
25
+ locked: boolean;
26
+ editing: boolean;
27
+ }
28
+ export interface HudLayoutOptions {
29
+ snap?: number;
30
+ locked?: boolean;
31
+ }
32
+ export declare function isPanelDraggable(state: HudLayoutState, id: string): boolean;
33
+ export interface HudLayoutStore {
34
+ getState(): HudLayoutState;
35
+ subscribe(listener: (state: HudLayoutState) => void): () => void;
36
+ register(id: string, placement: HudPlacement): void;
37
+ move(id: string, rect: HudRect, viewport: HudSize): void;
38
+ bringToFront(id: string): void;
39
+ setLocked(locked: boolean): void;
40
+ setEditing(editing: boolean): void;
41
+ reset(id?: string): void;
42
+ serialize(): string;
43
+ hydrate(raw: string | null | undefined): void;
44
+ }
45
+ export declare const HUD_ANCHOR_FRACTIONS: Record<HudAnchor, {
46
+ fx: number;
47
+ fy: number;
48
+ }>;
49
+ export declare function isHudAnchor(value: unknown): value is HudAnchor;
50
+ export declare function anchoredPlacement(anchor: HudAnchor, inset: {
51
+ x: number;
52
+ y: number;
53
+ }): HudPlacement;
54
+ export declare function nearestAnchor(rect: HudRect, viewport: HudSize): HudAnchor;
55
+ export declare function clampRect(rect: HudRect, viewport: HudSize): HudRect;
56
+ export declare function placementFromRect(rect: HudRect, viewport: HudSize, snap?: number): HudPlacement;
57
+ export declare function rectFromPlacement(placement: HudPlacement, size: HudSize, viewport: HudSize): HudRect;
58
+ export declare function createHudLayout(options?: HudLayoutOptions): HudLayoutStore;
@@ -0,0 +1,207 @@
1
+ export function isPanelDraggable(state, id) {
2
+ return state.panels[id] !== undefined && (state.editing || !state.locked);
3
+ }
4
+ export const HUD_ANCHOR_FRACTIONS = {
5
+ "top-left": { fx: 0, fy: 0 },
6
+ top: { fx: 0.5, fy: 0 },
7
+ "top-right": { fx: 1, fy: 0 },
8
+ left: { fx: 0, fy: 0.5 },
9
+ center: { fx: 0.5, fy: 0.5 },
10
+ right: { fx: 1, fy: 0.5 },
11
+ "bottom-left": { fx: 0, fy: 1 },
12
+ bottom: { fx: 0.5, fy: 1 },
13
+ "bottom-right": { fx: 1, fy: 1 },
14
+ };
15
+ const HUD_ANCHORS = Object.keys(HUD_ANCHOR_FRACTIONS);
16
+ export function isHudAnchor(value) {
17
+ return typeof value === "string" && value in HUD_ANCHOR_FRACTIONS;
18
+ }
19
+ export function anchoredPlacement(anchor, inset) {
20
+ const { fx, fy } = HUD_ANCHOR_FRACTIONS[anchor];
21
+ return {
22
+ anchor,
23
+ dx: fx === 1 ? -inset.x : inset.x,
24
+ dy: fy === 1 ? -inset.y : inset.y,
25
+ };
26
+ }
27
+ export function nearestAnchor(rect, viewport) {
28
+ const cx = viewport.width > 0 ? (rect.x + rect.width / 2) / viewport.width : 0.5;
29
+ const cy = viewport.height > 0 ? (rect.y + rect.height / 2) / viewport.height : 0.5;
30
+ let best = "center";
31
+ let bestDist = Infinity;
32
+ for (const anchor of HUD_ANCHORS) {
33
+ const { fx, fy } = HUD_ANCHOR_FRACTIONS[anchor];
34
+ const dist = (cx - fx) * (cx - fx) + (cy - fy) * (cy - fy);
35
+ if (dist < bestDist) {
36
+ bestDist = dist;
37
+ best = anchor;
38
+ }
39
+ }
40
+ return best;
41
+ }
42
+ export function clampRect(rect, viewport) {
43
+ return {
44
+ x: Math.max(0, Math.min(rect.x, viewport.width - rect.width)),
45
+ y: Math.max(0, Math.min(rect.y, viewport.height - rect.height)),
46
+ width: rect.width,
47
+ height: rect.height,
48
+ };
49
+ }
50
+ export function placementFromRect(rect, viewport, snap = 1) {
51
+ const clamped = clampRect(rect, viewport);
52
+ const anchor = nearestAnchor(clamped, viewport);
53
+ const { fx, fy } = HUD_ANCHOR_FRACTIONS[anchor];
54
+ const step = snap > 0 ? snap : 1;
55
+ const dx = clamped.x + fx * clamped.width - fx * viewport.width;
56
+ const dy = clamped.y + fy * clamped.height - fy * viewport.height;
57
+ return {
58
+ anchor,
59
+ dx: Math.round(dx / step) * step,
60
+ dy: Math.round(dy / step) * step,
61
+ };
62
+ }
63
+ export function rectFromPlacement(placement, size, viewport) {
64
+ const { fx, fy } = HUD_ANCHOR_FRACTIONS[placement.anchor];
65
+ return {
66
+ x: fx * viewport.width + placement.dx - fx * size.width,
67
+ y: fy * viewport.height + placement.dy - fy * size.height,
68
+ width: size.width,
69
+ height: size.height,
70
+ };
71
+ }
72
+ function parseSerialized(raw) {
73
+ const out = new Map();
74
+ let data;
75
+ try {
76
+ data = JSON.parse(raw);
77
+ }
78
+ catch {
79
+ return out;
80
+ }
81
+ if (typeof data !== "object" || data === null)
82
+ return out;
83
+ const panels = data.panels;
84
+ if (typeof panels !== "object" || panels === null)
85
+ return out;
86
+ for (const [id, value] of Object.entries(panels)) {
87
+ if (typeof value !== "object" || value === null)
88
+ continue;
89
+ const { anchor, dx, dy } = value;
90
+ if (!isHudAnchor(anchor))
91
+ continue;
92
+ if (typeof dx !== "number" || !Number.isFinite(dx))
93
+ continue;
94
+ if (typeof dy !== "number" || !Number.isFinite(dy))
95
+ continue;
96
+ out.set(id, { anchor, dx, dy });
97
+ }
98
+ return out;
99
+ }
100
+ export function createHudLayout(options) {
101
+ const snap = options?.snap ?? 1;
102
+ const defaults = new Map();
103
+ const pending = new Map();
104
+ let topZ = 0;
105
+ let state = { panels: {}, locked: options?.locked ?? true, editing: false };
106
+ const listeners = new Set();
107
+ const emit = () => {
108
+ for (const listener of [...listeners])
109
+ listener(state);
110
+ };
111
+ const setPanel = (panel) => {
112
+ state = { ...state, panels: { ...state.panels, [panel.id]: panel } };
113
+ };
114
+ return {
115
+ getState: () => state,
116
+ subscribe(listener) {
117
+ listeners.add(listener);
118
+ return () => listeners.delete(listener);
119
+ },
120
+ register(id, placement) {
121
+ defaults.set(id, placement);
122
+ if (state.panels[id] !== undefined)
123
+ return;
124
+ const override = pending.get(id);
125
+ setPanel({
126
+ id,
127
+ placement: override ?? placement,
128
+ z: ++topZ,
129
+ moved: override !== undefined,
130
+ });
131
+ },
132
+ move(id, rect, viewport) {
133
+ const panel = state.panels[id];
134
+ if (panel === undefined)
135
+ return;
136
+ const placement = placementFromRect(rect, viewport, snap);
137
+ setPanel({ ...panel, placement, moved: true });
138
+ emit();
139
+ },
140
+ bringToFront(id) {
141
+ const panel = state.panels[id];
142
+ if (panel === undefined || panel.z === topZ)
143
+ return;
144
+ setPanel({ ...panel, z: ++topZ });
145
+ emit();
146
+ },
147
+ setLocked(locked) {
148
+ if (state.locked === locked)
149
+ return;
150
+ state = { ...state, locked };
151
+ emit();
152
+ },
153
+ setEditing(editing) {
154
+ if (state.editing === editing)
155
+ return;
156
+ state = { ...state, editing };
157
+ emit();
158
+ },
159
+ reset(id) {
160
+ const ids = id === undefined ? Object.keys(state.panels) : [id];
161
+ let changed = false;
162
+ for (const panelId of ids) {
163
+ pending.delete(panelId);
164
+ const panel = state.panels[panelId];
165
+ const fallback = defaults.get(panelId);
166
+ if (panel === undefined || fallback === undefined || !panel.moved)
167
+ continue;
168
+ setPanel({ ...panel, placement: fallback, moved: false });
169
+ changed = true;
170
+ }
171
+ if (changed)
172
+ emit();
173
+ },
174
+ serialize() {
175
+ const panels = {};
176
+ for (const panel of Object.values(state.panels)) {
177
+ if (panel.moved)
178
+ panels[panel.id] = panel.placement;
179
+ }
180
+ for (const [id, placement] of pending) {
181
+ if (state.panels[id] === undefined)
182
+ panels[id] = placement;
183
+ }
184
+ const data = { v: 1, panels };
185
+ return JSON.stringify(data);
186
+ },
187
+ hydrate(raw) {
188
+ if (raw === null || raw === undefined || raw === "")
189
+ return;
190
+ const parsed = parseSerialized(raw);
191
+ if (parsed.size === 0)
192
+ return;
193
+ let changed = false;
194
+ for (const [id, placement] of parsed) {
195
+ const panel = state.panels[id];
196
+ if (panel === undefined) {
197
+ pending.set(id, placement);
198
+ continue;
199
+ }
200
+ setPanel({ ...panel, placement, moved: true });
201
+ changed = true;
202
+ }
203
+ if (changed)
204
+ emit();
205
+ },
206
+ };
207
+ }
@@ -0,0 +1,44 @@
1
+ import type { HudRect, HudSize } from "./hudLayout.js";
2
+ /** Where a game is meant to be played. `"web"` alone keeps today's desktop-first HUD; adding `"mobile"` turns on design-resolution fit scaling on compact displays. */
3
+ export type HudPlatform = "web" | "mobile";
4
+ /**
5
+ * Design-resolution fit for the HUD surface. The game authors its UI against
6
+ * `designSize`; on smaller viewports the whole HUD scales down by the limiting
7
+ * axis ratio, clamped to `[minScale, maxScale]`, so panels keep their layout
8
+ * and simply shrink instead of overflowing.
9
+ */
10
+ export interface HudFitConfig {
11
+ designSize?: HudSize;
12
+ /** Floor for the computed scale — below this, shrinking further makes text unreadable. Default 0.4. */
13
+ minScale?: number;
14
+ /** Ceiling for the computed scale. Default 1 (never upscale past authored size). */
15
+ maxScale?: number;
16
+ }
17
+ /** Per-game HUD viewport declaration carried on `PlayableGame.hudFit`; `mobile` overrides the fit on compact displays so the owner can tune the phone layout separately. */
18
+ export interface HudViewportConfig extends HudFitConfig {
19
+ mobile?: HudFitConfig;
20
+ }
21
+ export declare const DEFAULT_HUD_DESIGN_SIZE: HudSize;
22
+ export declare const DEFAULT_HUD_MIN_SCALE = 0.4;
23
+ export declare const DEFAULT_HUD_MAX_SCALE = 1;
24
+ export declare function resolveHudFit(config: HudViewportConfig | undefined, mobile: boolean): Required<HudFitConfig>;
25
+ /**
26
+ * The one scaling rule for every display: the ratio of the live viewport to
27
+ * the authored design size along the limiting axis, clamped. 1 on a viewport
28
+ * at or above design size; smoothly below 1 down to `minScale` on phones.
29
+ */
30
+ export declare function hudScaleForViewport(fit: Required<HudFitConfig>, viewport: HudSize): number;
31
+ export interface HudOverflow {
32
+ id: string;
33
+ left: number;
34
+ top: number;
35
+ right: number;
36
+ bottom: number;
37
+ }
38
+ /** Non-null when any edge of `rect` falls outside `viewport` by more than `tolerance` px; each field is the overflow distance past that edge (0 when inside). */
39
+ export declare function rectOverflow(rect: HudRect, viewport: HudSize, tolerance?: number): Omit<HudOverflow, "id"> | null;
40
+ /** Every panel rect that escapes the viewport — the data behind the HUD overflow gate. */
41
+ export declare function overflowingPanels(panels: readonly {
42
+ id: string;
43
+ rect: HudRect;
44
+ }[], viewport: HudSize, tolerance?: number): HudOverflow[];
@@ -0,0 +1,55 @@
1
+ export const DEFAULT_HUD_DESIGN_SIZE = { width: 1600, height: 900 };
2
+ export const DEFAULT_HUD_MIN_SCALE = 0.4;
3
+ export const DEFAULT_HUD_MAX_SCALE = 1;
4
+ export function resolveHudFit(config, mobile) {
5
+ const base = {
6
+ designSize: config?.designSize ?? DEFAULT_HUD_DESIGN_SIZE,
7
+ minScale: config?.minScale ?? DEFAULT_HUD_MIN_SCALE,
8
+ maxScale: config?.maxScale ?? DEFAULT_HUD_MAX_SCALE,
9
+ };
10
+ if (!mobile || config?.mobile === undefined)
11
+ return base;
12
+ return {
13
+ designSize: config.mobile.designSize ?? base.designSize,
14
+ minScale: config.mobile.minScale ?? base.minScale,
15
+ maxScale: config.mobile.maxScale ?? base.maxScale,
16
+ };
17
+ }
18
+ /**
19
+ * The one scaling rule for every display: the ratio of the live viewport to
20
+ * the authored design size along the limiting axis, clamped. 1 on a viewport
21
+ * at or above design size; smoothly below 1 down to `minScale` on phones.
22
+ */
23
+ export function hudScaleForViewport(fit, viewport) {
24
+ if (!Number.isFinite(viewport.width) ||
25
+ !Number.isFinite(viewport.height) ||
26
+ viewport.width <= 0 ||
27
+ viewport.height <= 0 ||
28
+ fit.designSize.width <= 0 ||
29
+ fit.designSize.height <= 0) {
30
+ return 1;
31
+ }
32
+ const ratio = Math.min(viewport.width / fit.designSize.width, viewport.height / fit.designSize.height);
33
+ return Math.min(fit.maxScale, Math.max(fit.minScale, ratio));
34
+ }
35
+ /** Non-null when any edge of `rect` falls outside `viewport` by more than `tolerance` px; each field is the overflow distance past that edge (0 when inside). */
36
+ export function rectOverflow(rect, viewport, tolerance = 1.5) {
37
+ const left = Math.max(0, -rect.x);
38
+ const top = Math.max(0, -rect.y);
39
+ const right = Math.max(0, rect.x + rect.width - viewport.width);
40
+ const bottom = Math.max(0, rect.y + rect.height - viewport.height);
41
+ if (left <= tolerance && top <= tolerance && right <= tolerance && bottom <= tolerance)
42
+ return null;
43
+ const round = (n) => Math.round(n * 10) / 10;
44
+ return { left: round(left), top: round(top), right: round(right), bottom: round(bottom) };
45
+ }
46
+ /** Every panel rect that escapes the viewport — the data behind the HUD overflow gate. */
47
+ export function overflowingPanels(panels, viewport, tolerance = 1.5) {
48
+ const out = [];
49
+ for (const panel of panels) {
50
+ const overflow = rectOverflow(panel.rect, viewport, tolerance);
51
+ if (overflow !== null)
52
+ out.push({ id: panel.id, ...overflow });
53
+ }
54
+ return out;
55
+ }
@@ -0,0 +1,66 @@
1
+ import type { StreamingSettings } from "./settings.js";
2
+ import type { VisibilityStats } from "./diagnostics.js";
3
+ export type AssetLoadState = "queued" | "loading" | "loaded" | "unloaded" | "error";
4
+ export interface AssetLoadResult {
5
+ /** Approximate resident size, used for the memory budget and small-asset resident policy. */
6
+ readonly bytes?: number;
7
+ /** Opaque handle to the loaded resource (texture, mesh, buffer). */
8
+ readonly value?: unknown;
9
+ }
10
+ export interface CancelSignal {
11
+ readonly cancelled: boolean;
12
+ }
13
+ export interface AssetStreamingOptions {
14
+ /** Async loader. Must honor `signal.cancelled` and avoid committing work when it flips true. */
15
+ readonly load: (assetId: string, signal: CancelSignal) => Promise<AssetLoadResult>;
16
+ /** Release GPU/CPU resources for an asset. */
17
+ readonly unload?: (assetId: string) => void;
18
+ readonly settings?: Partial<StreamingSettings>;
19
+ readonly now?: () => number;
20
+ }
21
+ export interface AssetRecord {
22
+ readonly id: string;
23
+ state: AssetLoadState;
24
+ bytes: number;
25
+ priority: number;
26
+ refCount: number;
27
+ pinned: boolean;
28
+ lastActiveMs: number;
29
+ value: unknown;
30
+ }
31
+ export interface StreamingStats {
32
+ queued: number;
33
+ loading: number;
34
+ loaded: number;
35
+ unloaded: number;
36
+ errored: number;
37
+ inFlight: number;
38
+ cancelled: number;
39
+ bytes: number;
40
+ }
41
+ export interface AssetStreamingSystem {
42
+ /** Demand an asset (near/in view). Deduplicated — an already loaded/loading/queued asset is never re-fetched concurrently. */
43
+ request(assetId: string, priority?: number): void;
44
+ /** Shared ownership by an active object. A retained asset (refCount > 0) is never auto-unloaded. */
45
+ retain(assetId: string): void;
46
+ release(assetId: string): void;
47
+ /** Pin resident regardless of distance/refcount/scene. */
48
+ pin(assetId: string): void;
49
+ unpin(assetId: string): void;
50
+ /** Refresh the "last needed" timestamp used by the grace-period unload. */
51
+ markActive(assetId: string): void;
52
+ /** Advance one frame: start up to the load budget, evict past-grace assets up to the unload budget. */
53
+ tick(dt: number): void;
54
+ /** Cancel an in-flight or queued request that is no longer needed. */
55
+ cancel(assetId: string): void;
56
+ stateOf(assetId: string): AssetLoadState | undefined;
57
+ isLoaded(assetId: string): boolean;
58
+ record(assetId: string): AssetRecord | undefined;
59
+ stats(): StreamingStats;
60
+ /** Merge asset counters into a per-frame VisibilityStats. */
61
+ applyTo(stats: VisibilityStats): void;
62
+ /** Resolve once every in-flight load settles — for deterministic tests. */
63
+ settle(): Promise<void>;
64
+ clear(): void;
65
+ }
66
+ export declare function createAssetStreamingSystem(options: AssetStreamingOptions): AssetStreamingSystem;