@jgengine/core 0.11.0 → 0.12.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 (219) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/ai/populationDirector.d.ts +103 -0
  3. package/dist/ai/populationDirector.js +190 -0
  4. package/dist/ai/pursuit.d.ts +56 -0
  5. package/dist/ai/pursuit.js +53 -0
  6. package/dist/authoring.d.ts +1 -1
  7. package/dist/authoring.js +1 -1
  8. package/dist/combat.d.ts +1 -0
  9. package/dist/combat.js +1 -0
  10. package/dist/devtools/controls.d.ts +38 -0
  11. package/dist/devtools/controls.js +181 -0
  12. package/dist/devtools/devtools.d.ts +5 -197
  13. package/dist/devtools/devtools.js +38 -774
  14. package/dist/devtools/discover.d.ts +32 -0
  15. package/dist/devtools/discover.js +240 -0
  16. package/dist/devtools/fallbackSeams.d.ts +50 -0
  17. package/dist/devtools/fallbackSeams.js +111 -0
  18. package/dist/devtools/frame.d.ts +15 -0
  19. package/dist/devtools/frame.js +247 -0
  20. package/dist/devtools/logs.d.ts +15 -0
  21. package/dist/devtools/logs.js +86 -0
  22. package/dist/devtools/overrides.d.ts +16 -0
  23. package/dist/devtools/overrides.js +66 -0
  24. package/dist/devtools/types.d.ts +212 -0
  25. package/dist/devtools/types.js +1 -0
  26. package/dist/economy/auctionBook.d.ts +122 -0
  27. package/dist/economy/auctionBook.js +213 -0
  28. package/dist/economy/priceHistory.d.ts +44 -0
  29. package/dist/economy/priceHistory.js +89 -0
  30. package/dist/economy/unlockPoints.d.ts +92 -0
  31. package/dist/economy/unlockPoints.js +103 -0
  32. package/dist/editor/commandApply.d.ts +5 -0
  33. package/dist/editor/commandApply.js +755 -0
  34. package/dist/editor/commands.d.ts +48 -3
  35. package/dist/editor/commands.js +40 -754
  36. package/dist/editor/directives.d.ts +94 -0
  37. package/dist/editor/directives.js +206 -0
  38. package/dist/editor/document.d.ts +37 -2
  39. package/dist/editor/document.js +422 -6
  40. package/dist/editor/entityCatalog.d.ts +42 -0
  41. package/dist/editor/entityCatalog.js +101 -0
  42. package/dist/editor/environment.d.ts +74 -0
  43. package/dist/editor/environment.js +169 -0
  44. package/dist/editor/index.d.ts +26 -1
  45. package/dist/editor/index.js +8 -1
  46. package/dist/editor/minimap.d.ts +31 -0
  47. package/dist/editor/minimap.js +73 -0
  48. package/dist/editor/streamer.d.ts +66 -0
  49. package/dist/editor/streamer.js +68 -0
  50. package/dist/editor/types.d.ts +143 -0
  51. package/dist/editor/world.d.ts +136 -0
  52. package/dist/editor/world.js +303 -0
  53. package/dist/game/breeding.d.ts +161 -0
  54. package/dist/game/breeding.js +181 -0
  55. package/dist/game/cameraConfig.d.ts +451 -0
  56. package/dist/game/cameraConfig.js +1 -0
  57. package/dist/game/defineGame.d.ts +16 -9
  58. package/dist/game/defineGame.js +14 -6
  59. package/dist/game/defineSystem.d.ts +2 -0
  60. package/dist/game/defineSystem.js +2 -0
  61. package/dist/game/events.d.ts +28 -0
  62. package/dist/game/playableGame.d.ts +34 -435
  63. package/dist/game/playableGame.js +1 -1
  64. package/dist/game/progression.d.ts +35 -0
  65. package/dist/game/progression.js +19 -0
  66. package/dist/game/quest.js +1 -3
  67. package/dist/game/tribe.d.ts +184 -0
  68. package/dist/game/tribe.js +432 -0
  69. package/dist/gameplay.d.ts +3 -1
  70. package/dist/gameplay.js +3 -1
  71. package/dist/inventory/encumbrance.d.ts +69 -0
  72. package/dist/inventory/encumbrance.js +68 -0
  73. package/dist/item/durability.js +1 -3
  74. package/dist/meta/changelog.d.ts +1 -1
  75. package/dist/meta/changelog.js +24 -1
  76. package/dist/movement/movementModel.d.ts +15 -1
  77. package/dist/movement/movementModel.js +33 -15
  78. package/dist/movement/playerMovement.js +102 -2
  79. package/dist/multiplayer/combatSnapshot.js +13 -8
  80. package/dist/multiplayer/presenceModel.d.ts +2 -0
  81. package/dist/multiplayer/presenceModel.js +8 -0
  82. package/dist/physics/drivableVehicle.d.ts +2 -0
  83. package/dist/physics/drivableVehicle.js +1 -1
  84. package/dist/physics/flightDynamics.d.ts +82 -0
  85. package/dist/physics/flightDynamics.js +165 -0
  86. package/dist/physics/gravityField.d.ts +25 -0
  87. package/dist/physics/gravityField.js +41 -0
  88. package/dist/physics/kinematicVehicle.d.ts +87 -0
  89. package/dist/physics/kinematicVehicle.js +193 -11
  90. package/dist/physics/physicsWorld.d.ts +26 -0
  91. package/dist/physics/physicsWorld.js +109 -117
  92. package/dist/physics/vehicleObstacles.d.ts +44 -0
  93. package/dist/physics/vehicleObstacles.js +115 -0
  94. package/dist/random/rng.d.ts +4 -1
  95. package/dist/random/rng.js +4 -1
  96. package/dist/runtime/adapter.d.ts +13 -0
  97. package/dist/runtime/adapter.js +13 -0
  98. package/dist/runtime/context/combat.d.ts +51 -0
  99. package/dist/runtime/context/combat.js +130 -0
  100. package/dist/runtime/context/deathLoot.d.ts +30 -0
  101. package/dist/runtime/context/deathLoot.js +39 -0
  102. package/dist/runtime/context/player.d.ts +48 -0
  103. package/dist/runtime/context/player.js +125 -0
  104. package/dist/runtime/context/scene.d.ts +59 -0
  105. package/dist/runtime/context/scene.js +291 -0
  106. package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
  107. package/dist/runtime/context/snapshotCodecs.js +26 -0
  108. package/dist/runtime/descriptors/baseline.js +26 -3
  109. package/dist/runtime/descriptors/features.d.ts +5 -0
  110. package/dist/runtime/descriptors/features.js +20 -2
  111. package/dist/runtime/gameContext.d.ts +2 -428
  112. package/dist/runtime/gameContext.js +88 -501
  113. package/dist/runtime/gameContextTypes.d.ts +467 -0
  114. package/dist/runtime/gameContextTypes.js +1 -0
  115. package/dist/runtime/headlessRunner.d.ts +4 -2
  116. package/dist/runtime/headlessRunner.js +1 -0
  117. package/dist/runtime/hostPolicy.d.ts +51 -0
  118. package/dist/runtime/hostPolicy.js +68 -0
  119. package/dist/runtime/hostedGameRunner.d.ts +3 -1
  120. package/dist/runtime/hostedGameRunner.js +1 -0
  121. package/dist/runtime/hostedWorldSession.d.ts +3 -1
  122. package/dist/runtime/hostedWorldSession.js +1 -0
  123. package/dist/runtime/worldSnapshot.d.ts +13 -1
  124. package/dist/runtime/worldSnapshot.js +15 -3
  125. package/dist/scene/assetCatalog.d.ts +5 -0
  126. package/dist/scene/assetSpace.js +1 -3
  127. package/dist/scene/authoredTriggers.d.ts +50 -0
  128. package/dist/scene/authoredTriggers.js +114 -0
  129. package/dist/scene/builtinSceneKinds.js +8 -3
  130. package/dist/scene/colliders.d.ts +58 -0
  131. package/dist/scene/colliders.js +108 -0
  132. package/dist/scene/collisionMesh.d.ts +94 -0
  133. package/dist/scene/collisionMesh.js +699 -0
  134. package/dist/scene/companion.d.ts +147 -0
  135. package/dist/scene/companion.js +193 -0
  136. package/dist/scene/entityStats.d.ts +7 -9
  137. package/dist/scene/entityStats.js +10 -14
  138. package/dist/scene/sceneKinds.d.ts +16 -0
  139. package/dist/scene/sceneKinds.js +4 -0
  140. package/dist/scene/sceneRaycast.js +15 -0
  141. package/dist/stats/spawnLevelStats.d.ts +110 -0
  142. package/dist/stats/spawnLevelStats.js +140 -0
  143. package/dist/stats/statPool.d.ts +70 -0
  144. package/dist/stats/statPool.js +59 -0
  145. package/dist/store/changeSignal.d.ts +3 -0
  146. package/dist/store/changeSignal.js +2 -0
  147. package/dist/store/observableKeyedStore.d.ts +2 -0
  148. package/dist/store/observableKeyedStore.js +1 -0
  149. package/dist/survival/decayMeter.js +1 -3
  150. package/dist/survival/regionHealth.js +1 -3
  151. package/dist/ui/hudLayout.d.ts +5 -0
  152. package/dist/ui/hudLayout.js +17 -1
  153. package/dist/world/authoredEntities.d.ts +29 -0
  154. package/dist/world/authoredEntities.js +29 -0
  155. package/dist/world/authoredObjects.d.ts +15 -1
  156. package/dist/world/authoredObjects.js +13 -1
  157. package/dist/world/authoredSpawn.d.ts +39 -0
  158. package/dist/world/authoredSpawn.js +37 -0
  159. package/dist/world/buildSockets.d.ts +152 -0
  160. package/dist/world/buildSockets.js +188 -0
  161. package/dist/world/cityBlocks.d.ts +152 -0
  162. package/dist/world/cityBlocks.js +670 -0
  163. package/dist/world/cityContent.d.ts +77 -0
  164. package/dist/world/cityContent.js +261 -0
  165. package/dist/world/cityGeometry.d.ts +114 -0
  166. package/dist/world/cityGeometry.js +573 -0
  167. package/dist/world/cityKind.d.ts +314 -0
  168. package/dist/world/cityKind.js +1578 -0
  169. package/dist/world/features.d.ts +18 -3
  170. package/dist/world/features.js +10 -1
  171. package/dist/world/grassKind.js +2 -2
  172. package/dist/world/markers.d.ts +50 -4
  173. package/dist/world/markers.js +49 -0
  174. package/dist/world/massing.js +1 -1
  175. package/dist/world/minimapBake.d.ts +78 -0
  176. package/dist/world/minimapBake.js +208 -0
  177. package/dist/world/minimapTrack.d.ts +17 -0
  178. package/dist/world/minimapTrack.js +22 -0
  179. package/dist/world/pathNetwork.d.ts +159 -0
  180. package/dist/world/pathNetwork.js +684 -0
  181. package/dist/world/place.d.ts +166 -0
  182. package/dist/world/place.js +96 -0
  183. package/dist/world/placeAsset.js +3 -1
  184. package/dist/world/poleLineKind.d.ts +40 -0
  185. package/dist/world/poleLineKind.js +97 -0
  186. package/dist/world/polyline.js +9 -2
  187. package/dist/world/resourceNode.d.ts +109 -0
  188. package/dist/world/resourceNode.js +140 -0
  189. package/dist/world/scatter.js +2 -20
  190. package/dist/world/streets.d.ts +29 -0
  191. package/dist/world/streets.js +55 -0
  192. package/dist/world/terraform.js +58 -88
  193. package/dist/world/terrain.d.ts +45 -0
  194. package/dist/world/terrain.js +78 -0
  195. package/dist/world.d.ts +14 -5
  196. package/dist/world.js +12 -3
  197. package/package.json +1 -1
  198. package/skills/game-design/SKILL.md +28 -0
  199. package/skills/game-design/agents/openai.yaml +4 -0
  200. package/skills/game-design/references/application-playbook.md +170 -0
  201. package/skills/game-design/references/field-guide.md +288 -0
  202. package/skills/jgengine/SKILL.md +7 -4
  203. package/skills/jgengine/api.md +172 -79
  204. package/skills/jgengine/capabilities.md +104 -2
  205. package/skills/jgengine/recipes/minimal-game.md +88 -0
  206. package/skills/jgengine-combat/SKILL.md +5 -1
  207. package/skills/jgengine-combat/api.md +36 -0
  208. package/skills/jgengine-combat/capabilities.md +44 -0
  209. package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
  210. package/skills/jgengine-combat/reference.md +12 -2
  211. package/skills/jgengine-gameplay/api.md +152 -26
  212. package/skills/jgengine-gameplay/capabilities.md +78 -2
  213. package/skills/jgengine-gameplay/reference-systems.md +1 -5
  214. package/skills/jgengine-world/SKILL.md +13 -0
  215. package/skills/jgengine-world/api.md +419 -15
  216. package/skills/jgengine-world/capabilities.md +108 -2
  217. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  218. package/skills/jgengine-world/reference.md +43 -4
  219. package/skills/jgengine/reference-cartridge.md +0 -72
