@jgengine/core 0.8.0 → 0.10.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 (303) hide show
  1. package/CHANGELOG.md +36 -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.d.ts +13 -0
  11. package/dist/ai/spawnDirector.js +41 -17
  12. package/dist/ai/spawnPoint.d.ts +5 -0
  13. package/dist/ai/spawnPoint.js +5 -0
  14. package/dist/ai/threat.d.ts +2 -0
  15. package/dist/ai/threat.js +34 -0
  16. package/dist/audio/audioFalloff.d.ts +5 -2
  17. package/dist/audio/music.d.ts +53 -0
  18. package/dist/audio/music.js +34 -0
  19. package/dist/audio/synth.d.ts +60 -0
  20. package/dist/audio/synth.js +8 -0
  21. package/dist/authoring.d.ts +14 -0
  22. package/dist/authoring.js +14 -0
  23. package/dist/cartridge/runtime.d.ts +15 -0
  24. package/dist/cartridge/runtime.js +556 -0
  25. package/dist/cartridge/spec.d.ts +188 -0
  26. package/dist/cartridge/spec.js +19 -0
  27. package/dist/cartridge/validate.d.ts +2 -0
  28. package/dist/cartridge/validate.js +78 -0
  29. package/dist/combat/abilityKit.d.ts +17 -2
  30. package/dist/combat/abilityKit.js +62 -13
  31. package/dist/combat/castRunner.d.ts +40 -0
  32. package/dist/combat/castRunner.js +56 -0
  33. package/dist/combat/comboPoints.d.ts +14 -0
  34. package/dist/combat/comboPoints.js +52 -0
  35. package/dist/combat/defensiveWindow.d.ts +1 -1
  36. package/dist/combat/defensiveWindow.js +5 -3
  37. package/dist/combat/dotField.d.ts +43 -0
  38. package/dist/combat/dotField.js +48 -0
  39. package/dist/combat/projectiles.d.ts +33 -12
  40. package/dist/combat/projectiles.js +214 -129
  41. package/dist/combat/regenShield.d.ts +35 -0
  42. package/dist/combat/regenShield.js +45 -0
  43. package/dist/combat/resistance.d.ts +16 -4
  44. package/dist/combat/resistance.js +43 -7
  45. package/dist/combat/resourcePool.d.ts +20 -0
  46. package/dist/combat/resourcePool.js +55 -0
  47. package/dist/combat/shotOrigin.d.ts +52 -0
  48. package/dist/combat/shotOrigin.js +86 -0
  49. package/dist/combat/telegraph.d.ts +25 -0
  50. package/dist/combat/telegraph.js +47 -0
  51. package/dist/crafting/recipe.js +4 -1
  52. package/dist/devtools/devtools.d.ts +72 -24
  53. package/dist/devtools/devtools.js +449 -101
  54. package/dist/devtools/transformTunables.js +3 -3
  55. package/dist/devtools/tunableSchema.d.ts +140 -0
  56. package/dist/devtools/tunableSchema.js +476 -0
  57. package/dist/editor/commands.d.ts +65 -0
  58. package/dist/editor/commands.js +193 -0
  59. package/dist/editor/document.d.ts +40 -0
  60. package/dist/editor/document.js +153 -0
  61. package/dist/editor/index.d.ts +30 -0
  62. package/dist/editor/index.js +6 -0
  63. package/dist/editor/types.d.ts +69 -0
  64. package/dist/editor/types.js +29 -0
  65. package/dist/faction/factions.d.ts +33 -0
  66. package/dist/faction/factions.js +76 -0
  67. package/dist/faction/reputation.d.ts +32 -0
  68. package/dist/faction/reputation.js +82 -0
  69. package/dist/game/chat.d.ts +2 -0
  70. package/dist/game/chat.js +9 -1
  71. package/dist/game/chatFilter.d.ts +15 -0
  72. package/dist/game/chatFilter.js +55 -0
  73. package/dist/game/connectedPlayers.d.ts +28 -0
  74. package/dist/game/connectedPlayers.js +21 -0
  75. package/dist/game/controlGate.d.ts +4 -0
  76. package/dist/game/controlGate.js +7 -0
  77. package/dist/game/defineGame.d.ts +44 -1
  78. package/dist/game/defineGame.js +1 -0
  79. package/dist/game/events.d.ts +25 -0
  80. package/dist/game/gamePhase.d.ts +20 -0
  81. package/dist/game/gamePhase.js +21 -0
  82. package/dist/game/keyValueStore.d.ts +25 -0
  83. package/dist/game/keyValueStore.js +61 -0
  84. package/dist/game/levelSequence.d.ts +24 -2
  85. package/dist/game/levelSequence.js +51 -2
  86. package/dist/game/lootTable.d.ts +14 -1
  87. package/dist/game/lootTable.js +28 -7
  88. package/dist/game/modelAnimation.d.ts +8 -0
  89. package/dist/game/modelAnimation.js +18 -0
  90. package/dist/game/playableGame.d.ts +116 -4
  91. package/dist/game/playableGame.js +1 -1
  92. package/dist/game/quest.d.ts +7 -3
  93. package/dist/game/quest.js +17 -8
  94. package/dist/game/race.d.ts +39 -1
  95. package/dist/game/race.js +96 -11
  96. package/dist/game/recordBook.d.ts +27 -0
  97. package/dist/game/recordBook.js +86 -0
  98. package/dist/game/talents.d.ts +47 -0
  99. package/dist/game/talents.js +140 -0
  100. package/dist/index.d.ts +8 -0
  101. package/dist/index.js +8 -0
  102. package/dist/input/actionBindings.d.ts +2 -0
  103. package/dist/input/actionBindings.js +1 -0
  104. package/dist/input/axisInput.d.ts +33 -3
  105. package/dist/input/axisInput.js +60 -4
  106. package/dist/input/bindingOverrides.d.ts +25 -0
  107. package/dist/input/bindingOverrides.js +95 -0
  108. package/dist/input/pointerAxis.d.ts +30 -0
  109. package/dist/input/pointerAxis.js +31 -0
  110. package/dist/input/touchScheme.d.ts +6 -0
  111. package/dist/input/touchScheme.js +45 -7
  112. package/dist/interaction/contextMenu.d.ts +1 -0
  113. package/dist/interaction/contextMenu.js +1 -0
  114. package/dist/interaction/proximityPrompt.d.ts +3 -1
  115. package/dist/interaction/proximityPrompt.js +3 -3
  116. package/dist/meta/changelog.d.ts +1 -1
  117. package/dist/meta/changelog.js +44 -1
  118. package/dist/movement/dash.d.ts +3 -0
  119. package/dist/movement/dash.js +19 -5
  120. package/dist/movement/glideModel.d.ts +44 -0
  121. package/dist/movement/glideModel.js +54 -0
  122. package/dist/movement/grappleSwing.d.ts +34 -0
  123. package/dist/movement/grappleSwing.js +56 -0
  124. package/dist/movement/leaderTrail.d.ts +29 -0
  125. package/dist/movement/leaderTrail.js +67 -0
  126. package/dist/movement/movementModel.d.ts +4 -0
  127. package/dist/movement/movementModel.js +13 -0
  128. package/dist/movement/playerMovement.d.ts +41 -0
  129. package/dist/movement/playerMovement.js +181 -0
  130. package/dist/movement/steering.d.ts +27 -0
  131. package/dist/movement/steering.js +40 -0
  132. package/dist/multiplayer/feedWriteGate.d.ts +10 -0
  133. package/dist/multiplayer/feedWriteGate.js +12 -0
  134. package/dist/multiplayer/presenceModel.d.ts +14 -2
  135. package/dist/multiplayer/presenceModel.js +15 -3
  136. package/dist/nav/corridors.d.ts +30 -0
  137. package/dist/nav/corridors.js +69 -0
  138. package/dist/nav/navGrid.js +30 -4
  139. package/dist/nav/railGraph.d.ts +66 -0
  140. package/dist/nav/railGraph.js +129 -0
  141. package/dist/nav/timetable.d.ts +41 -0
  142. package/dist/nav/timetable.js +94 -0
  143. package/dist/physics/buoyancy.d.ts +6 -0
  144. package/dist/physics/buoyancy.js +14 -0
  145. package/dist/physics/flowTube.d.ts +32 -0
  146. package/dist/physics/flowTube.js +64 -0
  147. package/dist/physics/forceVolume.d.ts +27 -0
  148. package/dist/physics/forceVolume.js +38 -0
  149. package/dist/physics/kinematicVehicle.d.ts +52 -0
  150. package/dist/physics/kinematicVehicle.js +81 -0
  151. package/dist/physics/physicsWorld.d.ts +13 -6
  152. package/dist/physics/physicsWorld.js +169 -40
  153. package/dist/physics/ragdoll.js +1 -1
  154. package/dist/physics/vehicleBody.js +2 -1
  155. package/dist/random/rng.d.ts +2 -0
  156. package/dist/random/rng.js +2 -0
  157. package/dist/random/seedLink.d.ts +5 -0
  158. package/dist/random/seedLink.js +35 -0
  159. package/dist/render/postProcessing.d.ts +56 -0
  160. package/dist/render/postProcessing.js +1 -0
  161. package/dist/runtime/adapter.d.ts +19 -0
  162. package/dist/runtime/adapter.js +11 -5
  163. package/dist/runtime/cameraDirector.d.ts +6 -1
  164. package/dist/runtime/cameraDirector.js +6 -0
  165. package/dist/runtime/commandRunner.d.ts +2 -2
  166. package/dist/runtime/commandRunner.js +2 -2
  167. package/dist/runtime/gameContext.d.ts +72 -8
  168. package/dist/runtime/gameContext.js +374 -61
  169. package/dist/runtime/gameRuntime.d.ts +5 -1
  170. package/dist/runtime/gameRuntime.js +13 -12
  171. package/dist/runtime/hostPersistence.d.ts +9 -1
  172. package/dist/runtime/hostPersistence.js +23 -8
  173. package/dist/runtime/hostedGameRunner.d.ts +49 -0
  174. package/dist/runtime/hostedGameRunner.js +62 -0
  175. package/dist/runtime/hostedWorldSession.d.ts +65 -0
  176. package/dist/runtime/hostedWorldSession.js +57 -0
  177. package/dist/runtime/inputSnapshot.d.ts +18 -0
  178. package/dist/runtime/inputSnapshot.js +7 -0
  179. package/dist/runtime/motionIntents.d.ts +16 -6
  180. package/dist/runtime/motionIntents.js +31 -2
  181. package/dist/runtime/snapshot.d.ts +1 -0
  182. package/dist/runtime/snapshot.js +1 -1
  183. package/dist/runtime/visibility.d.ts +74 -0
  184. package/dist/runtime/visibility.js +127 -0
  185. package/dist/runtime/worldChannel.d.ts +58 -0
  186. package/dist/runtime/worldChannel.js +69 -0
  187. package/dist/runtime/worldMirror.d.ts +24 -0
  188. package/dist/runtime/worldMirror.js +31 -0
  189. package/dist/runtime/worldReplication.d.ts +46 -0
  190. package/dist/runtime/worldReplication.js +185 -0
  191. package/dist/runtime/worldSnapshot.d.ts +17 -0
  192. package/dist/runtime/worldSnapshot.js +15 -0
  193. package/dist/scene/assetCatalog.d.ts +1 -0
  194. package/dist/scene/colliders.d.ts +50 -0
  195. package/dist/scene/colliders.js +101 -0
  196. package/dist/scene/entityStats.d.ts +4 -0
  197. package/dist/scene/entityStats.js +21 -0
  198. package/dist/scene/entityStore.d.ts +51 -5
  199. package/dist/scene/entityStore.js +119 -12
  200. package/dist/scene/objectQuery.d.ts +13 -2
  201. package/dist/scene/objectQuery.js +34 -2
  202. package/dist/scene/objectStore.d.ts +2 -0
  203. package/dist/scene/objectStore.js +31 -12
  204. package/dist/scene/sceneRaycast.d.ts +80 -0
  205. package/dist/scene/sceneRaycast.js +284 -0
  206. package/dist/scene/spatial.d.ts +11 -16
  207. package/dist/scene/spatial.js +99 -38
  208. package/dist/sensor/recordingBuffer.d.ts +6 -0
  209. package/dist/sensor/recordingBuffer.js +7 -0
  210. package/dist/sensor/replayLoop.d.ts +39 -0
  211. package/dist/sensor/replayLoop.js +64 -0
  212. package/dist/sensor/visionCone.d.ts +31 -0
  213. package/dist/sensor/visionCone.js +72 -0
  214. package/dist/session/extraction.d.ts +2 -2
  215. package/dist/session/extraction.js +13 -2
  216. package/dist/settings/settingsModel.d.ts +97 -0
  217. package/dist/settings/settingsModel.js +100 -0
  218. package/dist/stats/statModifiers.d.ts +5 -2
  219. package/dist/stats/statModifiers.js +32 -14
  220. package/dist/store/observableKeyedStore.d.ts +4 -0
  221. package/dist/store/observableKeyedStore.js +17 -2
  222. package/dist/survival/decayMeter.d.ts +1 -1
  223. package/dist/survival/decayMeter.js +0 -1
  224. package/dist/tactics/fallingGrid.d.ts +2 -2
  225. package/dist/tactics/fallingGrid.js +2 -2
  226. package/dist/time/beatClock.d.ts +18 -0
  227. package/dist/time/beatClock.js +24 -0
  228. package/dist/time/idleProgress.d.ts +29 -0
  229. package/dist/time/idleProgress.js +31 -0
  230. package/dist/time/serverTick.d.ts +7 -1
  231. package/dist/time/serverTick.js +27 -4
  232. package/dist/time/stateSchedule.d.ts +57 -0
  233. package/dist/time/stateSchedule.js +121 -0
  234. package/dist/turn/commit.d.ts +2 -2
  235. package/dist/turn/commit.js +2 -2
  236. package/dist/ui/gameLayout.d.ts +125 -0
  237. package/dist/ui/gameLayout.js +129 -0
  238. package/dist/ui/hudLayout.d.ts +58 -0
  239. package/dist/ui/hudLayout.js +207 -0
  240. package/dist/ui/hudScale.d.ts +44 -0
  241. package/dist/ui/hudScale.js +55 -0
  242. package/dist/ui/orientation.d.ts +39 -0
  243. package/dist/ui/orientation.js +49 -0
  244. package/dist/ui/swingTimer.d.ts +42 -0
  245. package/dist/ui/swingTimer.js +38 -0
  246. package/dist/visibility/assetStreaming.d.ts +66 -0
  247. package/dist/visibility/assetStreaming.js +201 -0
  248. package/dist/visibility/bounds.d.ts +66 -0
  249. package/dist/visibility/bounds.js +113 -0
  250. package/dist/visibility/camera.d.ts +21 -0
  251. package/dist/visibility/camera.js +3 -0
  252. package/dist/visibility/config.d.ts +21 -0
  253. package/dist/visibility/config.js +1 -0
  254. package/dist/visibility/diagnostics.d.ts +26 -0
  255. package/dist/visibility/diagnostics.js +36 -0
  256. package/dist/visibility/distance.d.ts +11 -0
  257. package/dist/visibility/distance.js +30 -0
  258. package/dist/visibility/frustum.d.ts +47 -0
  259. package/dist/visibility/frustum.js +186 -0
  260. package/dist/visibility/occlusion.d.ts +36 -0
  261. package/dist/visibility/occlusion.js +45 -0
  262. package/dist/visibility/settings.d.ts +75 -0
  263. package/dist/visibility/settings.js +49 -0
  264. package/dist/visibility/simulationCulling.d.ts +34 -0
  265. package/dist/visibility/simulationCulling.js +36 -0
  266. package/dist/visibility/spatialIndex.d.ts +45 -0
  267. package/dist/visibility/spatialIndex.js +212 -0
  268. package/dist/visibility/visibilitySystem.d.ts +90 -0
  269. package/dist/visibility/visibilitySystem.js +257 -0
  270. package/dist/world/buildings.d.ts +6 -0
  271. package/dist/world/buildings.js +152 -0
  272. package/dist/world/carve.d.ts +7 -2
  273. package/dist/world/carve.js +17 -7
  274. package/dist/world/cellStates.d.ts +45 -0
  275. package/dist/world/cellStates.js +103 -0
  276. package/dist/world/environmentSummary.d.ts +21 -4
  277. package/dist/world/environmentSummary.js +22 -3
  278. package/dist/world/features.d.ts +185 -8
  279. package/dist/world/features.js +64 -6
  280. package/dist/world/gridInstances.d.ts +7 -8
  281. package/dist/world/gridInstances.js +145 -7
  282. package/dist/world/lod.d.ts +17 -0
  283. package/dist/world/lod.js +66 -0
  284. package/dist/world/mapLayers.d.ts +60 -0
  285. package/dist/world/mapLayers.js +44 -0
  286. package/dist/world/massing.d.ts +45 -0
  287. package/dist/world/massing.js +581 -0
  288. package/dist/world/minimap.d.ts +13 -1
  289. package/dist/world/minimap.js +21 -2
  290. package/dist/world/roads.d.ts +47 -0
  291. package/dist/world/roads.js +146 -0
  292. package/dist/world/streets.d.ts +80 -0
  293. package/dist/world/streets.js +144 -0
  294. package/dist/world/terrain.d.ts +69 -3
  295. package/dist/world/terrain.js +309 -16
  296. package/dist/world/water.d.ts +7 -0
  297. package/dist/world/water.js +19 -4
  298. package/dist/world/weather.d.ts +3 -18
  299. package/dist/world/weather.js +4 -17
  300. package/dist/world/windZones.d.ts +47 -0
  301. package/dist/world/windZones.js +59 -0
  302. package/llms.txt +3013 -1726
  303. package/package.json +12 -2
