@jgengine/core 0.19.0 → 0.22.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 (266) hide show
  1. package/CHANGELOG.md +364 -0
  2. package/dist/ai/decisionGraph.d.ts +2 -0
  3. package/dist/ai/decisionGraph.js +6 -0
  4. package/dist/ai/flock.d.ts +1 -1
  5. package/dist/ai/flock.js +7 -4
  6. package/dist/anim/animGraph.d.ts +33 -2
  7. package/dist/anim/animGraph.js +200 -12
  8. package/dist/anim/locomotionGraph.d.ts +11 -3
  9. package/dist/anim/locomotionGraph.js +25 -15
  10. package/dist/audio/audioFalloff.d.ts +4 -0
  11. package/dist/audio/voiceAllocator.d.ts +46 -0
  12. package/dist/audio/voiceAllocator.js +76 -0
  13. package/dist/combat/abilityKit.d.ts +19 -0
  14. package/dist/combat/abilityKit.js +40 -4
  15. package/dist/combat/death.d.ts +4 -3
  16. package/dist/combat/death.js +3 -3
  17. package/dist/combat/magazine.d.ts +9 -0
  18. package/dist/combat/magazine.js +24 -2
  19. package/dist/combat/projectiles.d.ts +94 -2
  20. package/dist/combat/projectiles.js +282 -61
  21. package/dist/combat/regenShield.d.ts +14 -0
  22. package/dist/combat/regenShield.js +23 -7
  23. package/dist/crafting/recipeCatalog.d.ts +16 -0
  24. package/dist/crafting/recipeCatalog.js +55 -0
  25. package/dist/economy/auctionBook.d.ts +1 -1
  26. package/dist/economy/auctionBook.js +11 -1
  27. package/dist/economy/listingBook.d.ts +1 -1
  28. package/dist/economy/listingBook.js +11 -1
  29. package/dist/economy/resourceLedger.js +3 -3
  30. package/dist/economy/shopStock.js +7 -6
  31. package/dist/economy/techTree.js +7 -6
  32. package/dist/economy/unlockPoints.js +3 -6
  33. package/dist/editor/commandApply.js +89 -16
  34. package/dist/editor/commands.d.ts +38 -1
  35. package/dist/editor/commands.js +320 -3
  36. package/dist/editor/creatorStorage.d.ts +59 -0
  37. package/dist/editor/creatorStorage.js +164 -0
  38. package/dist/editor/document.d.ts +4 -0
  39. package/dist/editor/document.js +116 -17
  40. package/dist/editor/index.d.ts +3 -0
  41. package/dist/editor/index.js +1 -0
  42. package/dist/editor/liveSync.d.ts +4 -1
  43. package/dist/editor/liveSync.js +26 -10
  44. package/dist/editor/materialAuthoring.d.ts +14 -0
  45. package/dist/editor/materialAuthoring.js +38 -0
  46. package/dist/editor/modelNodeVisibility.d.ts +11 -0
  47. package/dist/editor/modelNodeVisibility.js +29 -0
  48. package/dist/editor/movementCatalog.d.ts +90 -0
  49. package/dist/editor/movementCatalog.js +144 -0
  50. package/dist/editor/simulation.d.ts +60 -0
  51. package/dist/editor/simulation.js +202 -0
  52. package/dist/editor/staticPrefab.d.ts +17 -0
  53. package/dist/editor/staticPrefab.js +51 -0
  54. package/dist/editor/terrainMaterials.d.ts +10 -0
  55. package/dist/editor/terrainMaterials.js +13 -0
  56. package/dist/editor/types.d.ts +11 -0
  57. package/dist/game/cameraConfig.d.ts +3 -1
  58. package/dist/game/clipRoles.d.ts +2 -1
  59. package/dist/game/clipRoles.js +7 -3
  60. package/dist/game/contentValidation.d.ts +69 -0
  61. package/dist/game/contentValidation.js +118 -0
  62. package/dist/game/controlGate.d.ts +8 -0
  63. package/dist/game/controlGate.js +43 -6
  64. package/dist/game/defineGame.d.ts +32 -3
  65. package/dist/game/dialogueGraph.d.ts +20 -0
  66. package/dist/game/dialogueGraph.js +107 -0
  67. package/dist/game/events.d.ts +5 -0
  68. package/dist/game/keyValueStore.d.ts +17 -4
  69. package/dist/game/keyValueStore.js +35 -23
  70. package/dist/game/lootModifiers.d.ts +17 -0
  71. package/dist/game/lootModifiers.js +26 -0
  72. package/dist/game/narrativeAuthoring.d.ts +67 -0
  73. package/dist/game/narrativeAuthoring.js +122 -0
  74. package/dist/game/playableGame.d.ts +52 -20
  75. package/dist/game/quest.d.ts +15 -0
  76. package/dist/game/quest.js +209 -63
  77. package/dist/game/questCatalog.d.ts +50 -0
  78. package/dist/game/questCatalog.js +205 -0
  79. package/dist/game/saveStore.d.ts +6 -5
  80. package/dist/game/saveStore.js +57 -57
  81. package/dist/game/systemRuntime.d.ts +2 -2
  82. package/dist/game/systemRuntime.js +84 -33
  83. package/dist/gameplay.d.ts +3 -0
  84. package/dist/gameplay.js +3 -0
  85. package/dist/interaction/promptRegistry.d.ts +5 -4
  86. package/dist/interaction/promptRegistry.js +8 -2
  87. package/dist/inventory/inventoryModel.js +9 -6
  88. package/dist/inventory/shapedGrid.d.ts +48 -9
  89. package/dist/inventory/shapedGrid.js +69 -10
  90. package/dist/item/durability.js +4 -0
  91. package/dist/item/itemInstanceRegistry.d.ts +32 -1
  92. package/dist/item/itemInstanceRegistry.js +111 -7
  93. package/dist/item/itemgen.d.ts +1 -1
  94. package/dist/item/itemgen.js +1 -1
  95. package/dist/item/modularItem.js +7 -1
  96. package/dist/material/appearanceSignals.d.ts +53 -0
  97. package/dist/material/appearanceSignals.js +47 -0
  98. package/dist/material/hairCards.d.ts +47 -0
  99. package/dist/material/hairCards.js +136 -0
  100. package/dist/material/materialAsset.d.ts +162 -0
  101. package/dist/material/materialAsset.js +382 -0
  102. package/dist/meta/changelog.d.ts +1 -1
  103. package/dist/meta/changelog.js +348 -1
  104. package/dist/movement/characterController.d.ts +5 -2
  105. package/dist/movement/characterController.js +50 -23
  106. package/dist/movement/freeFlight.d.ts +2 -2
  107. package/dist/movement/freeFlight.js +3 -3
  108. package/dist/movement/movementModel.d.ts +24 -1
  109. package/dist/movement/movementModel.js +74 -7
  110. package/dist/movement/playerMovement.d.ts +28 -1
  111. package/dist/movement/playerMovement.js +425 -102
  112. package/dist/movement/poseState.d.ts +2 -0
  113. package/dist/movement/poseState.js +29 -1
  114. package/dist/movement/solidObstacles.d.ts +3 -1
  115. package/dist/movement/solidObstacles.js +8 -5
  116. package/dist/movement/terrainGrade.d.ts +13 -0
  117. package/dist/movement/terrainGrade.js +33 -0
  118. package/dist/movement/voxelController.d.ts +2 -2
  119. package/dist/movement/voxelController.js +3 -3
  120. package/dist/nav/navGrid.d.ts +19 -1
  121. package/dist/nav/navGrid.js +26 -10
  122. package/dist/nav/solidRoute.d.ts +50 -0
  123. package/dist/nav/solidRoute.js +144 -0
  124. package/dist/physics/ballisticSweep.d.ts +10 -3
  125. package/dist/physics/ballisticSweep.js +53 -29
  126. package/dist/physics/forceVolume.d.ts +41 -0
  127. package/dist/physics/forceVolume.js +88 -0
  128. package/dist/physics/kinematicVehicle.d.ts +8 -3
  129. package/dist/physics/kinematicVehicle.js +2 -2
  130. package/dist/physics/physicsWorldBackend.js +19 -3
  131. package/dist/physics/spatialGrid.d.ts +1 -1
  132. package/dist/physics/spatialGrid.js +8 -9
  133. package/dist/physics/vehicleDynamics.d.ts +3 -2
  134. package/dist/physics/vehicleDynamics.js +2 -2
  135. package/dist/physics/vehicleObstacles.d.ts +3 -2
  136. package/dist/physics/vehicleObstacles.js +29 -5
  137. package/dist/runtime/cameraDirector.d.ts +39 -2
  138. package/dist/runtime/cameraDirector.js +162 -0
  139. package/dist/runtime/commandDispatch.d.ts +14 -0
  140. package/dist/runtime/commandDispatch.js +19 -0
  141. package/dist/runtime/context/combat.d.ts +11 -4
  142. package/dist/runtime/context/combat.js +178 -11
  143. package/dist/runtime/context/deathLoot.d.ts +9 -8
  144. package/dist/runtime/context/deathLoot.js +16 -16
  145. package/dist/runtime/context/scene.d.ts +2 -0
  146. package/dist/runtime/context/scene.js +13 -21
  147. package/dist/runtime/descriptors/baseline.js +34 -3
  148. package/dist/runtime/descriptors/features.d.ts +2 -1
  149. package/dist/runtime/descriptors/features.js +18 -0
  150. package/dist/runtime/gameContext.js +79 -13
  151. package/dist/runtime/gameContextTypes.d.ts +14 -0
  152. package/dist/runtime/headlessRunner.d.ts +3 -1
  153. package/dist/runtime/headlessRunner.js +11 -11
  154. package/dist/runtime/hostedGameRunner.d.ts +24 -1
  155. package/dist/runtime/hostedGameRunner.js +156 -10
  156. package/dist/runtime/hostedWorldSession.d.ts +17 -5
  157. package/dist/runtime/hostedWorldSession.js +154 -16
  158. package/dist/runtime/hostedWorldStore.d.ts +2 -2
  159. package/dist/runtime/inputRecorder.d.ts +1 -1
  160. package/dist/runtime/inputRecorder.js +21 -1
  161. package/dist/runtime/inputSnapshot.d.ts +16 -4
  162. package/dist/runtime/inputSnapshot.js +31 -3
  163. package/dist/runtime/runtimeSave.d.ts +10 -6
  164. package/dist/runtime/runtimeSave.js +11 -5
  165. package/dist/runtime/simContext.d.ts +8 -3
  166. package/dist/runtime/simContext.js +7 -3
  167. package/dist/runtime/simSnapshot.js +2 -0
  168. package/dist/runtime/worldReplication.d.ts +2 -0
  169. package/dist/runtime/worldReplication.js +1 -1
  170. package/dist/scene/assetCatalog.d.ts +2 -0
  171. package/dist/scene/behaviorRuntime.d.ts +26 -5
  172. package/dist/scene/behaviorRuntime.js +263 -16
  173. package/dist/scene/behaviors.d.ts +30 -1
  174. package/dist/scene/behaviors.js +29 -0
  175. package/dist/scene/entityStore.d.ts +14 -3
  176. package/dist/scene/entityStore.js +45 -9
  177. package/dist/scene/mount.d.ts +9 -0
  178. package/dist/scene/mount.js +35 -0
  179. package/dist/scene/possession.d.ts +2 -0
  180. package/dist/scene/possession.js +21 -1
  181. package/dist/scene/sceneRaycast.d.ts +7 -0
  182. package/dist/scene/sceneRaycast.js +55 -0
  183. package/dist/scene/spatial.d.ts +20 -1
  184. package/dist/scene/spatial.js +94 -2
  185. package/dist/scene/vehicleSeat.d.ts +36 -2
  186. package/dist/scene/vehicleSeat.js +89 -2
  187. package/dist/stats/accumulatorMeter.d.ts +10 -0
  188. package/dist/stats/accumulatorMeter.js +6 -0
  189. package/dist/stats/eventMeter.d.ts +9 -2
  190. package/dist/stats/eventMeter.js +5 -2
  191. package/dist/store/defineStore.d.ts +3 -1
  192. package/dist/store/defineStore.js +3 -1
  193. package/dist/store/observableKeyedStore.d.ts +1 -1
  194. package/dist/store/observableKeyedStore.js +8 -6
  195. package/dist/time/simClock.d.ts +6 -2
  196. package/dist/time/simClock.js +5 -0
  197. package/dist/vfx/particleDirector.d.ts +58 -6
  198. package/dist/vfx/particleDirector.js +113 -22
  199. package/dist/vfx/particles.d.ts +68 -5
  200. package/dist/vfx/particles.js +284 -32
  201. package/dist/visibility/assetStreaming.d.ts +21 -3
  202. package/dist/visibility/assetStreaming.js +108 -24
  203. package/dist/visibility/settings.d.ts +8 -2
  204. package/dist/visibility/settings.js +3 -0
  205. package/dist/visibility/visibilitySystem.js +1 -0
  206. package/dist/work/expedition.d.ts +110 -0
  207. package/dist/work/expedition.js +195 -0
  208. package/dist/work/staffedStation.d.ts +29 -0
  209. package/dist/work/staffedStation.js +27 -0
  210. package/dist/world/authoredAnimation.d.ts +38 -0
  211. package/dist/world/authoredAnimation.js +99 -0
  212. package/dist/world/authoredObjects.d.ts +24 -4
  213. package/dist/world/authoredObjects.js +43 -16
  214. package/dist/world/authoredSimulation.d.ts +74 -0
  215. package/dist/world/authoredSimulation.js +302 -0
  216. package/dist/world/authoredWeather.d.ts +120 -0
  217. package/dist/world/authoredWeather.js +314 -0
  218. package/dist/world/buildingKit.d.ts +8 -3
  219. package/dist/world/buildingKit.js +21 -8
  220. package/dist/world/environmentForces.d.ts +21 -0
  221. package/dist/world/environmentForces.js +42 -0
  222. package/dist/world/features.d.ts +3 -3
  223. package/dist/world/footprintGrid.d.ts +55 -0
  224. package/dist/world/footprintGrid.js +201 -0
  225. package/dist/world/grassKind.d.ts +10 -0
  226. package/dist/world/grassKind.js +22 -0
  227. package/dist/world/pathTerrain.d.ts +1 -1
  228. package/dist/world/terraform.d.ts +2 -2
  229. package/dist/world/terraform.js +46 -18
  230. package/dist/world/weather.d.ts +17 -0
  231. package/dist/world/weather.js +116 -20
  232. package/dist/world/worldSolids.d.ts +11 -0
  233. package/dist/world/worldSolids.js +112 -1
  234. package/dist/world.d.ts +5 -5
  235. package/dist/world.js +4 -4
  236. package/package.json +1 -1
  237. package/skills/ce-handoff/SKILL.md +1 -1
  238. package/skills/game-design/SKILL.md +2 -0
  239. package/skills/jgengine/SKILL.md +23 -2
  240. package/skills/jgengine/api.md +28 -17
  241. package/skills/jgengine/recipes/minimal-game.md +7 -2
  242. package/skills/jgengine-combat/SKILL.md +8 -2
  243. package/skills/jgengine-combat/api.md +22 -10
  244. package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
  245. package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
  246. package/skills/jgengine-combat/reference.md +24 -2
  247. package/skills/jgengine-gameplay/SKILL.md +63 -0
  248. package/skills/jgengine-gameplay/api.md +131 -37
  249. package/skills/jgengine-gameplay/capabilities.md +68 -0
  250. package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
  251. package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
  252. package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
  253. package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
  254. package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
  255. package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
  256. package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
  257. package/skills/jgengine-gameplay/reference-systems.md +119 -0
  258. package/skills/jgengine-world/SKILL.md +12 -3
  259. package/skills/jgengine-world/api.md +201 -58
  260. package/skills/jgengine-world/capabilities.md +116 -3
  261. package/skills/jgengine-world/recipes/character-animation.md +31 -8
  262. package/skills/jgengine-world/recipes/controls.md +4 -0
  263. package/skills/jgengine-world/recipes/npc-senses.md +12 -0
  264. package/skills/jgengine-world/recipes/vehicle-feel.md +3 -1
  265. package/skills/jgengine-world/reference.md +167 -5
  266. package/skills/jgengine-world/visibility.md +28 -1
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Multiply weighted odds (or independent chances, clamped to one) by a caller-authored elapsed-time
3
+ * curve. Keeps source tables unchanged and uses the existing pipeline's modifier provenance.
4
+ * Elapsed time and multipliers must be finite and nonnegative; invalid curves reject the roll.
5
+ *
6
+ * @capability time-scaled-loot shape loot odds by elapsed hours with caller-authored curves and pipeline provenance
7
+ */
8
+ export function timeScaledRarity(options) {
9
+ return {
10
+ id: options.id ?? "time-scaled-rarity",
11
+ plan(plan, ctx) {
12
+ if (!Number.isFinite(ctx.elapsedMs) || ctx.elapsedMs < 0)
13
+ throw new RangeError("Loot elapsedMs must be finite and nonnegative");
14
+ const hours = ctx.elapsedMs / 3_600_000;
15
+ return { ...plan, entries: plan.entries.map(candidate => {
16
+ const factor = options.ramp(candidate.entry, hours);
17
+ if (!Number.isFinite(factor) || factor < 0)
18
+ throw new RangeError("Loot time multiplier must be finite and nonnegative");
19
+ const value = (plan.mode === "weighted" ? candidate.weight : candidate.chance ?? 0) * factor;
20
+ if (!Number.isFinite(value))
21
+ throw new RangeError("Loot time weighting overflowed");
22
+ return plan.mode === "weighted" ? { ...candidate, weight: value } : { ...candidate, chance: Math.min(1, value) };
23
+ }) };
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,67 @@
1
+ import { type DialogueGraph } from "./dialogueGraph.js";
2
+ /** Caller-reviewed facts for one authoring revision; event ids are in strict chronological order. */
3
+ export interface NarrativeCanon {
4
+ readonly revision: string;
5
+ readonly characterIds: readonly string[];
6
+ readonly facts: Readonly<Record<string, string>>;
7
+ readonly eventIds: readonly string[];
8
+ }
9
+ /** An explicit assertion attached to a dialogue node; dialogue prose is never parsed. */
10
+ export type NarrativeClaim = {
11
+ readonly nodeId: string;
12
+ } & ({
13
+ readonly kind: "character";
14
+ readonly characterId: string;
15
+ } | {
16
+ readonly kind: "fact";
17
+ readonly factId: string;
18
+ readonly value: string;
19
+ } | {
20
+ readonly kind: "before";
21
+ readonly earlier: string;
22
+ readonly later: string;
23
+ });
24
+ /** Authored dialogue with optional structured assertions against canon. */
25
+ export interface NarrativeDialogue {
26
+ readonly id: string;
27
+ readonly graph: DialogueGraph;
28
+ readonly claims: readonly NarrativeClaim[];
29
+ }
30
+ /** A candidate batch never changes canon; accepted new facts need a separately reviewed revision. */
31
+ export interface NarrativeBatch {
32
+ readonly canonRevision: string;
33
+ readonly dialogues: readonly NarrativeDialogue[];
34
+ }
35
+ /** Caller budgets across the entire batch, including graph branches and explicit assertions. */
36
+ export interface NarrativeBatchLimits {
37
+ readonly maxDialogues: number;
38
+ readonly maxNodes: number;
39
+ readonly maxChoices: number;
40
+ readonly maxClaims: number;
41
+ /** Sum of spoken lines and player choice text lengths (UTF-16 code units). */
42
+ readonly maxTextCharacters: number;
43
+ }
44
+ /** A repair diagnostic with a JSON-pointer location in the candidate batch. */
45
+ export interface NarrativeIssue {
46
+ readonly code: string;
47
+ readonly severity: "error" | "warning";
48
+ readonly path: string;
49
+ readonly message: string;
50
+ }
51
+ /**
52
+ * Detach and freeze reviewed canon before passing it to an asynchronous authoring callback.
53
+ * Duplicate character/event ids and an empty revision throw; facts retain exact caller values.
54
+ *
55
+ * @capability narrative-canon freeze caller-reviewed character ids, facts and chronology for a narrative authoring revision
56
+ */
57
+ export declare function freezeNarrativeCanon(canon: NarrativeCanon): NarrativeCanon;
58
+ /**
59
+ * Check a typed candidate against caller budgets, canon revision, explicit claims and graph topology.
60
+ * Stops at the first exceeded budget before topology work; stale revisions also stop immediately.
61
+ * Claims check exact fact values and the declared strict event order, never prose, speaker labels,
62
+ * motivations, branch consequences or story quality. O(canon + bounded nodes + choices + claims).
63
+ * Validate/parse external JSON before calling; this is not an untrusted JSON decoder.
64
+ *
65
+ * @capability narrative-batch-validation locate stale canon, oversized batches, explicit character/fact/chronology contradictions and dialogue graph issues for bounded repair
66
+ */
67
+ export declare function validateNarrativeBatch(canon: NarrativeCanon, batch: NarrativeBatch, limits: NarrativeBatchLimits): NarrativeIssue[];
@@ -0,0 +1,122 @@
1
+ import { validateDialogueGraph } from "./dialogueGraph.js";
2
+ /**
3
+ * Detach and freeze reviewed canon before passing it to an asynchronous authoring callback.
4
+ * Duplicate character/event ids and an empty revision throw; facts retain exact caller values.
5
+ *
6
+ * @capability narrative-canon freeze caller-reviewed character ids, facts and chronology for a narrative authoring revision
7
+ */
8
+ export function freezeNarrativeCanon(canon) {
9
+ if (canon.revision.length === 0)
10
+ throw new RangeError("Canon revision must not be empty.");
11
+ for (const ids of [canon.characterIds, canon.eventIds]) {
12
+ if (new Set(ids).size !== ids.length)
13
+ throw new RangeError("Canon ids must be unique within each list.");
14
+ }
15
+ return Object.freeze({
16
+ revision: canon.revision,
17
+ characterIds: Object.freeze([...canon.characterIds]),
18
+ facts: Object.freeze({ ...canon.facts }),
19
+ eventIds: Object.freeze([...canon.eventIds]),
20
+ });
21
+ }
22
+ /**
23
+ * Check a typed candidate against caller budgets, canon revision, explicit claims and graph topology.
24
+ * Stops at the first exceeded budget before topology work; stale revisions also stop immediately.
25
+ * Claims check exact fact values and the declared strict event order, never prose, speaker labels,
26
+ * motivations, branch consequences or story quality. O(canon + bounded nodes + choices + claims).
27
+ * Validate/parse external JSON before calling; this is not an untrusted JSON decoder.
28
+ *
29
+ * @capability narrative-batch-validation locate stale canon, oversized batches, explicit character/fact/chronology contradictions and dialogue graph issues for bounded repair
30
+ */
31
+ export function validateNarrativeBatch(canon, batch, limits) {
32
+ for (const value of Object.values(limits)) {
33
+ if (!Number.isSafeInteger(value) || value < 0)
34
+ throw new RangeError("Narrative limits must be non-negative safe integers.");
35
+ }
36
+ if (batch.canonRevision !== canon.revision) {
37
+ return [{ code: "stale-canon", severity: "error", path: "/canonRevision", message: "Candidate uses a different canon revision." }];
38
+ }
39
+ const budget = (path) => [{
40
+ code: "batch-budget", severity: "error", path, message: "Candidate exceeds the caller's narrative batch budget.",
41
+ }];
42
+ if (batch.dialogues.length > limits.maxDialogues)
43
+ return budget("/dialogues");
44
+ let nodes = 0;
45
+ let choices = 0;
46
+ let claims = 0;
47
+ let text = 0;
48
+ for (let index = 0; index < batch.dialogues.length; index++) {
49
+ const dialogue = batch.dialogues[index];
50
+ const path = `/dialogues/${index}`;
51
+ nodes += dialogue.graph.nodes.length;
52
+ claims += dialogue.claims.length;
53
+ if (nodes > limits.maxNodes)
54
+ return budget(`${path}/graph/nodes`);
55
+ if (claims > limits.maxClaims)
56
+ return budget(`${path}/claims`);
57
+ for (let nodeIndex = 0; nodeIndex < dialogue.graph.nodes.length; nodeIndex++) {
58
+ const node = dialogue.graph.nodes[nodeIndex];
59
+ choices += node.choices?.length ?? 0;
60
+ if (choices > limits.maxChoices)
61
+ return budget(`${path}/graph/nodes/${nodeIndex}/choices`);
62
+ text += node.text.length;
63
+ for (const choice of node.choices ?? [])
64
+ text += choice.text.length;
65
+ if (text > limits.maxTextCharacters)
66
+ return budget(`${path}/graph/nodes/${nodeIndex}`);
67
+ }
68
+ }
69
+ const issues = [];
70
+ const add = (code, path, message) => {
71
+ issues.push({ code, severity: "error", path, message });
72
+ };
73
+ const characters = new Set(canon.characterIds);
74
+ const facts = new Map(Object.entries(canon.facts));
75
+ const events = new Map(canon.eventIds.map((id, index) => [id, index]));
76
+ const dialogueIds = new Set();
77
+ for (let index = 0; index < batch.dialogues.length; index++) {
78
+ const dialogue = batch.dialogues[index];
79
+ const path = `/dialogues/${index}`;
80
+ if (dialogueIds.has(dialogue.id))
81
+ add("duplicate-dialogue", `${path}/id`, "Dialogue id repeats within the batch.");
82
+ dialogueIds.add(dialogue.id);
83
+ for (const issue of validateDialogueGraph(dialogue.graph)) {
84
+ let location = `${path}/graph`;
85
+ if (issue.nodeIndex !== undefined)
86
+ location += `/nodes/${issue.nodeIndex}`;
87
+ if (issue.choiceIndex !== undefined)
88
+ location += `/choices/${issue.choiceIndex}/to`;
89
+ if (issue.code === "missing-start")
90
+ location += "/start";
91
+ issues.push({ code: `dialogue:${issue.code}`, severity: issue.severity, path: location, message: issue.message });
92
+ }
93
+ const nodeIds = new Set(dialogue.graph.nodes.map((node) => node.id));
94
+ for (let claimIndex = 0; claimIndex < dialogue.claims.length; claimIndex++) {
95
+ const claim = dialogue.claims[claimIndex];
96
+ const location = `${path}/claims/${claimIndex}`;
97
+ if (!nodeIds.has(claim.nodeId))
98
+ add("missing-claim-node", `${location}/nodeId`, "Claim references an unknown dialogue node.");
99
+ if (claim.kind === "character" && !characters.has(claim.characterId)) {
100
+ add("unknown-character", `${location}/characterId`, "Claim references an unknown canon character.");
101
+ }
102
+ else if (claim.kind === "fact") {
103
+ if (!facts.has(claim.factId))
104
+ add("unknown-fact", `${location}/factId`, "Claim references an unknown canon fact.");
105
+ else if (facts.get(claim.factId) !== claim.value)
106
+ add("fact-conflict", `${location}/value`, "Claim differs from the reviewed fact value.");
107
+ }
108
+ else if (claim.kind === "before") {
109
+ const earlier = events.get(claim.earlier);
110
+ const later = events.get(claim.later);
111
+ if (earlier === undefined)
112
+ add("unknown-event", `${location}/earlier`, "Claim references an unknown canon event.");
113
+ if (later === undefined)
114
+ add("unknown-event", `${location}/later`, "Claim references an unknown canon event.");
115
+ if (earlier !== undefined && later !== undefined && earlier >= later) {
116
+ add("chronology-conflict", location, "Claim contradicts the reviewed strict event order.");
117
+ }
118
+ }
119
+ }
120
+ }
121
+ return issues;
122
+ }
@@ -1,6 +1,7 @@
1
1
  import type { AnimGraph } from "../anim/animGraph.js";
2
2
  import type { AudioBusDef, SoundDef } from "../audio/audioFalloff.js";
3
3
  import type { MusicTheme } from "../audio/music.js";
4
+ import type { MaterialAsset, MaterialAssignment, MaterialSurfaceParameters, MaterialTextureRole } from "../material/materialAsset.js";
4
5
  import type { EditorCatalogDefinition, EditorDocument } from "../editor/types.js";
5
6
  import type { PostProcessingConfig } from "../render/postProcessing.js";
6
7
  import type { EnvironmentSource } from "../render/environment.js";
@@ -17,6 +18,7 @@ import type { PositionedPrompt } from "../interaction/proximityPrompt.js";
17
18
  import type { ChaseCameraTuning } from "../runtime/cameraDirector.js";
18
19
  import type { CatalogEntityRole, GameContext, GameContextContent } from "../runtime/gameContext.js";
19
20
  import type { FreeFlightBindings } from "../movement/freeFlight.js";
21
+ import type { SceneEntity } from "../scene/entityStore.js";
20
22
  import type { ModelDims } from "../scene/assetCatalog.js";
21
23
  import type { PartMotionParams, PartRole } from "./partAnimation.js";
22
24
  import type { CollisionMeshData } from "../scene/collisionMesh.js";
@@ -120,6 +122,10 @@ export interface ModelAnimationStates {
120
122
  }
121
123
  /** Rig playback for a `ModelConfig`'s GLTF animation clips — looping idles, one-shots, and held poses. */
122
124
  export interface ModelAnimationConfig {
125
+ /** Infer defaults from the loaded rig's actual clip roles, then replace fields with explicit overrides. Requires an identifiable idle clip. */
126
+ auto?: true;
127
+ /** Playback clock: real seconds (default), or entity-bound locally accepted clock progress after speed/timescale and before calendar scale. Requires an advancing accepted-progress clock; unbound previews use real seconds. */
128
+ clock?: "real" | "game";
123
129
  /** Clip name to play; defaults to the GLB's first clip. */
124
130
  clip?: string;
125
131
  /** Loop the clip. Default true. */
@@ -151,24 +157,13 @@ export interface ModelAnimationConfig {
151
157
  /**
152
158
  * Real PBR map URLs (e.g. `buildMaterialCatalog(...).resolve(id)!.maps` from `@jgengine/assets`)
153
159
  * layered onto a model's material — the seam for texturing an otherwise-flat/untextured GLB. Any
154
- * role may be omitted to keep the model's own map.
160
+ * supported MaterialTextureRole may be omitted to keep the model's own map. Physical roles use the
161
+ * renderer's physical material; map URLs and response parameters remain caller-owned.
155
162
  */
156
- export interface ModelMaterialMaps {
157
- color?: string;
158
- normal?: string;
159
- roughness?: string;
160
- ao?: string;
161
- metalness?: string;
162
- emissive?: string;
163
- height?: string;
163
+ export interface ModelMaterialMaps extends Partial<Record<MaterialTextureRole, string>> {
164
164
  }
165
165
  /** Per-entity PBR material override (#151.3) applied to every standard or physical material in the model's cloned scene graph. */
166
- export interface ModelMaterialOverride {
167
- color?: string;
168
- metalness?: number;
169
- roughness?: number;
170
- emissive?: string;
171
- emissiveIntensity?: number;
166
+ export interface ModelMaterialOverride extends MaterialSurfaceParameters {
172
167
  /** Real PBR texture maps applied over the model's material — see {@link ModelMaterialMaps}. */
173
168
  maps?: ModelMaterialMaps;
174
169
  /**
@@ -234,6 +229,12 @@ export interface ModelConfig {
234
229
  collisionMesh?: CollisionMeshData;
235
230
  /** Per-entity PBR tint/finish override (#151.3); cloned onto each `MeshStandardMaterial` in the model so shared GLTF caches stay untouched. */
236
231
  material?: ModelMaterialOverride;
232
+ /** Reusable authored surfaces available to this model's named slot assignments. */
233
+ materialAssets?: readonly MaterialAsset[];
234
+ /** Sparse per-slot assignments; unselected imported materials keep their original appearance. */
235
+ materialAssignments?: readonly MaterialAssignment[];
236
+ /** Hide named visual subtrees on this instance before placement measurement. Exact runtime names or unique original imported names resolve without removing bones or changing the cached model; missing or ambiguous names warn and leave existing visibility unchanged. Malformed lists reject before model or texture allocation. Omit or pass [] to retain imported visibility. */
237
+ hiddenNodes?: readonly string[];
237
238
  /** Shadow participation of every mesh in the model. Default `"both"` (casts and receives); `"none"` for foliage cards, decals, or viewmodels that must never shadow the world. */
238
239
  shadows?: "cast" | "receive" | "both" | "none";
239
240
  /**
@@ -425,6 +426,8 @@ export interface PlayerMovementConfig {
425
426
  * colliders instead of relying on this switch.
426
427
  */
427
428
  collideObjects?: boolean;
429
+ /** Heightfield collision span from feet, finite and positive; default 1.8 meters. Live growth waits for clear headroom. Capsule and voxel paths retain their own dimensions; model proportions and camera placement remain game-owned. */
430
+ collisionHeight?: number;
428
431
  /** Tallest object ledge stepped up while walking (and largest drop still snapped down instead of falling). Default 0.4. */
429
432
  stepHeight?: number;
430
433
  /** Intercepts each frame's resolved position before the pose commits (and before onTick): return a replacement [x,y,z] to constrain or redirect the step, or nothing to accept it. */
@@ -436,8 +439,8 @@ export interface PlayerMovementConfig {
436
439
  /**
437
440
  * Walk-controller feel. Acceleration and friction are exponential response rates (1/s): higher reaches
438
441
  * the target velocity or stops sooner. Omitted fields keep the engine defaults, so nothing changes until a
439
- * game sets one. The jump fields apply to the kinematic walk; a `physics.backend` capsule or `collision.voxel`
440
- * body jumps on the press frame with plain gravity. Tune against `measureMovement` (`@jgengine/core/movement/movementProbe`).
442
+ * game sets one. The heightfield and physics-backed capsule paths share jump feel; a `collision.voxel`
443
+ * body retains press-frame jumping with plain gravity. Tune against `measureMovement` (`@jgengine/core/movement/movementProbe`).
441
444
  */
442
445
  feel?: MovementFeelConfig;
443
446
  /** Radians/second the rendered body rotates toward its movement heading (shortest arc), so strafing/backpedalling read as a turn rather than an instant flip; also the rate the internally-integrated `turnLeft`/`turnRight` heading turns when the shell doesn't own yaw. Unset = body facing snaps instantly (no change to existing feel). */
@@ -450,6 +453,19 @@ export interface PlayerMovementConfig {
450
453
  slopeSlide?: {
451
454
  maxClimbSlope?: number;
452
455
  } | boolean;
456
+ /**
457
+ * Maximum sampled uphill rise/run for heightfield walking, including jumps. A rejected step
458
+ * slides along a traversable X or Z axis and feet use the accepted ground. Finite, nonnegative;
459
+ * omitted means unrestricted. Capsule, voxel and flight paths retain their own collision rules.
460
+ */
461
+ maxClimbGrade?: number;
462
+ /**
463
+ * Optional terrain-policy sampler for `maxClimbGrade`; defaults to the resolved ground sampler.
464
+ * Use when grade rules follow a different terrain field (for example before road cuts). This
465
+ * does not change foot grounding. Called at most four times per constrained walking step;
466
+ * must return finite heights or the step throws with the invalid sampling coordinate.
467
+ */
468
+ climbGradeHeight?: (x: number, z: number) => number;
453
469
  /**
454
470
  * Free-flight — creative/spectator/noclip/hover — folded into the same walk controller so a game
455
471
  * doesn't hand-roll a second movement loop to get Minecraft-like flight. `true` uses creative defaults;
@@ -610,19 +626,23 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEnt
610
626
  platforms?: readonly HudPlatform[];
611
627
  /** HUD design resolution + scale clamps (default 1600×900, scale 0.4–1). `mobile` overrides tune the phone fit separately — the same resolution system drives desktop UI-scale and phone shrink. */
612
628
  hudFit?: HudViewportConfig;
613
- /** Opt in to world-space health bars floating over non-local entities that carry the stat. `roles` restricts bars to entities whose catalog entry declares one of the given roles; `maxDistance` hides bars beyond this many world units from the player (default 60). */
629
+ /** Opt in to world-space health bars floating over non-local entities that carry the stat. `roles` restricts bars to entities whose catalog entry declares one of the given roles; `maxDistance` hides bars beyond this many world units from the render camera (default 60). */
614
630
  worldHealthBars?: boolean | {
615
631
  statId?: string;
616
632
  roles?: readonly CatalogEntityRole[];
617
633
  maxDistance?: number;
618
- /** Hide a bar when world geometry stands between the player and the entity. These bars are a screen-space overlay outside the depth buffer, so by default they draw through walls. Default false. */
634
+ /** Maximum overlays and visibility rays per refresh. Default 64, capped at 256. */
635
+ maxSamples?: number;
636
+ /** Minimum refresh interval in ms. Default 80 for bars, 120 for plates. */
637
+ tickMs?: number;
638
+ /** Hide bars behind blocking geometry from the render camera. Default true; false explicitly reveals through walls. */
619
639
  occlude?: boolean;
620
640
  };
621
641
  /**
622
642
  * Opt in to billboarded nameplates (name + optional HP bar) floating over non-local entities — the
623
643
  * MMO "who's this and how hurt are they" readout. `roles` restricts to entities whose catalog entry
624
644
  * declares one of the given roles (default: all); `maxDistance` hides nameplates beyond this many
625
- * world units from the player (default 40). Headless: skin every part via `className`/`data-*` hooks
645
+ * world units from the render camera (default 40). Headless: skin every part via `className`/`data-*` hooks
626
646
  * on `WorldNameplates` (`@jgengine/shell/world/WorldHud`) — this flag only turns the readout on and
627
647
  * scopes which entities it covers. Set `showHealth: false` for a name-only plate when a game already
628
648
  * draws the HP bar itself (its own HUD, or `worldHealthBars`) — otherwise the nameplate's built-in bar
@@ -632,7 +652,15 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEnt
632
652
  statId?: string;
633
653
  roles?: readonly CatalogEntityRole[];
634
654
  maxDistance?: number;
655
+ /** Maximum overlays and visibility rays per refresh. Default 64, capped at 256. */
656
+ maxSamples?: number;
657
+ /** Minimum refresh interval in ms. Default 80 for bars, 120 for plates. */
658
+ tickMs?: number;
635
659
  showHealth?: boolean;
660
+ /** Hide plates behind blocking geometry from the render camera. Default true; false explicitly reveals through walls. */
661
+ occlude?: boolean;
662
+ /** Authored display name. Return null to omit a plate; default suppresses machine identifiers. */
663
+ resolveName?: (entity: SceneEntity) => string | null;
636
664
  };
637
665
  /**
638
666
  * Combat presentation stack mounted inside the 3D canvas (telegraphs, spell VFX, retained VFX,
@@ -644,6 +672,10 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEnt
644
672
  /** Sound catalog + mix buses (music/sfx/ambient/…) the shell's Web Audio glue plays from. Catalog-first — no per-game audio wiring. `sounds` may be sample (`url`) or procedural (`synth`); `music` holds procedural themes crossfaded via `ctx.game.audio.music(id)`. */
645
673
  audio?: {
646
674
  sounds: Record<string, SoundDef>;
675
+ /** SFX playback/patch budget, including pending samples; individual synth oscillators share one slot. Default 64; zero disables SFX. */
676
+ maxVoices?: number;
677
+ /** Overflow rejects new cues or steals the lowest-priority oldest cue. Default "steal-lowest". */
678
+ voiceOverflow?: "steal-lowest" | "reject";
647
679
  buses?: Record<string, AudioBusDef>;
648
680
  music?: Record<string, MusicTheme>;
649
681
  musicBus?: string;
@@ -59,12 +59,17 @@ export interface QuestJournalDeps {
59
59
  grantItem(userId: string, inventoryId: string, itemId: string, count: number): {
60
60
  reason: string;
61
61
  } | null;
62
+ /** Grant the whole item batch, or reject without granting any items. Required for multiple item rewards. */
63
+ grantItems?(userId: string, items: Readonly<NonNullable<QuestRewards["items"]>>): {
64
+ reason: string;
65
+ } | null;
62
66
  grantUnlock(userId: string, unlockId: string): void;
63
67
  };
64
68
  hasUnlock?(userId: string, id: string): boolean;
65
69
  partyMembersNear?(userId: string, radius: number): string[];
66
70
  }
67
71
  export interface QuestJournal {
72
+ /** Add or replace definitions and refresh active credit indexes; re-register after editing objectives. */
68
73
  register(catalog: readonly QuestDef[] | Record<string, QuestDef>): void;
69
74
  has(questId: string): boolean;
70
75
  canAccept(userId: string, questId: string): {
@@ -86,6 +91,7 @@ export interface QuestJournal {
86
91
  }): void;
87
92
  revoke(userId: string, questId: string): void;
88
93
  list(userId: string): QuestInstance[];
94
+ /** Credit objectives active at event start in catalog order; quests activated during credit join the next event. */
89
95
  bind(action: "entity.died" | "inventory.added"): () => void;
90
96
  snapshot(userId: string): QuestSnapshotEntry[];
91
97
  hydrate(userId: string, data: QuestSnapshotEntry[]): void;
@@ -157,12 +163,21 @@ export interface QuestEvaluator {
157
163
  creditCollect(state: readonly QuestSnapshotEntry[], itemId: string, count: number): QuestSnapshotEntry[];
158
164
  list(state: readonly QuestSnapshotEntry[]): QuestInstance[];
159
165
  }
166
+ /**
167
+ * Apply items before XP, currency, and unlocks. Multiple items require an all-or-reject `grantItems`
168
+ * callback; legacy single-item appliers remain supported. Rejected callbacks must make no writes.
169
+ * Arbitrary callback side effects or thrown exceptions cannot be rolled back by this helper.
170
+ */
160
171
  export declare function applyQuestRewards(rewards: QuestRewards, appliers: {
161
172
  grantXp?(amount: number): void;
162
173
  grantEconomy?(currencyId: string, amount: number): void;
163
174
  grantItem?(inventoryId: string, itemId: string, count: number): {
164
175
  reason: string;
165
176
  } | null | void;
177
+ /** Grant every item, or reject without writes. Takes precedence over `grantItem`. */
178
+ grantItems?(items: Readonly<NonNullable<QuestRewards["items"]>>): {
179
+ reason: string;
180
+ } | null | void;
166
181
  grantUnlock?(unlockId: string): void;
167
182
  }): {
168
183
  reason: string;