@jgengine/core 0.8.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 (228) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/ai/flock.d.ts +36 -0
  3. package/dist/ai/flock.js +182 -0
  4. package/dist/ai/groupAssist.d.ts +19 -0
  5. package/dist/ai/groupAssist.js +59 -0
  6. package/dist/ai/laneSelect.d.ts +21 -0
  7. package/dist/ai/laneSelect.js +37 -0
  8. package/dist/ai/mobBrain.d.ts +68 -0
  9. package/dist/ai/mobBrain.js +115 -0
  10. package/dist/ai/spawnDirector.js +1 -1
  11. package/dist/ai/threat.d.ts +2 -0
  12. package/dist/ai/threat.js +34 -0
  13. package/dist/cartridge/runtime.d.ts +15 -0
  14. package/dist/cartridge/runtime.js +556 -0
  15. package/dist/cartridge/spec.d.ts +188 -0
  16. package/dist/cartridge/spec.js +19 -0
  17. package/dist/cartridge/validate.d.ts +2 -0
  18. package/dist/cartridge/validate.js +78 -0
  19. package/dist/combat/abilityKit.d.ts +17 -2
  20. package/dist/combat/abilityKit.js +62 -13
  21. package/dist/combat/castRunner.d.ts +40 -0
  22. package/dist/combat/castRunner.js +56 -0
  23. package/dist/combat/comboPoints.d.ts +14 -0
  24. package/dist/combat/comboPoints.js +52 -0
  25. package/dist/combat/defensiveWindow.d.ts +1 -1
  26. package/dist/combat/defensiveWindow.js +5 -3
  27. package/dist/combat/projectiles.d.ts +33 -12
  28. package/dist/combat/projectiles.js +201 -129
  29. package/dist/combat/resistance.d.ts +16 -4
  30. package/dist/combat/resistance.js +43 -7
  31. package/dist/combat/resourcePool.d.ts +20 -0
  32. package/dist/combat/resourcePool.js +55 -0
  33. package/dist/combat/shotOrigin.d.ts +40 -0
  34. package/dist/combat/shotOrigin.js +57 -0
  35. package/dist/combat/telegraph.d.ts +25 -0
  36. package/dist/combat/telegraph.js +47 -0
  37. package/dist/crafting/recipe.js +4 -1
  38. package/dist/devtools/devtools.d.ts +72 -24
  39. package/dist/devtools/devtools.js +449 -101
  40. package/dist/devtools/transformTunables.js +3 -3
  41. package/dist/devtools/tunableSchema.d.ts +140 -0
  42. package/dist/devtools/tunableSchema.js +476 -0
  43. package/dist/faction/factions.d.ts +33 -0
  44. package/dist/faction/factions.js +76 -0
  45. package/dist/faction/reputation.d.ts +32 -0
  46. package/dist/faction/reputation.js +82 -0
  47. package/dist/game/chat.d.ts +2 -0
  48. package/dist/game/chat.js +9 -1
  49. package/dist/game/chatFilter.d.ts +15 -0
  50. package/dist/game/chatFilter.js +55 -0
  51. package/dist/game/controlGate.d.ts +4 -0
  52. package/dist/game/controlGate.js +7 -0
  53. package/dist/game/defineGame.d.ts +2 -0
  54. package/dist/game/events.d.ts +10 -0
  55. package/dist/game/gamePhase.d.ts +20 -0
  56. package/dist/game/gamePhase.js +21 -0
  57. package/dist/game/lootTable.d.ts +10 -1
  58. package/dist/game/lootTable.js +27 -7
  59. package/dist/game/playableGame.d.ts +50 -1
  60. package/dist/game/playableGame.js +1 -1
  61. package/dist/game/quest.d.ts +7 -3
  62. package/dist/game/quest.js +17 -8
  63. package/dist/game/race.d.ts +39 -1
  64. package/dist/game/race.js +96 -11
  65. package/dist/game/recordBook.d.ts +29 -0
  66. package/dist/game/recordBook.js +86 -0
  67. package/dist/game/talents.d.ts +47 -0
  68. package/dist/game/talents.js +140 -0
  69. package/dist/index.d.ts +1 -0
  70. package/dist/index.js +1 -0
  71. package/dist/input/axisInput.d.ts +26 -3
  72. package/dist/input/axisInput.js +44 -4
  73. package/dist/input/bindingOverrides.d.ts +25 -0
  74. package/dist/input/bindingOverrides.js +95 -0
  75. package/dist/input/pointerAxis.d.ts +30 -0
  76. package/dist/input/pointerAxis.js +31 -0
  77. package/dist/input/touchScheme.d.ts +6 -0
  78. package/dist/input/touchScheme.js +45 -7
  79. package/dist/meta/changelog.d.ts +1 -1
  80. package/dist/meta/changelog.js +23 -1
  81. package/dist/movement/dash.d.ts +3 -0
  82. package/dist/movement/dash.js +19 -5
  83. package/dist/movement/glideModel.d.ts +44 -0
  84. package/dist/movement/glideModel.js +54 -0
  85. package/dist/movement/grappleSwing.d.ts +34 -0
  86. package/dist/movement/grappleSwing.js +56 -0
  87. package/dist/movement/leaderTrail.d.ts +29 -0
  88. package/dist/movement/leaderTrail.js +67 -0
  89. package/dist/movement/steering.d.ts +27 -0
  90. package/dist/movement/steering.js +40 -0
  91. package/dist/multiplayer/feedWriteGate.d.ts +10 -0
  92. package/dist/multiplayer/feedWriteGate.js +12 -0
  93. package/dist/multiplayer/presenceModel.d.ts +14 -2
  94. package/dist/multiplayer/presenceModel.js +15 -3
  95. package/dist/nav/corridors.d.ts +30 -0
  96. package/dist/nav/corridors.js +69 -0
  97. package/dist/nav/navGrid.js +30 -4
  98. package/dist/nav/railGraph.d.ts +66 -0
  99. package/dist/nav/railGraph.js +129 -0
  100. package/dist/nav/timetable.d.ts +41 -0
  101. package/dist/nav/timetable.js +94 -0
  102. package/dist/physics/buoyancy.d.ts +6 -0
  103. package/dist/physics/buoyancy.js +14 -0
  104. package/dist/physics/flowTube.d.ts +32 -0
  105. package/dist/physics/flowTube.js +64 -0
  106. package/dist/physics/forceVolume.d.ts +27 -0
  107. package/dist/physics/forceVolume.js +38 -0
  108. package/dist/physics/kinematicVehicle.d.ts +52 -0
  109. package/dist/physics/kinematicVehicle.js +81 -0
  110. package/dist/physics/physicsWorld.d.ts +13 -6
  111. package/dist/physics/physicsWorld.js +169 -40
  112. package/dist/physics/ragdoll.js +1 -1
  113. package/dist/physics/vehicleBody.js +2 -1
  114. package/dist/random/seedLink.d.ts +5 -0
  115. package/dist/random/seedLink.js +35 -0
  116. package/dist/runtime/cameraDirector.d.ts +6 -1
  117. package/dist/runtime/cameraDirector.js +6 -0
  118. package/dist/runtime/commandRunner.d.ts +2 -2
  119. package/dist/runtime/commandRunner.js +2 -2
  120. package/dist/runtime/gameContext.d.ts +21 -0
  121. package/dist/runtime/gameContext.js +184 -9
  122. package/dist/runtime/gameRuntime.d.ts +5 -1
  123. package/dist/runtime/gameRuntime.js +13 -12
  124. package/dist/runtime/hostPersistence.d.ts +9 -1
  125. package/dist/runtime/hostPersistence.js +23 -8
  126. package/dist/runtime/inputSnapshot.d.ts +5 -0
  127. package/dist/runtime/inputSnapshot.js +5 -0
  128. package/dist/runtime/motionIntents.d.ts +14 -6
  129. package/dist/runtime/motionIntents.js +20 -2
  130. package/dist/runtime/snapshot.d.ts +1 -0
  131. package/dist/runtime/snapshot.js +1 -1
  132. package/dist/runtime/visibility.d.ts +74 -0
  133. package/dist/runtime/visibility.js +127 -0
  134. package/dist/scene/colliders.d.ts +44 -0
  135. package/dist/scene/colliders.js +77 -0
  136. package/dist/scene/entityStore.d.ts +22 -5
  137. package/dist/scene/entityStore.js +59 -12
  138. package/dist/scene/objectQuery.d.ts +13 -2
  139. package/dist/scene/objectQuery.js +34 -2
  140. package/dist/scene/objectStore.d.ts +2 -0
  141. package/dist/scene/objectStore.js +31 -12
  142. package/dist/scene/sceneRaycast.d.ts +80 -0
  143. package/dist/scene/sceneRaycast.js +284 -0
  144. package/dist/scene/spatial.d.ts +11 -16
  145. package/dist/scene/spatial.js +99 -38
  146. package/dist/sensor/recordingBuffer.d.ts +6 -0
  147. package/dist/sensor/recordingBuffer.js +7 -0
  148. package/dist/sensor/replayLoop.d.ts +39 -0
  149. package/dist/sensor/replayLoop.js +64 -0
  150. package/dist/sensor/visionCone.d.ts +31 -0
  151. package/dist/sensor/visionCone.js +72 -0
  152. package/dist/session/extraction.d.ts +2 -2
  153. package/dist/session/extraction.js +13 -2
  154. package/dist/settings/settingsModel.d.ts +97 -0
  155. package/dist/settings/settingsModel.js +100 -0
  156. package/dist/stats/statModifiers.d.ts +5 -2
  157. package/dist/stats/statModifiers.js +32 -14
  158. package/dist/store/observableKeyedStore.js +8 -2
  159. package/dist/survival/decayMeter.d.ts +1 -1
  160. package/dist/survival/decayMeter.js +0 -1
  161. package/dist/time/beatClock.d.ts +18 -0
  162. package/dist/time/beatClock.js +24 -0
  163. package/dist/time/idleProgress.d.ts +29 -0
  164. package/dist/time/idleProgress.js +31 -0
  165. package/dist/time/serverTick.d.ts +7 -1
  166. package/dist/time/serverTick.js +27 -4
  167. package/dist/time/stateSchedule.d.ts +57 -0
  168. package/dist/time/stateSchedule.js +121 -0
  169. package/dist/turn/commit.d.ts +2 -2
  170. package/dist/turn/commit.js +2 -2
  171. package/dist/ui/hudLayout.d.ts +58 -0
  172. package/dist/ui/hudLayout.js +207 -0
  173. package/dist/ui/hudScale.d.ts +44 -0
  174. package/dist/ui/hudScale.js +55 -0
  175. package/dist/visibility/assetStreaming.d.ts +66 -0
  176. package/dist/visibility/assetStreaming.js +201 -0
  177. package/dist/visibility/bounds.d.ts +66 -0
  178. package/dist/visibility/bounds.js +113 -0
  179. package/dist/visibility/camera.d.ts +21 -0
  180. package/dist/visibility/camera.js +3 -0
  181. package/dist/visibility/config.d.ts +21 -0
  182. package/dist/visibility/config.js +1 -0
  183. package/dist/visibility/diagnostics.d.ts +26 -0
  184. package/dist/visibility/diagnostics.js +36 -0
  185. package/dist/visibility/distance.d.ts +11 -0
  186. package/dist/visibility/distance.js +30 -0
  187. package/dist/visibility/frustum.d.ts +47 -0
  188. package/dist/visibility/frustum.js +186 -0
  189. package/dist/visibility/occlusion.d.ts +36 -0
  190. package/dist/visibility/occlusion.js +45 -0
  191. package/dist/visibility/settings.d.ts +75 -0
  192. package/dist/visibility/settings.js +49 -0
  193. package/dist/visibility/simulationCulling.d.ts +34 -0
  194. package/dist/visibility/simulationCulling.js +36 -0
  195. package/dist/visibility/spatialIndex.d.ts +45 -0
  196. package/dist/visibility/spatialIndex.js +198 -0
  197. package/dist/visibility/visibilitySystem.d.ts +90 -0
  198. package/dist/visibility/visibilitySystem.js +257 -0
  199. package/dist/world/buildings.d.ts +6 -0
  200. package/dist/world/buildings.js +152 -0
  201. package/dist/world/carve.d.ts +7 -2
  202. package/dist/world/carve.js +17 -7
  203. package/dist/world/cellStates.d.ts +45 -0
  204. package/dist/world/cellStates.js +103 -0
  205. package/dist/world/environmentSummary.d.ts +10 -2
  206. package/dist/world/environmentSummary.js +11 -2
  207. package/dist/world/features.d.ts +56 -8
  208. package/dist/world/features.js +24 -3
  209. package/dist/world/gridInstances.d.ts +7 -8
  210. package/dist/world/gridInstances.js +145 -7
  211. package/dist/world/lod.d.ts +17 -0
  212. package/dist/world/lod.js +66 -0
  213. package/dist/world/mapLayers.d.ts +60 -0
  214. package/dist/world/mapLayers.js +44 -0
  215. package/dist/world/massing.d.ts +45 -0
  216. package/dist/world/massing.js +581 -0
  217. package/dist/world/minimap.d.ts +13 -1
  218. package/dist/world/minimap.js +21 -2
  219. package/dist/world/terrain.d.ts +42 -3
  220. package/dist/world/terrain.js +197 -16
  221. package/dist/world/water.d.ts +7 -0
  222. package/dist/world/water.js +19 -4
  223. package/dist/world/weather.d.ts +3 -18
  224. package/dist/world/weather.js +4 -17
  225. package/dist/world/windZones.d.ts +47 -0
  226. package/dist/world/windZones.js +59 -0
  227. package/llms.txt +2585 -1704
  228. package/package.json +12 -2