@@ -80,7 +80,7 @@ export function advanceSpawnDirector(config, state, dt, ctx) {
80
80
  let done = state.done;
81
81
  const alert = Math.max(0, state.alert - alertDecay * dt);
82
82
  const trickle = config.waves[wave]?.budgetPerSecond ?? 0;
83
- const escalation = (config.escalationPerSecond ?? 0) * elapsed;
83
+ const escalation = config.escalationPerSecond ?? 0;
84
84
  const alertBonus = (config.alertBudgetPerSecond ?? 0) * alert;
85
85
  const playerBonus = (config.playerBudgetPerSecond ?? 0) * players;
86
86
  budget += (trickle + escalation + alertBonus + playerBonus) * dt;
@@ -118,7 +118,14 @@ export function advanceSpawnDirector(config, state, dt, ctx) {
118
118
  if (spawnPoints !== undefined && spawnPoints.length > 0) {
119
119
  const [roll, nextRng] = nextRandom(rng);
120
120
  rng = nextRng;
121
- const point = pickSpawnPoint(spawnPoints, playerPositions, { roll, bias: config.spawnPointBias });
121
+ const bias = config.spawnPointBias ?? 1;
122
+ const point = pickSpawnPoint({
123
+ candidates: spawnPoints,
124
+ avoid: playerPositions,
125
+ random: () => roll,
126
+ distanceBias: bias < 0 ? "far" : bias > 0 ? "near" : "none",
127
+ biasStrength: Math.abs(bias),
128
+ });
122
129
  if (point !== null) {
123
130
  spawn.point = point;
124
131
  spawn.laneId = spawnPoints.indexOf(point);
@@ -134,29 +141,46 @@ export function advanceSpawnDirector(config, state, dt, ctx) {
134
141
  spawns,
135
142
  };
136
143
  }
137
- export function pickSpawnPoint(points, players, options) {
138
- if (points.length === 0)
144
+ export function pickSpawnPoint(optionsOrPoints, legacyPlayers = [], legacyOptions = { roll: 0 }) {
145
+ let options;
146
+ if (Array.isArray(optionsOrPoints)) {
147
+ const legacyBias = legacyOptions.bias ?? 1;
148
+ options = {
149
+ candidates: optionsOrPoints,
150
+ avoid: legacyPlayers,
151
+ random: () => legacyOptions.roll,
152
+ distanceBias: legacyBias < 0 ? "far" : legacyBias > 0 ? "near" : "none",
153
+ biasStrength: Math.abs(legacyBias),
154
+ };
155
+ }
156
+ else {
157
+ options = optionsOrPoints;
158
+ }
159
+ const { candidates, avoid = [], random, distanceBias = "near", biasStrength = 1 } = options;
160
+ if (candidates.length === 0)
139
161
  return null;
140
- const bias = options.bias ?? 1;
162
+ if (distanceBias === "none" || avoid.length === 0 || biasStrength === 0) {
163
+ return candidates[Math.min(candidates.length - 1, Math.floor(clamp01(random()) * candidates.length))];
164
+ }
165
+ const exponent = distanceBias === "far" ? biasStrength : -biasStrength;
141
166
  const weights = [];
142
167
  let total = 0;
143
- for (const point of points) {
168
+ for (const point of candidates) {
144
169
  let nearest = Number.POSITIVE_INFINITY;
145
- for (const player of players) {
146
- const d = Math.hypot(point[0] - player[0], point[1] - player[1]);
147
- if (d < nearest)
148
- nearest = d;
170
+ for (const avoidedPoint of avoid) {
171
+ const distance = Math.hypot(point[0] - avoidedPoint[0], point[1] - avoidedPoint[1]);
172
+ if (distance < nearest)
173
+ nearest = distance;
149
174
  }
150
- const distance = nearest === Number.POSITIVE_INFINITY ? 0 : nearest;
151
- const weight = Math.max(1e-6, (1 + distance) ** -bias);
175
+ const weight = Math.max(1e-6, (1 + nearest) ** exponent);
152
176
  weights.push(weight);
153
177
  total += weight;
154
178
  }
155
- let cursor = clamp01(options.roll) * total;
156
- for (let i = 0; i < points.length; i += 1) {
157
- cursor -= weights[i];
179
+ let cursor = clamp01(random()) * total;
180
+ for (let index = 0; index < candidates.length; index += 1) {
181
+ cursor -= weights[index];
158
182
  if (cursor <= 0)
159
- return points[i];
183
+ return candidates[index];
160
184
  }
161
- return points[points.length - 1];
185
+ return candidates[candidates.length - 1];
162
186
  }
@@ -0,0 +1,5 @@
1
+ import type { NavPoint } from "../nav/navGrid.js";
2
+ import { type SpawnPointDistanceBias, type SpawnPointSelectionOptions } from "./spawnDirector.js";
3
+ export type { SpawnPointDistanceBias, SpawnPointSelectionOptions };
4
+ /** Selects a spawn point by game intent while keeping weighting mechanics internal. */
5
+ export declare function selectSpawnPoint(options: SpawnPointSelectionOptions): NavPoint | null;
@@ -0,0 +1,5 @@
1
+ import { pickSpawnPoint, } from "./spawnDirector.js";
2
+ /** Selects a spawn point by game intent while keeping weighting mechanics internal. */
3
+ export function selectSpawnPoint(options) {
4
+ return pickSpawnPoint(options);
5
+ }
@@ -18,6 +18,8 @@ export interface ThreatTable {
18
18
  decay(dt: number): void;
19
19
  highest(options?: HighestThreatOptions): string | null;
20
20
  ranked(): ThreatEntry[];
21
+ taunt(sourceId: string, durationSeconds: number): void;
22
+ forcedTarget(): string | null;
21
23
  remove(sourceId: string): void;
22
24
  clear(): void;
23
25
  size(): number;
package/dist/ai/threat.js CHANGED
@@ -4,6 +4,8 @@ export function createThreatTable(config = {}) {
4
4
  const max = config.max ?? Number.POSITIVE_INFINITY;
5
5
  const forgetBelow = config.forgetBelow ?? DEFAULT_FORGET_BELOW;
6
6
  const table = new Map();
7
+ let forcedSource = null;
8
+ let forcedRemaining = 0;
7
9
  function store(sourceId, value) {
8
10
  const clamped = Math.min(max, value);
9
11
  if (clamped <= forgetBelow) {
@@ -13,6 +15,16 @@ export function createThreatTable(config = {}) {
13
15
  table.set(sourceId, clamped);
14
16
  return clamped;
15
17
  }
18
+ function topThreat() {
19
+ let best = 0;
20
+ for (const value of table.values())
21
+ if (value > best)
22
+ best = value;
23
+ return best;
24
+ }
25
+ function forcedTarget() {
26
+ return forcedRemaining > 0 ? forcedSource : null;
27
+ }
16
28
  return {
17
29
  add(sourceId, amount) {
18
30
  return store(sourceId, (table.get(sourceId) ?? 0) + amount);
@@ -24,6 +36,13 @@ export function createThreatTable(config = {}) {
24
36
  return table.get(sourceId) ?? 0;
25
37
  },
26
38
  decay(dt) {
39
+ if (dt > 0 && forcedRemaining > 0) {
40
+ forcedRemaining -= dt;
41
+ if (forcedRemaining <= 0) {
42
+ forcedRemaining = 0;
43
+ forcedSource = null;
44
+ }
45
+ }
27
46
  if (decayPerSecond <= 0 || dt <= 0)
28
47
  return;
29
48
  const loss = decayPerSecond * dt;
@@ -31,6 +50,9 @@ export function createThreatTable(config = {}) {
31
50
  store(sourceId, value - loss);
32
51
  },
33
52
  highest(options = {}) {
53
+ const forced = forcedTarget();
54
+ if (forced !== null)
55
+ return forced;
34
56
  let bestId = null;
35
57
  let bestThreat = Number.NEGATIVE_INFINITY;
36
58
  for (const [sourceId, value] of table) {
@@ -54,11 +76,23 @@ export function createThreatTable(config = {}) {
54
76
  .map(([sourceId, threat]) => ({ sourceId, threat }))
55
77
  .sort((a, b) => b.threat - a.threat || a.sourceId.localeCompare(b.sourceId));
56
78
  },
79
+ taunt(sourceId, durationSeconds) {
80
+ store(sourceId, Math.max(table.get(sourceId) ?? 0, topThreat()));
81
+ forcedSource = sourceId;
82
+ forcedRemaining = Math.max(0, durationSeconds);
83
+ },
84
+ forcedTarget,
57
85
  remove(sourceId) {
58
86
  table.delete(sourceId);
87
+ if (forcedSource === sourceId) {
88
+ forcedSource = null;
89
+ forcedRemaining = 0;
90
+ }
59
91
  },
60
92
  clear() {
61
93
  table.clear();
94
+ forcedSource = null;
95
+ forcedRemaining = 0;
62
96
  },
63
97
  size() {
64
98
  return table.size;
@@ -1,3 +1,4 @@
1
+ import type { SynthPatch } from "./synth.js";
1
2
  export type AudioBusId = string;
2
3
  export type FalloffCurve = "linear" | "inverse" | "none";
3
4
  export interface AudioFalloffConfig {
@@ -15,8 +16,10 @@ export interface AudioBusDef {
15
16
  }
16
17
  export interface SoundDef {
17
18
  id: string;
18
- /** Playable source — a URL/data URI the shell's Web Audio glue fetches and decodes. */
19
- url: string;
19
+ /** Sample source — a URL/data URI the shell's Web Audio glue fetches and decodes. Provide exactly one of `url` or `synth`. */
20
+ url?: string;
21
+ /** Procedural source — a pure synth patch the shell realises on Web Audio, no file fetch. Provide exactly one of `url` or `synth`. */
22
+ synth?: SynthPatch;
20
23
  bus: AudioBusId;
21
24
  /** Base gain before bus/falloff multipliers. Default 1. */
22
25
  gain?: number;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Named synthesised instrument. Each maps to a voice in the shell's instrument
3
+ * library (`@jgengine/shell/audio/musicVoices`); an unknown name falls back to a
4
+ * plain sine voice so a theme is never silent.
5
+ */
6
+ export type MusicInstrument = "strings" | "flute" | "harp" | "horn" | "choir" | "bell" | "timpani" | "bass" | "stacc" | "pad" | "lute" | "dulcimer" | "frameDrum" | "warDrum" | "reed" | "pipe" | "squareLead" | "woodBlock" | "tinyBell" | "piano" | "shaker" | "brassStab" | "cymSwell" | "oboe";
7
+ /** One scheduled note in a theme, positioned on the loop's quarter-note grid. */
8
+ export interface NoteEvent {
9
+ /** Quarter-note position within the loop (0-based). */
10
+ beat: number;
11
+ /** MIDI note number (60 = middle C). */
12
+ midi: number;
13
+ /** Sounding length in quarter-note beats. */
14
+ dur: number;
15
+ /** Velocity 0..1. */
16
+ vel: number;
17
+ /** Which instrument voice plays it. */
18
+ inst: MusicInstrument;
19
+ }
20
+ /**
21
+ * A through-composed, looping music track. `events` need not be sorted; the
22
+ * director schedules them ahead against a fixed anchor so loops are seamless.
23
+ */
24
+ export interface MusicTheme {
25
+ id: string;
26
+ /** Tempo in beats (quarter notes) per minute. */
27
+ bpm: number;
28
+ /** Loop length in 4/4 bars; the loop is `bars * 4` beats long. */
29
+ bars: number;
30
+ events: readonly NoteEvent[];
31
+ /**
32
+ * Per-theme loudness normalisation multiplied into the crossfade target, so
33
+ * dense and sparse themes sit at the same perceived level. Default 1.
34
+ */
35
+ trim?: number;
36
+ }
37
+ /** A resolved note occurrence: the source note plus its absolute onset in seconds. */
38
+ export interface ScheduledNote {
39
+ event: NoteEvent;
40
+ /** Absolute onset in seconds, on the timeline of the `anchorSec`/window args. */
41
+ when: number;
42
+ }
43
+ /** Standard equal-temperament MIDI-to-frequency (A4 = 440 Hz at MIDI 69). */
44
+ export declare function mtof(midi: number): number;
45
+ /** Loop length of a theme in seconds. */
46
+ export declare function themeLoopSeconds(theme: MusicTheme): number;
47
+ /**
48
+ * Pure lookahead scheduler: every note occurrence of `theme` whose onset falls
49
+ * in the half-open window `(fromSec, toSec]`, given the theme's loop-zero at
50
+ * `anchorSec`. Handles any number of loop wraps, so a director calls it once per
51
+ * tick with a non-overlapping window and never double-schedules a note.
52
+ */
53
+ export declare function notesInWindow(theme: MusicTheme, anchorSec: number, fromSec: number, toSec: number): ScheduledNote[];
@@ -0,0 +1,34 @@
1
+ /** Standard equal-temperament MIDI-to-frequency (A4 = 440 Hz at MIDI 69). */
2
+ export function mtof(midi) {
3
+ return 440 * 2 ** ((midi - 69) / 12);
4
+ }
5
+ /** Loop length of a theme in seconds. */
6
+ export function themeLoopSeconds(theme) {
7
+ return (theme.bars * 4 * 60) / theme.bpm;
8
+ }
9
+ /**
10
+ * Pure lookahead scheduler: every note occurrence of `theme` whose onset falls
11
+ * in the half-open window `(fromSec, toSec]`, given the theme's loop-zero at
12
+ * `anchorSec`. Handles any number of loop wraps, so a director calls it once per
13
+ * tick with a non-overlapping window and never double-schedules a note.
14
+ */
15
+ export function notesInWindow(theme, anchorSec, fromSec, toSec) {
16
+ const loop = themeLoopSeconds(theme);
17
+ if (loop <= 0 || toSec <= fromSec)
18
+ return [];
19
+ const spb = 60 / theme.bpm;
20
+ const out = [];
21
+ for (const event of theme.events) {
22
+ const onset = event.beat * spb;
23
+ let k = Math.max(0, Math.floor((fromSec - anchorSec - onset) / loop));
24
+ for (;;) {
25
+ const when = anchorSec + onset + k * loop;
26
+ if (when > toSec)
27
+ break;
28
+ if (when > fromSec)
29
+ out.push({ event, when });
30
+ k += 1;
31
+ }
32
+ }
33
+ return out;
34
+ }
@@ -0,0 +1,60 @@
1
+ /** Oscillator waveform available to a pitched synth voice. */
2
+ export type SynthWave = "sine" | "square" | "sawtooth" | "triangle";
3
+ /** Biquad filter shape a noise voice is coloured through. */
4
+ export type SynthFilter = "lowpass" | "highpass" | "bandpass";
5
+ /**
6
+ * A pitched oscillator voice: a 12ms linear attack to `gain`, then an
7
+ * exponential decay to silence across `duration`, with an optional
8
+ * exponential pitch slide from `freq` to `slideTo`.
9
+ */
10
+ export interface ToneVoice {
11
+ kind: "tone";
12
+ /** Start frequency in Hz. */
13
+ freq: number;
14
+ /** Total voice length in seconds (the decay spans this). */
15
+ duration: number;
16
+ /** Peak gain before patch/bus scaling. Default 1. */
17
+ gain?: number;
18
+ /** Oscillator waveform. Default "sine". */
19
+ wave?: SynthWave;
20
+ /** Start offset in seconds from the cue trigger. Default 0. */
21
+ delay?: number;
22
+ /** When set, pitch ramps exponentially from `freq` to this Hz across `duration`. */
23
+ slideTo?: number;
24
+ /** Attack ramp in seconds. Default 0.012. */
25
+ attack?: number;
26
+ }
27
+ /**
28
+ * A filtered white-noise burst — impacts, whooshes, breath, crackle. Realised
29
+ * from a shared 1s noise buffer at a randomised playback rate and start offset,
30
+ * decaying exponentially to silence at `duration * decay`.
31
+ */
32
+ export interface NoiseVoice {
33
+ kind: "noise";
34
+ /** Total voice length in seconds. */
35
+ duration: number;
36
+ /** Biquad cutoff/centre in Hz. */
37
+ filterFreq: number;
38
+ /** Peak gain before patch/bus scaling. Default 1. */
39
+ gain?: number;
40
+ /** Filter shape. Default "lowpass". */
41
+ filterType?: SynthFilter;
42
+ /** Fraction of `duration` over which the burst decays to silence. Default 0.9. */
43
+ decay?: number;
44
+ /** Start offset in seconds from the cue trigger. Default 0. */
45
+ delay?: number;
46
+ }
47
+ /** One layered voice of a synth cue. */
48
+ export type SynthVoice = ToneVoice | NoiseVoice;
49
+ /**
50
+ * A procedural sound cue: a set of voices triggered together, each with its own
51
+ * `delay`, summed into one one-shot. Pure serialisable data — the shell realises
52
+ * it on Web Audio, so the same catalog runs headless in tests with no `AudioContext`.
53
+ */
54
+ export interface SynthPatch {
55
+ voices: readonly SynthVoice[];
56
+ /** Overall cue gain multiplier applied to every voice. Default 1. */
57
+ gain?: number;
58
+ }
59
+ /** Total wall-clock length of a patch in seconds — the latest voice end across all voices. */
60
+ export declare function patchDuration(patch: SynthPatch): number;
@@ -0,0 +1,8 @@
1
+ /** Total wall-clock length of a patch in seconds — the latest voice end across all voices. */
2
+ export function patchDuration(patch) {
3
+ let end = 0;
4
+ for (const voice of patch.voices) {
5
+ end = Math.max(end, (voice.delay ?? 0) + voice.duration);
6
+ }
7
+ return end;
8
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Task-first JGEngine authoring surface.
3
+ *
4
+ * Game code should begin here. Domain and runtime deep imports remain available for
5
+ * advanced composition, but this module intentionally exposes common authored
6
+ * concepts without requiring callers to understand the repository layout.
7
+ */
8
+ export { defineGame, type GameDefinition, type GameDefinitionConfig, type GameLoop, type InventoryDeclaration, type PhysicsConfig, type GameServerConfig, } from "./game/defineGame.js";
9
+ export { lootTable, type LootTableDef, type LootEntry, type Drop, } from "./game/lootTable.js";
10
+ export { biomes, voxel, plots, tilemap, flat, environment, terrain, rain, snow, grass, ocean, building, road, type WorldFeature, } from "./world/features.js";
11
+ export { seededRng, seededStreams } from "./random/rng.js";
12
+ export { selectSpawnPoint, type SpawnPointSelectionOptions, type SpawnPointDistanceBias, } from "./ai/spawnPoint.js";
13
+ export { contextVerb } from "./interaction/contextMenu.js";
14
+ export { type ActionCodesMap } from "./input/actionBindings.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Task-first JGEngine authoring surface.
3
+ *
4
+ * Game code should begin here. Domain and runtime deep imports remain available for
5
+ * advanced composition, but this module intentionally exposes common authored
6
+ * concepts without requiring callers to understand the repository layout.
7
+ */
8
+ export { defineGame, } from "./game/defineGame.js";
9
+ export { lootTable, } from "./game/lootTable.js";
10
+ export { biomes, voxel, plots, tilemap, flat, environment, terrain, rain, snow, grass, ocean, building, road, } from "./world/features.js";
11
+ export { seededRng, seededStreams } from "./random/rng.js";
12
+ export { selectSpawnPoint, } from "./ai/spawnPoint.js";
13
+ export { contextVerb } from "./interaction/contextMenu.js";
14
+ export {} from "./input/actionBindings.js";
@@ -0,0 +1,15 @@
1
+ import { type AbilityKit } from "../combat/abilityKit.js";
2
+ import type { GameLoop } from "../game/defineGame.js";
3
+ import type { GameContext, GameContextContent } from "../runtime/gameContext.js";
4
+ import { type CartridgeRun, type CartridgeSpec } from "./spec.js";
5
+ export interface CartridgeRuntime {
6
+ content: GameContextContent;
7
+ loop: GameLoop<GameContext>;
8
+ run(ctx: GameContext): CartridgeRun;
9
+ weaponKit(ctx: GameContext): AbilityKit;
10
+ chooseUpgrade(ctx: GameContext, offerId: string): void;
11
+ begin(ctx: GameContext): void;
12
+ reset(ctx: GameContext): void;
13
+ spec: CartridgeSpec;
14
+ }
15
+ export declare function createCartridge(spec: CartridgeSpec): CartridgeRuntime;