@@ -0,0 +1,140 @@
1
+ function clamp01(value) {
2
+ return value < 0 ? 0 : value > 1 ? 1 : value;
3
+ }
4
+ function normalizeLevel(level) {
5
+ const floored = Math.floor(level);
6
+ return floored < 1 ? 1 : floored;
7
+ }
8
+ /**
9
+ * Scatter `count` points one at a time across `statIds`, choosing each target uniformly from the
10
+ * injected rng. Mutates `into` in place; bounded by `count`. The rng draws the sole source of
11
+ * randomness, so an identical rng sequence reproduces an identical distribution.
12
+ */
13
+ function distributePoints(statIds, count, rng, into) {
14
+ const n = statIds.length;
15
+ if (n === 0 || count <= 0)
16
+ return;
17
+ for (let i = 0; i < count; i++) {
18
+ let index = Math.floor(rng() * n);
19
+ if (index >= n)
20
+ index = n - 1;
21
+ if (index < 0)
22
+ index = 0;
23
+ const stat = statIds[index];
24
+ into[stat] = (into[stat] ?? 0) + 1;
25
+ }
26
+ }
27
+ function zeroedPoints(statIds) {
28
+ const points = {};
29
+ for (const stat of statIds)
30
+ points[stat] = 0;
31
+ return points;
32
+ }
33
+ /**
34
+ * Roll a fresh wild creature at `level`: distribute `(level - 1)` points randomly across the
35
+ * species' stats using the injected rng and return a serializable {@link CreatureStatInstance}.
36
+ * Deterministic — the same rng sequence yields the same point spread. Levels below 1 clamp to 1
37
+ * (no points). Use `seededRng` from `../random/rng` for a persisted, reproducible stream.
38
+ *
39
+ * @capability spawn-level-roll distribute a wild creature's spawn level into random per-stat points
40
+ */
41
+ export function rollSpawnInstance(speciesId, species, level, rng) {
42
+ const statIds = Object.keys(species);
43
+ const points = zeroedPoints(statIds);
44
+ const normalized = normalizeLevel(level);
45
+ distributePoints(statIds, normalized - 1, rng, points);
46
+ return { speciesId, level: normalized, points, domesticLevels: 0 };
47
+ }
48
+ /**
49
+ * Resolve an instance's live stat values from its distributed points:
50
+ * `value = base * (1 + points * growth)` per stat. Stats with zero points return their base.
51
+ *
52
+ * @capability spawn-stat-values resolve a creature's per-stat values from its distributed points
53
+ */
54
+ export function resolveSpawnStats(species, instance) {
55
+ const out = {};
56
+ for (const stat of Object.keys(species)) {
57
+ const def = species[stat];
58
+ const pts = instance.points[stat] ?? 0;
59
+ out[stat] = def.base * (1 + pts * def.growth);
60
+ }
61
+ return out;
62
+ }
63
+ /**
64
+ * Bonus levels granted by a capture at the given effectiveness:
65
+ * `floor(level * clamp01(effectiveness) / 2)`. A perfect (1.0) capture grants half the wild
66
+ * level; a botched (0) capture grants none. Effectiveness outside `[0,1]` clamps.
67
+ *
68
+ * @capability capture-bonus-levels convert wild level and capture effectiveness into bonus levels
69
+ */
70
+ export function captureBonusLevels(level, effectiveness) {
71
+ return Math.floor((normalizeLevel(level) * clamp01(effectiveness)) / 2);
72
+ }
73
+ /**
74
+ * Apply a capture at the given effectiveness: compute bonus levels via {@link captureBonusLevels},
75
+ * distribute those extra points randomly with the injected rng, and return the post-capture
76
+ * instance (level raised to the tamed total) alongside the granted bonus levels and tamed level.
77
+ * The input instance is not mutated.
78
+ *
79
+ * @capability capture-effectiveness-tame apply capture effectiveness to grant and distribute bonus levels
80
+ */
81
+ export function applyCaptureEffectiveness(species, instance, effectiveness, rng) {
82
+ const bonusLevels = captureBonusLevels(instance.level, effectiveness);
83
+ const statIds = Object.keys(species);
84
+ const points = { ...instance.points };
85
+ distributePoints(statIds, bonusLevels, rng, points);
86
+ const tamedLevel = instance.level + bonusLevels;
87
+ return {
88
+ bonusLevels,
89
+ tamedLevel,
90
+ instance: { ...instance, level: tamedLevel, points },
91
+ };
92
+ }
93
+ /**
94
+ * Spend one earned post-capture level into `stat`, adding a single growth increment (one point)
95
+ * and raising both `level` and `domesticLevels`. When `maxDomesticLevels` is set and already
96
+ * reached, the spend is refused (`spent: false`) and the instance is returned untouched. Pure:
97
+ * returns a new instance rather than mutating the input.
98
+ *
99
+ * @capability domestic-level-spend spend an earned post-capture level into a chosen stat under a cap
100
+ */
101
+ export function spendDomesticLevel(instance, stat, options) {
102
+ const cap = options?.maxDomesticLevels;
103
+ if (cap !== undefined && instance.domesticLevels >= cap) {
104
+ return { instance, spent: false };
105
+ }
106
+ const points = { ...instance.points };
107
+ points[stat] = (points[stat] ?? 0) + 1;
108
+ return {
109
+ instance: {
110
+ ...instance,
111
+ points,
112
+ level: instance.level + 1,
113
+ domesticLevels: instance.domesticLevels + 1,
114
+ },
115
+ spent: true,
116
+ };
117
+ }
118
+ /**
119
+ * Deep-copy an instance into a detached, serializable record — the transport/save counterpart of
120
+ * {@link restoreSpawnInstance}. Copies the points map so later edits don't leak into the snapshot.
121
+ *
122
+ * @capability spawn-instance-snapshot deep-copy a creature stat instance into a serializable record
123
+ */
124
+ export function snapshotSpawnInstance(instance) {
125
+ return {
126
+ speciesId: instance.speciesId,
127
+ level: instance.level,
128
+ domesticLevels: instance.domesticLevels,
129
+ points: { ...instance.points },
130
+ };
131
+ }
132
+ /**
133
+ * Rehydrate a creature stat instance from decoded data (e.g. `JSON.parse` output), returning a
134
+ * fresh deep copy the caller owns. Inverse of {@link snapshotSpawnInstance}.
135
+ *
136
+ * @capability spawn-instance-restore rehydrate a creature stat instance from decoded snapshot data
137
+ */
138
+ export function restoreSpawnInstance(data) {
139
+ return snapshotSpawnInstance(data);
140
+ }
@@ -0,0 +1,70 @@
1
+ /** Serializable state for any bounded named resource. The caller defines what the resource means. */
2
+ export interface StatPool {
3
+ current: number;
4
+ max: number;
5
+ min: number;
6
+ }
7
+ /** Input used to create a normalized pool. `current` defaults to `max`; `min` defaults to zero. */
8
+ export interface StatPoolInput {
9
+ current?: number;
10
+ max: number;
11
+ min?: number;
12
+ }
13
+ /** Partial update applied without mutating the previous pool. */
14
+ export interface StatPoolPatch {
15
+ current?: number;
16
+ max?: number;
17
+ min?: number;
18
+ }
19
+ /**
20
+ * Structural bridge to caller-owned resource state. Implement it over an ECS,
21
+ * Redux/Zustand store, plain object, database-backed model, or JGengine's native
22
+ * entity stats without moving ownership into the SDK.
23
+ */
24
+ export interface StatPoolAccess<TOwnerId extends string = string, TStatId extends string = string> {
25
+ get(ownerId: TOwnerId, statId: TStatId): StatPool | null;
26
+ set(ownerId: TOwnerId, statId: TStatId, next: StatPool): unknown;
27
+ }
28
+ /** Inspectable result of a bounded pool change. */
29
+ export interface StatPoolChange {
30
+ previous: StatPool;
31
+ pool: StatPool;
32
+ /** Actual applied amount after clamping. */
33
+ applied: number;
34
+ hitMin: boolean;
35
+ hitMax: boolean;
36
+ }
37
+ /** Outcome of changing a pool through {@link StatPoolAccess}. */
38
+ export type StatPoolAccessResult = ({
39
+ status: "ok";
40
+ } & StatPoolChange) | {
41
+ status: "rejected";
42
+ reason: string;
43
+ };
44
+ /**
45
+ * Normalize caller data into a complete plain-data pool.
46
+ *
47
+ * @capability stat-pool create a serializable current/min/max pool for any caller-named resource
48
+ */
49
+ export declare function createStatPool(input: StatPoolInput): StatPool;
50
+ /**
51
+ * Apply new bounds/current to a pool, clamping the result and leaving the input untouched.
52
+ *
53
+ * @capability stat-pool-patch immutably retune a bounded resource's current value or bounds
54
+ */
55
+ export declare function patchStatPool(pool: StatPool, patch: StatPoolPatch): StatPool;
56
+ /**
57
+ * Pure bounded delta transition with the exact applied amount and boundary flags.
58
+ *
59
+ * @capability stat-pool-change immutably increase or decrease any named bounded resource with clamp evidence
60
+ */
61
+ export declare function changeStatPool(pool: StatPool, amount: number): StatPoolChange;
62
+ /**
63
+ * Read, transition, and write a caller-owned pool through the structural
64
+ * adapter. Positive amounts increase the resource; negative amounts decrease
65
+ * it. The adapter receives a complete replacement value, never a hidden
66
+ * mutation.
67
+ *
68
+ * @capability portable-stat-pool apply deterministic bounded resource changes through a caller-owned stat adapter
69
+ */
70
+ export declare function applyStatPoolDelta<TOwnerId extends string, TStatId extends string>(access: StatPoolAccess<TOwnerId, TStatId>, ownerId: TOwnerId, statId: TStatId, amount: number): StatPoolAccessResult;
@@ -0,0 +1,59 @@
1
+ import { clamp } from "../math/scalar.js";
2
+ /**
3
+ * Normalize caller data into a complete plain-data pool.
4
+ *
5
+ * @capability stat-pool create a serializable current/min/max pool for any caller-named resource
6
+ */
7
+ export function createStatPool(input) {
8
+ const min = input.min ?? 0;
9
+ return {
10
+ current: clamp(input.current ?? input.max, min, input.max),
11
+ max: input.max,
12
+ min,
13
+ };
14
+ }
15
+ /**
16
+ * Apply new bounds/current to a pool, clamping the result and leaving the input untouched.
17
+ *
18
+ * @capability stat-pool-patch immutably retune a bounded resource's current value or bounds
19
+ */
20
+ export function patchStatPool(pool, patch) {
21
+ const max = patch.max ?? pool.max;
22
+ const min = patch.min ?? pool.min;
23
+ return {
24
+ current: clamp(patch.current ?? pool.current, min, max),
25
+ max,
26
+ min,
27
+ };
28
+ }
29
+ /**
30
+ * Pure bounded delta transition with the exact applied amount and boundary flags.
31
+ *
32
+ * @capability stat-pool-change immutably increase or decrease any named bounded resource with clamp evidence
33
+ */
34
+ export function changeStatPool(pool, amount) {
35
+ const next = patchStatPool(pool, { current: pool.current + amount });
36
+ return {
37
+ previous: pool,
38
+ pool: next,
39
+ applied: next.current - pool.current,
40
+ hitMin: next.current === next.min,
41
+ hitMax: next.current === next.max,
42
+ };
43
+ }
44
+ /**
45
+ * Read, transition, and write a caller-owned pool through the structural
46
+ * adapter. Positive amounts increase the resource; negative amounts decrease
47
+ * it. The adapter receives a complete replacement value, never a hidden
48
+ * mutation.
49
+ *
50
+ * @capability portable-stat-pool apply deterministic bounded resource changes through a caller-owned stat adapter
51
+ */
52
+ export function applyStatPoolDelta(access, ownerId, statId, amount) {
53
+ const pool = access.get(ownerId, statId);
54
+ if (pool === null)
55
+ return { status: "rejected", reason: `unknown stat "${statId}"` };
56
+ const change = changeStatPool(pool, amount);
57
+ access.set(ownerId, statId, change.pool);
58
+ return { status: "ok", ...change };
59
+ }
@@ -1,7 +1,10 @@
1
+ /** @internal */
1
2
  export interface ChangeSignal {
2
3
  subscribe(listener: () => void): () => void;
3
4
  notify(): void;
4
5
  version(): number;
5
6
  }