@@ -0,0 +1,20 @@
1
+ export interface ResourcePoolConfig {
2
+ max: number;
3
+ initial?: number;
4
+ regenPerSecond?: number;
5
+ decayPerSecond?: number;
6
+ }
7
+ export interface ResourcePool {
8
+ current(): number;
9
+ max(): number;
10
+ setMax(value: number): void;
11
+ fraction(): number;
12
+ canSpend(amount: number): boolean;
13
+ spend(amount: number): boolean;
14
+ gain(amount: number): number;
15
+ set(value: number): void;
16
+ tick(dtSeconds: number): void;
17
+ isFull(): boolean;
18
+ isEmpty(): boolean;
19
+ }
20
+ export declare function createResourcePool(config: ResourcePoolConfig): ResourcePool;
@@ -0,0 +1,55 @@
1
+ export function createResourcePool(config) {
2
+ let max = Math.max(0, config.max);
3
+ const regenPerSecond = Math.max(0, config.regenPerSecond ?? 0);
4
+ const decayPerSecond = Math.max(0, config.decayPerSecond ?? 0);
5
+ let current = clamp(config.initial ?? max, max);
6
+ function clamp(value, ceiling) {
7
+ return Math.max(0, Math.min(ceiling, value));
8
+ }
9
+ return {
10
+ current() {
11
+ return current;
12
+ },
13
+ max() {
14
+ return max;
15
+ },
16
+ setMax(value) {
17
+ max = Math.max(0, value);
18
+ current = clamp(current, max);
19
+ },
20
+ fraction() {
21
+ return max <= 0 ? 0 : current / max;
22
+ },
23
+ canSpend(amount) {
24
+ return amount <= 0 || amount <= current;
25
+ },
26
+ spend(amount) {
27
+ if (amount <= 0)
28
+ return true;
29
+ if (amount > current)
30
+ return false;
31
+ current = clamp(current - amount, max);
32
+ return true;
33
+ },
34
+ gain(amount) {
35
+ if (amount > 0)
36
+ current = clamp(current + amount, max);
37
+ return current;
38
+ },
39
+ set(value) {
40
+ current = clamp(value, max);
41
+ },
42
+ tick(dtSeconds) {
43
+ if (dtSeconds <= 0)
44
+ return;
45
+ const net = (regenPerSecond - decayPerSecond) * dtSeconds;
46
+ current = clamp(current + net, max);
47
+ },
48
+ isFull() {
49
+ return current >= max;
50
+ },
51
+ isEmpty() {
52
+ return current <= 0;
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,40 @@
1
+ import type { EntityPosition } from "../scene/entityStore.js";
2
+ import type { Aim } from "../scene/spatial.js";
3
+ /**
4
+ * How a shot's world-space origin (and optional direction) is resolved before prediction/settlement.
5
+ * - `legacy` — `aim.origin` when present, else the shooter's entity position (pre-#431 default).
6
+ * - `entity` — always the shooter's entity position.
7
+ * - `entityOffset` / `muzzle` — entity-local offset rotated by the shooter's yaw (muzzle on a weapon model).
8
+ * - `camera` — explicit camera/reticle world origin (and optional direction override).
9
+ * - `world` — absolute world origin.
10
+ */
11
+ export type ShotOriginPolicy = {
12
+ kind: "legacy";
13
+ } | {
14
+ kind: "entity";
15
+ } | {
16
+ kind: "entityOffset";
17
+ offset: EntityPosition;
18
+ } | {
19
+ kind: "muzzle";
20
+ offset?: EntityPosition;
21
+ } | {
22
+ kind: "camera";
23
+ origin: EntityPosition;
24
+ direction?: EntityPosition;
25
+ } | {
26
+ kind: "world";
27
+ origin: EntityPosition;
28
+ direction?: EntityPosition;
29
+ };
30
+ export interface ResolvedShot {
31
+ origin: EntityPosition;
32
+ direction: EntityPosition;
33
+ }
34
+ export interface ShotOriginDeps {
35
+ positionOf(instanceId: string): EntityPosition | undefined;
36
+ rotationYOf?(instanceId: string): number | undefined;
37
+ }
38
+ export declare function aimDirection(aim: Aim): EntityPosition | null;
39
+ export declare function aimSpreadDeg(aim: Aim): number;
40
+ export declare function resolveShot(deps: ShotOriginDeps, from: string, aim: Aim, policy?: ShotOriginPolicy): ResolvedShot | null;
@@ -0,0 +1,57 @@
1
+ import { worldOffset } from "../scene/colliders.js";
2
+ const DEFAULT_MUZZLE_OFFSET = [0, 1.4, 0.35];
3
+ function normalize(vector) {
4
+ const length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
5
+ if (length === 0)
6
+ return null;
7
+ return [vector[0] / length, vector[1] / length, vector[2] / length];
8
+ }
9
+ export function aimDirection(aim) {
10
+ if ("origin" in aim)
11
+ return normalize(aim.direction);
12
+ const cosPitch = Math.cos(aim.pitch);
13
+ return [Math.sin(aim.yaw) * cosPitch, Math.sin(aim.pitch), Math.cos(aim.yaw) * cosPitch];
14
+ }
15
+ export function aimSpreadDeg(aim) {
16
+ return "origin" in aim ? 0 : aim.spread ?? 0;
17
+ }
18
+ export function resolveShot(deps, from, aim, policy = { kind: "legacy" }) {
19
+ const aimDir = aimDirection(aim);
20
+ if (aimDir === null && policy.kind !== "camera" && policy.kind !== "world")
21
+ return null;
22
+ switch (policy.kind) {
23
+ case "legacy": {
24
+ const origin = "origin" in aim ? aim.origin : deps.positionOf(from);
25
+ if (origin === undefined || aimDir === null)
26
+ return null;
27
+ return { origin, direction: aimDir };
28
+ }
29
+ case "entity": {
30
+ const origin = deps.positionOf(from);
31
+ if (origin === undefined || aimDir === null)
32
+ return null;
33
+ return { origin, direction: aimDir };
34
+ }
35
+ case "entityOffset":
36
+ case "muzzle": {
37
+ const position = deps.positionOf(from);
38
+ if (position === undefined || aimDir === null)
39
+ return null;
40
+ const rotationY = deps.rotationYOf?.(from) ?? 0;
41
+ const offset = policy.kind === "muzzle" ? (policy.offset ?? DEFAULT_MUZZLE_OFFSET) : policy.offset;
42
+ return { origin: worldOffset(offset, position, rotationY), direction: aimDir };
43
+ }
44
+ case "camera": {
45
+ const direction = policy.direction !== undefined ? normalize(policy.direction) : aimDir;
46
+ if (direction === null)
47
+ return null;
48
+ return { origin: policy.origin, direction };
49
+ }
50
+ case "world": {
51
+ const direction = policy.direction !== undefined ? normalize(policy.direction) : aimDir;
52
+ if (direction === null)
53
+ return null;
54
+ return { origin: policy.origin, direction };
55
+ }
56
+ }
57
+ }
@@ -24,6 +24,31 @@ export interface TelegraphConfig {
24
24
  kind?: string;
25
25
  }
26
26
  export declare function telegraphProgress(windupMs: number, startedAtMs: number, nowMs: number): number;
27
+ export interface HazardCycleConfig {
28
+ windupMs: number;
29
+ activeMs: number;
30
+ /** Rest between one activation's end and the next windup; default `0` (back-to-back). */
31
+ cooldownMs?: number;
32
+ /** Shifts the whole cycle — staggers identical hazards. */
33
+ offsetMs?: number;
34
+ }
35
+ export type HazardPhase = "windup" | "active" | "cooldown";
36
+ export interface HazardCycleSample {
37
+ phase: HazardPhase;
38
+ /** Elapsed fraction of the current phase in `[0, 1]` — the windup fraction is the decal fill. */
39
+ fraction: number;
40
+ remainingMs: number;
41
+ /** How many full cycles have completed before this one. */
42
+ cycleIndex: number;
43
+ }
44
+ /**
45
+ * Deterministic repeating hazard timing — windup → active → cooldown as a pure function of absolute
46
+ * time, for recurring flame vents, crushers, lightning rings. Sample `hazardCycleAt` each tick: draw
47
+ * the telegraph during `windup`, apply the effect during `active`, rest through `cooldown`.
48
+ */
49
+ export declare function hazardCycleAt(config: HazardCycleConfig, nowMs: number): HazardCycleSample;
50
+ /** Absolute time the hazard's next `active` phase begins at or after `nowMs` — the countdown seam. */
51
+ export declare function nextHazardActiveAt(config: HazardCycleConfig, nowMs: number): number;
27
52
  export declare function telegraphFired(windupMs: number, startedAtMs: number, nowMs: number): boolean;
28
53
  export declare function telegraphTurnProgress(config: TelegraphConfig, startedTurn: number, currentTurn: number): number;
29
54
  export declare function telegraphFiredAtTurn(config: TelegraphConfig, startedTurn: number, currentTurn: number): boolean;
@@ -3,6 +3,53 @@ export function telegraphProgress(windupMs, startedAtMs, nowMs) {
3
3
  return 1;
4
4
  return Math.max(0, Math.min(1, (nowMs - startedAtMs) / windupMs));
5
5
  }
6
+ /**
7
+ * Deterministic repeating hazard timing — windup → active → cooldown as a pure function of absolute
8
+ * time, for recurring flame vents, crushers, lightning rings. Sample `hazardCycleAt` each tick: draw
9
+ * the telegraph during `windup`, apply the effect during `active`, rest through `cooldown`.
10
+ */
11
+ export function hazardCycleAt(config, nowMs) {
12
+ const cooldownMs = Math.max(0, config.cooldownMs ?? 0);
13
+ const cycleMs = config.windupMs + config.activeMs + cooldownMs;
14
+ if (cycleMs <= 0)
15
+ return { phase: "active", fraction: 1, remainingMs: 0, cycleIndex: 0 };
16
+ const shifted = nowMs + (config.offsetMs ?? 0);
17
+ const local = ((shifted % cycleMs) + cycleMs) % cycleMs;
18
+ const cycleIndex = Math.floor((shifted - local) / cycleMs);
19
+ if (local < config.windupMs) {
20
+ return {
21
+ phase: "windup",
22
+ fraction: config.windupMs <= 0 ? 1 : local / config.windupMs,
23
+ remainingMs: config.windupMs - local,
24
+ cycleIndex,
25
+ };
26
+ }
27
+ const intoActive = local - config.windupMs;
28
+ if (intoActive < config.activeMs) {
29
+ return {
30
+ phase: "active",
31
+ fraction: config.activeMs <= 0 ? 1 : intoActive / config.activeMs,
32
+ remainingMs: config.activeMs - intoActive,
33
+ cycleIndex,
34
+ };
35
+ }
36
+ const intoCooldown = intoActive - config.activeMs;
37
+ return {
38
+ phase: "cooldown",
39
+ fraction: cooldownMs <= 0 ? 1 : intoCooldown / cooldownMs,
40
+ remainingMs: cooldownMs - intoCooldown,
41
+ cycleIndex,
42
+ };
43
+ }
44
+ /** Absolute time the hazard's next `active` phase begins at or after `nowMs` — the countdown seam. */
45
+ export function nextHazardActiveAt(config, nowMs) {
46
+ const sample = hazardCycleAt(config, nowMs);
47
+ if (sample.phase === "windup")
48
+ return nowMs + sample.remainingMs;
49
+ const cooldownMs = Math.max(0, config.cooldownMs ?? 0);
50
+ const restMs = sample.phase === "active" ? sample.remainingMs + cooldownMs : sample.remainingMs;
51
+ return nowMs + restMs + config.windupMs;
52
+ }
6
53
  export function telegraphFired(windupMs, startedAtMs, nowMs) {
7
54
  return nowMs - startedAtMs >= windupMs;
8
55
  }
@@ -19,6 +19,9 @@ export function stationSatisfied(recipe, context) {
19
19
  return true;
20
20
  const range = recipe.stationRange !== undefined && recipe.stationRange > 0 ? recipe.stationRange : Infinity;
21
21
  const origin = context.origin;
22
+ if (range !== Infinity && origin === undefined) {
23
+ throw new Error(`recipe "${recipe.id}" sets stationRange=${recipe.stationRange} but CraftContext.origin is missing`);
24
+ }
22
25
  const stations = context.stations ?? [];
23
26
  for (const station of stations) {
24
27
  if (station.catalogId !== recipe.station)
@@ -37,7 +40,7 @@ function lockedRequirements(recipe, context) {
37
40
  return [];
38
41
  const unlocked = context.unlocked;
39
42
  if (unlocked === undefined)
40
- return [];
43
+ return recipe.requires;
41
44
  return recipe.requires.filter((id) => !unlocked(id));
42
45
  }
43
46
  function outputsFit(state, layout, traits, recipe) {
@@ -1,14 +1,7 @@
1
1
  import { type ChangeSignal } from "../store/changeSignal.js";
2
- export type DevtoolsControlKind = "slider" | "toggle" | "color" | "select" | "text";
3
- export interface TunableOptions<T> {
4
- min?: number;
5
- max?: number;
6
- step?: number;
7
- options?: readonly T[];
8
- label?: string;
9
- group?: string;
10
- onChange?: (value: T) => void;
11
- }
2
+ import { type AngleUnit, type DevtoolsControlKind, type DevtoolsOverrides, type DiscoverySkip, type OverrideApplyDiagnostic, type ScanMeta, type TunableChoice, type TunableOptions } from "./tunableSchema.js";
3
+ export type { AngleUnit, DevtoolsControlKind, DevtoolsOverrides, DiscoverySkip, OverrideApplyDiagnostic, TunableChoice, TunableInterval, TunableOptions, TunableVec2, TunableVec3, TunableVec4, ScanMeta, ScanFieldMeta, NormalizedColor, } from "./tunableSchema.js";
4
+ export { CONTROL_SCHEMA_VERSION, OVERRIDES_FORMAT_VERSION, convertAngle, escapePathSegment, formatColor, joinTunablePath, normalizeAngle, normalizeColorValue, parseColor, parseOverridesPayload, splitTunablePath, unescapePathSegment, validateControlValue, } from "./tunableSchema.js";
12
5
  export interface DevtoolsControl {
13
6
  readonly name: string;
14
7
  readonly kind: DevtoolsControlKind;
@@ -19,8 +12,19 @@ export interface DevtoolsControl {
19
12
  readonly max?: number;
20
13
  readonly step?: number;
21
14
  readonly options?: readonly unknown[];
15
+ readonly choices?: readonly TunableChoice[];
16
+ readonly unit?: AngleUnit;
17
+ readonly displayUnit?: AngleUnit;
18
+ readonly wrap?: boolean;
19
+ readonly integer?: boolean;
20
+ readonly axisLabels?: readonly string[];
21
+ readonly axisMin?: readonly number[];
22
+ readonly axisMax?: readonly number[];
23
+ readonly axisStep?: readonly number[];
24
+ readonly hasAlpha?: boolean;
25
+ readonly schemaVersion: number;
22
26
  read(): unknown;
23
- write(value: unknown): void;
27
+ write(value: unknown): boolean;
24
28
  reset(): void;
25
29
  }
26
30
  export interface Tunable<T> {
@@ -28,10 +32,15 @@ export interface Tunable<T> {
28
32
  readonly kind: DevtoolsControlKind;
29
33
  readonly initial: T;
30
34
  readonly value: T;
31
- set(value: T): void;
35
+ set(value: T): boolean;
32
36
  reset(): void;
33
37
  subscribe(listener: (value: T) => void): () => void;
34
38
  }
39
+ export interface OverrideApplyResult {
40
+ applied: number;
41
+ skipped: readonly OverrideApplyDiagnostic[];
42
+ diagnostics: readonly string[];
43
+ }
35
44
  export interface FrameStats {
36
45
  fps: number;
37
46
  avgFrameMs: number;
@@ -39,9 +48,36 @@ export interface FrameStats {
39
48
  maxFrameMs: number;
40
49
  avgSimMs: number;
41
50
  maxSimMs: number;
51
+ avgOutsideMs: number;
52
+ maxOutsideMs: number;
42
53
  longFrames: number;
43
54
  samples: number;
44
55
  recentFrameMs: readonly number[];
56
+ phases: readonly PhaseStats[];
57
+ }
58
+ export interface PhaseStats {
59
+ name: string;
60
+ avgMs: number;
61
+ maxMs: number;
62
+ lastMs: number;
63
+ samples: number;
64
+ pctOfSim: number;
65
+ }
66
+ export interface LongFrameEvent {
67
+ at: number;
68
+ frameMs: number;
69
+ simMs: number;
70
+ outsideMs: number;
71
+ phases: readonly {
72
+ name: string;
73
+ ms: number;
74
+ pct: number;
75
+ }[];
76
+ topPhase: string | null;
77
+ culprit: string;
78
+ reason: string;
79
+ probes: Record<string, unknown>;
80
+ render: RenderSample | null;
45
81
  }
46
82
  export interface RenderSample {
47
83
  drawCalls: number;
@@ -76,15 +112,12 @@ export interface TunableAccessor {
76
112
  get(): unknown;
77
113
  set(value: unknown): void;
78
114
  }
79
- export interface DevtoolsOverrides {
80
- enabled: string[];
81
- values: Record<string, unknown>;
82
- }
83
115
  export interface DevtoolsSnapshot {
84
116
  at: number;
85
117
  frame: FrameStats | null;
86
118
  render: RenderSample | null;
87
119
  latency: LatencyStats | null;
120
+ longFrames: readonly LongFrameEvent[];
88
121
  logs: readonly DevtoolsLogEntry[];
89
122
  probes: Record<string, unknown>;
90
123
  controls: readonly {
@@ -99,14 +132,26 @@ export interface DevtoolsSnapshot {
99
132
  value: unknown;
100
133
  enabled: boolean;
101
134
  }[];
135
+ skipped: readonly DiscoverySkip[];
136
+ }
137
+ export interface FrameRecordSample {
138
+ frameMs: number;
139
+ simMs: number;
140
+ phases?: Readonly<Record<string, number>>;
102
141
  }
103
142
  export interface Devtools {
104
143
  frame: {
105
- record(sample: {
106
- frameMs: number;
107
- simMs: number;
108
- }): void;
144
+ record(sample: FrameRecordSample): void;
109
145
  stats(): FrameStats | null;
146
+ longFrames(): readonly LongFrameEvent[];
147
+ clearLongFrames(): void;
148
+ };
149
+ profile: {
150
+ begin(name: string): () => void;
151
+ measure<T>(name: string, fn: () => T): T;
152
+ add(name: string, ms: number): void;
153
+ current(): Record<string, number>;
154
+ reset(): void;
110
155
  };
111
156
  render: {
112
157
  record(sample: RenderSample): void;
@@ -130,17 +175,18 @@ export interface Devtools {
130
175
  resetAll(): void;
131
176
  };
132
177
  discover: {
133
- bind(table: string, key: string, accessor: TunableAccessor): void;
134
- scanTable(table: string, candidate: unknown): number;
135
- scanModule(exports: Record<string, unknown>): number;
178
+ bind(table: string, key: string, accessor: TunableAccessor, options?: TunableOptions): void;
179
+ scanTable(table: string, candidate: unknown, meta?: ScanMeta): number;
180
+ scanModule(exports: Record<string, unknown>, meta?: ScanMeta): number;
136
181
  list(): readonly DiscoveredEntry[];
182
+ skipped(): readonly DiscoverySkip[];
137
183
  enable(id: string): void;
138
184
  disable(id: string): void;
139
185
  clear(): void;
140
186
  };
141
187
  overrides: {
142
188
  export(): DevtoolsOverrides;
143
- apply(overrides: DevtoolsOverrides): void;
189
+ apply(overrides: DevtoolsOverrides | unknown): OverrideApplyResult;
144
190
  };
145
191
  probes: {
146
192
  register(name: string, read: () => unknown): () => void;
@@ -149,9 +195,11 @@ export interface Devtools {
149
195
  signal: ChangeSignal;
150
196
  snapshot(): DevtoolsSnapshot;
151
197
  }
198
+ export declare const LONG_FRAME_MS = 33.4;
152
199
  export declare function formatLogMessage(args: readonly unknown[]): string;
153
200
  export declare function createDevtools(): Devtools;
154
201
  export declare const devtools: Devtools;
155
202
  export declare function tunable<T>(name: string, initial: T, options?: TunableOptions<T>): Tunable<T>;
156
203
  export declare function snapshotDevtools(): DevtoolsSnapshot;
204
+ export declare function measureProfile<T>(name: string, fn: () => T): T;
157
205
  export declare function instrumentLatency<T extends object>(target: T, methods: readonly (keyof T)[], record?: (ms: number) => void): T;