7
+ /** @internal */
6
8
  export declare function createChangeSignal(): ChangeSignal;
9
+ /** @internal */
7
10
  export declare function notifyAfter<T extends object, K extends keyof T>(target: T, methods: readonly K[], notify: () => void): T;
@@ -1,3 +1,4 @@
1
+ /** @internal */
1
2
  export function createChangeSignal() {
2
3
  const listeners = new Set();
3
4
  let version = 0;
@@ -18,6 +19,7 @@ export function createChangeSignal() {
18
19
  },
19
20
  };
20
21
  }
22
+ /** @internal */
21
23
  export function notifyAfter(target, methods, notify) {
22
24
  const wrapped = { ...target };
23
25
  for (const method of methods) {
@@ -1,3 +1,4 @@
1
+ /** @internal */
1
2
  export interface ObservableKeyedStore<T> {
2
3
  set(key: string, value: T): void;
3
4
  delete(key: string): void;
@@ -16,4 +17,5 @@ export interface ObservableKeyedStore<T> {
16
17
  /** Replace all entries with `data` (clears keys absent from it), emitting once. */
17
18
  hydrate(data: readonly (readonly [string, T])[]): void;
18
19
  }
20
+ /** @internal */
19
21
  export declare function createObservableKeyedStore<T>(areEqual?: (previous: T, next: T) => boolean): ObservableKeyedStore<T>;
@@ -1,3 +1,4 @@
1
+ /** @internal */
1
2
  export function createObservableKeyedStore(areEqual) {
2
3
  const store = new Map();
3
4
  const listeners = new Set();
@@ -1,6 +1,4 @@
1
- function clamp(value, min, max) {
2
- return value < min ? min : value > max ? max : value;
3
- }
1
+ import { clamp } from "../math/scalar.js";
4
2
  function meterMin(def) {
5
3
  return def.min ?? 0;
6
4
  }
@@ -1,6 +1,4 @@
1
- function clamp(value, min, max) {
2
- return value < min ? min : value > max ? max : value;
3
- }
1
+ import { clamp } from "../math/scalar.js";
4
2
  /**
5
3
  * Per-region/limb health tracked separately, so each body part takes and heals damage on its own.
6
4
  *
@@ -86,5 +86,10 @@ export declare function rectFromPlacement(placement: HudPlacement, size: HudSize
86
86
  export declare function registerActiveHudLayout(store: HudLayoutStore): () => void;
87
87
  /** @internal */
88
88
  export declare function listActiveHudLayouts(): HudLayoutStore[];
89
+ /**
90
+ * @internal Notified whenever a `HudCanvas` mounts or unmounts (the active-layout set changes).
91
+ * Lets the editor's HUD-layout surface enable editing on layouts that mount after it opens.
92
+ */
93
+ export declare function subscribeActiveHudLayouts(listener: () => void): () => void;
89
94
  /** @internal */
90
95
  export declare function createHudLayout(options?: HudLayoutOptions): HudLayoutStore;
@@ -116,17 +116,33 @@ function parseSerialized(raw) {
116
116
  return out;
117
117
  }
118
118
  const activeHudLayouts = new Set();
119
+ const activeHudLayoutListeners = new Set();
119
120
  /** @internal Agent bridge seam — HudCanvas registers its store while mounted so headless drivers can reach canvas mode. */
120
121
  export function registerActiveHudLayout(store) {
121
122
  activeHudLayouts.add(store);
123
+ for (const listener of [...activeHudLayoutListeners])
124
+ listener();
122
125
  return () => {
123
- activeHudLayouts.delete(store);
126
+ if (!activeHudLayouts.delete(store))
127
+ return;
128
+ for (const listener of [...activeHudLayoutListeners])
129
+ listener();
124
130
  };
125
131
  }
126
132
  /** @internal */
127
133
  export function listActiveHudLayouts() {
128
134
  return [...activeHudLayouts];
129
135
  }
136
+ /**
137
+ * @internal Notified whenever a `HudCanvas` mounts or unmounts (the active-layout set changes).
138
+ * Lets the editor's HUD-layout surface enable editing on layouts that mount after it opens.
139
+ */
140
+ export function subscribeActiveHudLayouts(listener) {
141
+ activeHudLayoutListeners.add(listener);
142
+ return () => {
143
+ activeHudLayoutListeners.delete(listener);
144
+ };
145
+ }
130
146
  function panelToUi(panel) {
131
147
  const layout = {
132
148
  anchor: panel.placement.anchor,
@@ -0,0 +1,29 @@
1
+ import { ENTITY_MARKER_KINDS, type AuthoredObjectMarkerLike } from "./authoredObjects.js";
2
+ /** Marker kinds {@link authoredEntitySpawns} treats as spawnable entities by default. Re-exported from `authoredObjects` as the single source of truth (the same kinds `resolveAuthoredObjects` skips). */
3
+ export { ENTITY_MARKER_KINDS };
4
+ /** The minimal marker shape entity spawns read; any `EditorMarker` satisfies it. */
5
+ export type AuthoredEntityMarkerLike = AuthoredObjectMarkerLike;
6
+ /** The minimal document shape {@link authoredEntitySpawns} walks; any `EditorDocument` satisfies it. */
7
+ export interface AuthoredEntityDocumentLike {
8
+ markers: readonly AuthoredEntityMarkerLike[];
9
+ }
10
+ /** One resolved entity to spawn from the authored scene — its marker id, catalog id, and pose. */
11
+ export interface AuthoredEntitySpawn {
12
+ /** The marker's id — a stable spawn id, so re-spawning the same marker is idempotent. */
13
+ markerId: string;
14
+ /** The entity catalog id to spawn (join key into `content.entityById`). */
15
+ catalogId: string;
16
+ /** World-space spawn position as a spawn-ready `[x, y, z]` tuple. */
17
+ position: [number, number, number];
18
+ /** Facing (yaw radians), 0 when the marker has none. */
19
+ rotationY: number;
20
+ }
21
+ /**
22
+ * Every authored entity marker (`mob`/`boss` by default) that carries a `catalogId`, as a spawn plan
23
+ * the runtime feeds to `ctx.scene.entity.spawn(catalogId, { id, position })`. Reads the same document
24
+ * the editor authors, so placing a mob and setting its catalog id in the editor is all it takes to
25
+ * spawn it — no coordinates or spawn tables copied into game code. Markers without a catalog id are
26
+ * skipped (they are placement-only, e.g. a boss arena marker a game handles specially).
27
+ * @capability authored-entities spawn authored mob/boss markers from the editor document at runtime
28
+ */
29
+ export declare function authoredEntitySpawns(document: AuthoredEntityDocumentLike, kinds?: readonly string[]): AuthoredEntitySpawn[];
@@ -0,0 +1,29 @@
1
+ import { ENTITY_MARKER_KINDS, markerCatalogId } from "./authoredObjects.js";
2
+ /** Marker kinds {@link authoredEntitySpawns} treats as spawnable entities by default. Re-exported from `authoredObjects` as the single source of truth (the same kinds `resolveAuthoredObjects` skips). */
3
+ export { ENTITY_MARKER_KINDS };
4
+ /**
5
+ * Every authored entity marker (`mob`/`boss` by default) that carries a `catalogId`, as a spawn plan
6
+ * the runtime feeds to `ctx.scene.entity.spawn(catalogId, { id, position })`. Reads the same document
7
+ * the editor authors, so placing a mob and setting its catalog id in the editor is all it takes to
8
+ * spawn it — no coordinates or spawn tables copied into game code. Markers without a catalog id are
9
+ * skipped (they are placement-only, e.g. a boss arena marker a game handles specially).
10
+ * @capability authored-entities spawn authored mob/boss markers from the editor document at runtime
11
+ */
12
+ export function authoredEntitySpawns(document, kinds = ENTITY_MARKER_KINDS) {
13
+ const wanted = new Set(kinds);
14
+ const spawns = [];
15
+ for (const marker of document.markers) {
16
+ if (!wanted.has(marker.kind))
17
+ continue;
18
+ const catalogId = markerCatalogId(marker);
19
+ if (catalogId === null)
20
+ continue;
21
+ spawns.push({
22
+ markerId: marker.id,
23
+ catalogId,
24
+ position: [marker.position.x, marker.position.y, marker.position.z],
25
+ rotationY: marker.rotationY ?? 0,
26
+ });
27
+ }
28
+ return spawns;
29
+ }
@@ -42,12 +42,26 @@ export interface PlaceAuthoredObjectsOptions {
42
42
  * @capability authored-objects place catalog mesh props from an editor document
43
43
  */
44
44
  export declare function markerCatalogId(marker: AuthoredObjectMarkerLike): string | null;
45
+ /**
46
+ * Marker kinds that are spawnable entities (materialized by `authoredEntitySpawns`), not static
47
+ * catalog props. {@link resolveAuthoredObjects} skips them by default so a placed `mob`/`boss` is
48
+ * spawned once as an entity and never also placed as a static mesh.
49
+ * @capability authored-objects marker kinds resolved as entity spawns, not static props
50
+ */
51
+ export declare const ENTITY_MARKER_KINDS: readonly string[];
52
+ /** Options for {@link resolveAuthoredObjects}. */
53
+ export interface ResolveAuthoredObjectsOptions {
54
+ /** Marker kinds to skip (they are handled elsewhere). Default {@link ENTITY_MARKER_KINDS}. */
55
+ excludeKinds?: readonly string[];
56
+ }
45
57
  /**
46
58
  * Every marker carrying a catalog id, as placeable props — pure, no terrain sample. Parallel to
47
59
  * {@link resolveScatter}: games and headless tests read the same list `<AuthoredObjects>` places.
60
+ * Entity-spawn kinds (`mob`/`boss`) are skipped by default — they carry a `catalogId` for their
61
+ * entity definition, but are spawned via `authoredEntitySpawns`, not placed as static meshes.
48
62
  * @capability authored-objects place catalog mesh props from an editor document
49
63
  */
50
- export declare function resolveAuthoredObjects(document: AuthoredObjectsDocumentLike): AuthoredObject[];
64
+ export declare function resolveAuthoredObjects(document: AuthoredObjectsDocumentLike, options?: ResolveAuthoredObjectsOptions): AuthoredObject[];
51
65
  /**
52
66
  * Places resolved authored objects into an object store, grounding each on `sampleHeight(x,z)` plus
53
67
  * per-object and options vertical offsets. Returns the instance ids that were placed (or kept).
@@ -22,14 +22,26 @@ function markerVerticalOffset(marker) {
22
22
  metaNumber(marker.meta, "yOffset");
23
23
  return fromMeta ?? 0;
24
24
  }
25
+ /**
26
+ * Marker kinds that are spawnable entities (materialized by `authoredEntitySpawns`), not static
27
+ * catalog props. {@link resolveAuthoredObjects} skips them by default so a placed `mob`/`boss` is
28
+ * spawned once as an entity and never also placed as a static mesh.
29
+ * @capability authored-objects marker kinds resolved as entity spawns, not static props
30
+ */
31
+ export const ENTITY_MARKER_KINDS = ["mob", "boss"];
25
32
  /**
26
33
  * Every marker carrying a catalog id, as placeable props — pure, no terrain sample. Parallel to
27
34
  * {@link resolveScatter}: games and headless tests read the same list `<AuthoredObjects>` places.
35
+ * Entity-spawn kinds (`mob`/`boss`) are skipped by default — they carry a `catalogId` for their
36
+ * entity definition, but are spawned via `authoredEntitySpawns`, not placed as static meshes.
28
37
  * @capability authored-objects place catalog mesh props from an editor document
29
38
  */
30
- export function resolveAuthoredObjects(document) {
39
+ export function resolveAuthoredObjects(document, options = {}) {
40
+ const excluded = new Set(options.excludeKinds ?? ENTITY_MARKER_KINDS);
31
41
  const out = [];
32
42
  for (const marker of document.markers) {
43
+ if (excluded.has(marker.kind))
44
+ continue;
33
45
  const catalogId = markerCatalogId(marker);
34
46
  if (catalogId === null)
35
47
  continue;
@@ -0,0 +1,39 @@
1
+ import type { SceneMarkerLike } from "./sceneShapes.js";
2
+ /** Minimal marker shape spawn queries read; any `EditorMarker` satisfies it. */
3
+ export interface AuthoredSpawnMarkerLike extends SceneMarkerLike {
4
+ rotationY?: number;
5
+ }
6
+ /** Minimal document shape spawn queries walk; any `EditorDocument` satisfies it. */
7
+ export interface AuthoredSpawnDocumentLike {
8
+ markers: readonly AuthoredSpawnMarkerLike[];
9
+ }
10
+ /** Marker kind {@link authoredSpawnPosition} resolves by default. */
11
+ export declare const PLAYER_SPAWN_KIND = "player_spawn";
12
+ /**
13
+ * Every marker of `kind`, in document order — the generic query behind gameplay that references
14
+ * authored markers by kind instead of copying coordinates into code.
15
+ * @capability authored-spawn read spawn points and markers from the editor document
16
+ */
17
+ export declare function markersOfKind<TMarker extends AuthoredSpawnMarkerLike>(document: {
18
+ markers: readonly TMarker[];
19
+ }, kind: string): TMarker[];
20
+ /** Options for {@link authoredSpawnPosition}: pick a specific marker id or a non-default kind. */
21
+ export interface AuthoredSpawnOptions {
22
+ /** Marker kind to resolve. Default {@link PLAYER_SPAWN_KIND}. */
23
+ kind?: string;
24
+ /** Exact marker id; when set, kind only filters among markers with this id's kind. */
25
+ id?: string;
26
+ }
27
+ /**
28
+ * Position of the authored spawn marker as a spawn-ready `[x, y, z]` tuple, or null when the
29
+ * document has none. Reads the first `player_spawn` marker by default, so dragging the marker in
30
+ * the editor moves where players spawn — no coordinates copied into game code.
31
+ * @capability authored-spawn read spawn points and markers from the editor document
32
+ */
33
+ export declare function authoredSpawnPosition(document: AuthoredSpawnDocumentLike, options?: AuthoredSpawnOptions): [number, number, number] | null;
34
+ /**
35
+ * Facing (yaw radians) of the authored spawn marker, or 0 when the document has none — pair with
36
+ * {@link authoredSpawnPosition} to spawn players where and how the editor placed them.
37
+ * @capability authored-spawn read spawn points and markers from the editor document
38
+ */
39
+ export declare function authoredSpawnRotation(document: AuthoredSpawnDocumentLike, options?: AuthoredSpawnOptions): number;
@@ -0,0 +1,37 @@
1
+ /** Marker kind {@link authoredSpawnPosition} resolves by default. */
2
+ export const PLAYER_SPAWN_KIND = "player_spawn";
3
+ /**
4
+ * Every marker of `kind`, in document order — the generic query behind gameplay that references
5
+ * authored markers by kind instead of copying coordinates into code.
6
+ * @capability authored-spawn read spawn points and markers from the editor document
7
+ */
8
+ export function markersOfKind(document, kind) {
9
+ return document.markers.filter((marker) => marker.kind === kind);
10
+ }
11
+ /**
12
+ * Position of the authored spawn marker as a spawn-ready `[x, y, z]` tuple, or null when the
13
+ * document has none. Reads the first `player_spawn` marker by default, so dragging the marker in
14
+ * the editor moves where players spawn — no coordinates copied into game code.
15
+ * @capability authored-spawn read spawn points and markers from the editor document
16
+ */
17
+ export function authoredSpawnPosition(document, options = {}) {
18
+ const kind = options.kind ?? PLAYER_SPAWN_KIND;
19
+ const marker = options.id === undefined
20
+ ? document.markers.find((entry) => entry.kind === kind)
21
+ : document.markers.find((entry) => entry.id === options.id && entry.kind === kind);
22
+ if (marker === undefined)
23
+ return null;
24
+ return [marker.position.x, marker.position.y, marker.position.z];
25
+ }
26
+ /**
27
+ * Facing (yaw radians) of the authored spawn marker, or 0 when the document has none — pair with
28
+ * {@link authoredSpawnPosition} to spawn players where and how the editor placed them.
29
+ * @capability authored-spawn read spawn points and markers from the editor document
30
+ */
31
+ export function authoredSpawnRotation(document, options = {}) {
32
+ const kind = options.kind ?? PLAYER_SPAWN_KIND;
33
+ const marker = options.id === undefined
34
+ ? document.markers.find((entry) => entry.kind === kind)
35
+ : document.markers.find((entry) => entry.id === options.id && entry.kind === kind);
36
+ return marker?.rotationY ?? 0;
37
+ }