@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,130 @@
1
+ import { createDeathSystem, deathReasonFromEffect } from "../../combat/death.js";
2
+ import { createEffectSystem, } from "../../combat/effects.js";
3
+ import { createProjectileSystem } from "../../combat/projectiles.js";
4
+ import { createVfxInstanceStore } from "../../game/vfxInstance.js";
5
+ import { notifyAfter } from "../../store/changeSignal.js";
6
+ import { createCombatFx } from "./combatFx.js";
7
+ import { applyLethalLoot, isLocalPlayerKill } from "./deathLoot.js";
8
+ /** @internal */
9
+ export function createCombatSubsystem(d) {
10
+ const { content, signalNotify, now, events, time, entities, objects, combatSpatial, sceneRaycast, entityCollidersOf, objectCollidersOf, catalogEntry, statsByInstance, weapon, loot, lootRegistry, spawnWorldItem, despawnEntity, runCommand, localUserId, } = d;
11
+ const death = createDeathSystem({
12
+ resolveOnDeath: (instanceId) => catalogEntry(instanceId)?.onDeath,
13
+ resolveIdentity(instanceId) {
14
+ const entity = entities.get(instanceId);
15
+ if (entity === null)
16
+ return null;
17
+ return {
18
+ catalogId: entity.name,
19
+ position: [entity.position[0], entity.position[1], entity.position[2]],
20
+ };
21
+ },
22
+ loot: { roll: (tableId) => (lootRegistry.has(tableId) ? lootRegistry.roll(tableId) : []) },
23
+ events,
24
+ runCommand(name, args) {
25
+ runCommand(name, args);
26
+ },
27
+ despawn(instanceId) {
28
+ despawnEntity(instanceId);
29
+ },
30
+ });
31
+ const effects = notifyAfter(createEffectSystem({
32
+ resolveReceive: (instanceId) => catalogEntry(instanceId)?.receive,
33
+ resolveStats: (instanceId) => statsByInstance.get(instanceId),
34
+ getStat: weapon.getStat,
35
+ spatial: combatSpatial,
36
+ onLethal(instanceId, lethalCtx) {
37
+ // Capture identity + onDeath *before* resolveDeath despawns the entity.
38
+ const dyingEntity = entities.get(instanceId);
39
+ const catalogId = dyingEntity?.name;
40
+ const position = dyingEntity?.position;
41
+ const onDeath = catalogEntry(instanceId)?.onDeath;
42
+ const reason = deathReasonFromEffect({
43
+ ...lethalCtx,
44
+ userIdOf: (id) => (id === localUserId ? localUserId : undefined),
45
+ });
46
+ const resolution = death.resolveDeath(instanceId, reason);
47
+ if (resolution.status !== "resolved")
48
+ return;
49
+ applyLethalLoot({
50
+ drops: resolution.drops,
51
+ grantToLocalPlayer: isLocalPlayerKill(lethalCtx, localUserId),
52
+ onDeath,
53
+ position,
54
+ catalogId,
55
+ content,
56
+ spawnWorldItem,
57
+ grantToPlayer: loot.grantToPlayer,
58
+ localUserId,
59
+ });
60
+ },
61
+ }), ["applyEffect"], signalNotify);
62
+ const combatFx = createCombatFx({
63
+ entities,
64
+ events,
65
+ time,
66
+ applyEffect: (input) => effects.applyEffect(input),
67
+ });
68
+ const vfxInstances = createVfxInstanceStore({
69
+ onOp: (op) => events.emit("combat.vfxInstance", op),
70
+ now: () => time.now() * 1000,
71
+ });
72
+ const floatingEffects = {
73
+ canReceive: effects.canReceive,
74
+ preview: effects.preview,
75
+ applyEffect: combatFx.applyEffectAndFloat,
76
+ };
77
+ const projectileObstacles = d.physics?.projectileObstacles === true;
78
+ const projectileFilterFor = (filter) => ({
79
+ entities: true,
80
+ objects: projectileObstacles,
81
+ terrain: false,
82
+ walls: projectileObstacles,
83
+ ...filter,
84
+ });
85
+ const projectileSceneRaycast = {
86
+ raycast(input) {
87
+ return sceneRaycast.raycast({ ...input, filter: projectileFilterFor(input.filter) });
88
+ },
89
+ raycastAll(input) {
90
+ return sceneRaycast.raycastAll({ ...input, filter: projectileFilterFor(input.filter) });
91
+ },
92
+ };
93
+ const projectiles = notifyAfter(createProjectileSystem({
94
+ effects: floatingEffects,
95
+ spatial: combatSpatial,
96
+ getStat: weapon.getStat,
97
+ sceneRaycast: projectileSceneRaycast,
98
+ objects: projectileObstacles
99
+ ? {
100
+ list: () => objects.list(),
101
+ inBox: (min, max) => objects.inBox(min, max),
102
+ halfExtents: (catalogId) => {
103
+ const half = content.objectById?.(catalogId)?.halfExtents;
104
+ return half === undefined ? null : [half[0], half[1], half[2]];
105
+ },
106
+ collidersOf: objectCollidersOf,
107
+ }
108
+ : undefined,
109
+ entityCollidersOf,
110
+ rotationYOf: (instanceId) => entities.get(instanceId)?.rotationY,
111
+ now,
112
+ onSettle(report) {
113
+ events.emit("projectile.settled", {
114
+ from: report.from,
115
+ origin: [report.origin[0], report.origin[1], report.origin[2]],
116
+ at: [report.at[0], report.at[1], report.at[2]],
117
+ effect: report.effect,
118
+ hit: report.hit,
119
+ });
120
+ },
121
+ }), ["fireProjectile", "settleProjectile"], signalNotify);
122
+ return {
123
+ death,
124
+ effects,
125
+ floatingEffects,
126
+ projectiles,
127
+ combatFx,
128
+ vfxInstances,
129
+ };
130
+ }
@@ -0,0 +1,30 @@
1
+ import { type EffectDeathContext, type OnDeathSpec } from "../../combat/death.js";
2
+ import type { Drop } from "../../game/lootTable.js";
3
+ import { type WorldItemSpawnInput } from "../../game/worldItem.js";
4
+ import type { GameContextContent } from "../gameContext.js";
5
+ /** @internal Inputs for routing a resolved lethal kill into bag grants and/or ground-item spawns. */
6
+ export interface LethalLootInput {
7
+ /** Drops produced by the death system's resolution (already rolled). */
8
+ drops: readonly Drop[];
9
+ /** True when the killer is the local/host player this context is resolving loot for. */
10
+ grantToLocalPlayer: boolean;
11
+ /** Catalog `onDeath` for the dying entity (drop mode + scatter). */
12
+ onDeath: OnDeathSpec | undefined;
13
+ /** World position of the dying entity — required for `dropMode: "world"`. */
14
+ position: readonly [number, number, number] | undefined;
15
+ /** Catalog id of the dying entity — used as loot source tag when present. */
16
+ catalogId: string | undefined;
17
+ content: GameContextContent;
18
+ spawnWorldItem: (input: WorldItemSpawnInput) => void;
19
+ grantToPlayer: (userId: string, drops: Drop[], source?: string) => void;
20
+ localUserId: string;
21
+ }
22
+ /**
23
+ * Pure death→loot policy: when a kill yields drops for the local player, either scatter them as
24
+ * world items (`dropMode: "world"`) or grant straight into bags. Extracted from `createGameContext`
25
+ * so combat install stays free of nested loot branching.
26
+ * @internal
27
+ */
28
+ export declare function applyLethalLoot(input: LethalLootInput): void;
29
+ /** @internal Build the grant-to-local-player flag from a lethal effect context + local user id. */
30
+ export declare function isLocalPlayerKill(lethalCtx: EffectDeathContext, localUserId: string): boolean;
@@ -0,0 +1,39 @@
1
+ import { deathReasonFromEffect, normalizeOnDeath, } from "../../combat/death.js";
2
+ import { DEFAULT_RARITY, resolveDeathDrops, } from "../../game/worldItem.js";
3
+ /**
4
+ * Pure death→loot policy: when a kill yields drops for the local player, either scatter them as
5
+ * world items (`dropMode: "world"`) or grant straight into bags. Extracted from `createGameContext`
6
+ * so combat install stays free of nested loot branching.
7
+ * @internal
8
+ */
9
+ export function applyLethalLoot(input) {
10
+ if (input.drops.length === 0 || !input.grantToLocalPlayer)
11
+ return;
12
+ const normalizedOnDeath = normalizeOnDeath(input.onDeath);
13
+ if (normalizedOnDeath.dropMode === "world" && input.position !== undefined) {
14
+ const resolved = resolveDeathDrops([...input.drops], {
15
+ mode: "world",
16
+ origin: input.position,
17
+ resolveRarity: (itemId) => input.content.itemById?.(itemId)?.rarity ?? DEFAULT_RARITY,
18
+ resolveBaseType: (itemId) => input.content.itemById?.(itemId)?.baseType ?? itemId,
19
+ scatter: normalizedOnDeath.scatter,
20
+ ...(input.catalogId !== undefined ? { source: input.catalogId } : {}),
21
+ });
22
+ for (const spawn of resolved.worldSpawns)
23
+ input.spawnWorldItem(spawn);
24
+ if (resolved.grants.length > 0) {
25
+ input.grantToPlayer(input.localUserId, resolved.grants, input.catalogId);
26
+ }
27
+ }
28
+ else {
29
+ input.grantToPlayer(input.localUserId, [...input.drops], input.catalogId);
30
+ }
31
+ }
32
+ /** @internal Build the grant-to-local-player flag from a lethal effect context + local user id. */
33
+ export function isLocalPlayerKill(lethalCtx, localUserId) {
34
+ const reason = deathReasonFromEffect({
35
+ ...lethalCtx,
36
+ userIdOf: (id) => (id === localUserId ? localUserId : undefined),
37
+ });
38
+ return reason.kind === "player_kill" && reason.killerUserId === localUserId;
39
+ }
@@ -0,0 +1,48 @@
1
+ import { type WalletState } from "../../economy/wallet.js";
2
+ import type { GameFeatures } from "../../game/defineGame.js";
3
+ import type { InventoryDeclaration } from "../../game/defineGame.js";
4
+ import type { GameEvents } from "../../game/events.js";
5
+ import { type Loadouts } from "../../game/loadout.js";
6
+ import { type InventoryLayout, type InventorySet } from "../../inventory/inventoryModel.js";
7
+ import { type PoseState } from "../../movement/poseState.js";
8
+ import { type Possession } from "../../scene/possession.js";
9
+ import type { EntityStore } from "../../scene/entityStore.js";
10
+ import { type StatValueMap } from "../../scene/entityStats.js";
11
+ import { type Stats } from "../../stats/statModifiers.js";
12
+ import { type MotionIntents } from "../motionIntents.js";
13
+ import type { GameContextEconomy, GameContextEntityEntry } from "../gameContext.js";
14
+ /** @internal Wiring player-owned state needs from the live context. */
15
+ export interface PlayerSubsystemDeps {
16
+ signalNotify: () => void;
17
+ inventoryDeclarations: Record<string, InventoryDeclaration>;
18
+ entities: EntityStore;
19
+ events: GameEvents;
20
+ catalogEntry: (instanceId: string) => GameContextEntityEntry | null | undefined;
21
+ ensureInstanceStats: (instanceId: string) => StatValueMap;
22
+ /** Reads another feature after descriptor install (loadout → unlocks). */
23
+ feature: <T>(key: keyof GameFeatures) => T | undefined;
24
+ }
25
+ /** @internal Inventory, wallets, pose, motion, loadout — per-player state surfaces. */
26
+ export interface PlayerSubsystem {
27
+ inventoryIds: readonly string[];
28
+ inventoryByUser: ReadonlyMap<string, InventorySet<string>>;
29
+ inventoryFor: (userId: string) => InventorySet<string>;
30
+ layouts: Record<string, InventoryLayout>;
31
+ wallets: Map<string, WalletState>;
32
+ walletOf: (userId: string) => WalletState;
33
+ economy: GameContextEconomy;
34
+ putIntoAnyInventory: (userId: string, itemId: string, count: number) => void;
35
+ playerStatsFor: (userId: string) => Stats<string>;
36
+ pose: PoseState;
37
+ possession: Possession;
38
+ motionByUser: ReadonlyMap<string, MotionIntents>;
39
+ motionFor: (userId: string) => MotionIntents;
40
+ loadouts: Loadouts;
41
+ seedUserPool: (userId: string, statId: string, pool: {
42
+ current: number;
43
+ max?: number;
44
+ min?: number;
45
+ }) => void;
46
+ }
47
+ /** @internal */
48
+ export declare function createPlayerSubsystem(d: PlayerSubsystemDeps): PlayerSubsystem;
@@ -0,0 +1,125 @@
1
+ import { balance as walletBalance, charge as walletCharge, createEmptyWallet, grant as walletGrant, isOverdrawn as walletIsOverdrawn, } from "../../economy/wallet.js";
2
+ import { createLoadouts } from "../../game/loadout.js";
3
+ import { createInventorySet, putItem, } from "../../inventory/inventoryModel.js";
4
+ import { createPoseState } from "../../movement/poseState.js";
5
+ import { createPossession } from "../../scene/possession.js";
6
+ import { setStatValue } from "../../scene/entityStats.js";
7
+ import { createStats } from "../../stats/statModifiers.js";
8
+ import { notifyAfter } from "../../store/changeSignal.js";
9
+ import { createMotionIntents } from "../motionIntents.js";
10
+ /** @internal */
11
+ export function createPlayerSubsystem(d) {
12
+ const { signalNotify, entities, events, catalogEntry, ensureInstanceStats, feature } = d;
13
+ const playerStatsByUser = new Map();
14
+ function playerStatsFor(userId) {
15
+ let stats = playerStatsByUser.get(userId);
16
+ if (stats === undefined) {
17
+ stats = createStats({});
18
+ playerStatsByUser.set(userId, stats);
19
+ }
20
+ return stats;
21
+ }
22
+ const pose = createPoseState((instanceId) => catalogEntry(instanceId)?.movement);
23
+ const possession = notifyAfter(createPossession({ entities, events }), ["possess", "own", "disown"], signalNotify);
24
+ const inventoryDeclarations = d.inventoryDeclarations;
25
+ const inventoryIds = Object.keys(inventoryDeclarations);
26
+ const layouts = {};
27
+ for (const [inventoryId, declaration] of Object.entries(inventoryDeclarations)) {
28
+ layouts[inventoryId] = { slots: declaration.slots, accepts: declaration.accepts };
29
+ }
30
+ const traits = Object.values(inventoryDeclarations).find((declaration) => declaration.traits !== undefined)
31
+ ?.traits ?? { stackLimit: () => Number.POSITIVE_INFINITY };
32
+ const inventoryByUser = new Map();
33
+ function inventoryFor(userId) {
34
+ let set = inventoryByUser.get(userId);
35
+ if (set === undefined) {
36
+ set = notifyAfter(createInventorySet(layouts, traits), ["put", "take", "move", "replaceState"], signalNotify);
37
+ inventoryByUser.set(userId, set);
38
+ }
39
+ return set;
40
+ }
41
+ const wallets = new Map();
42
+ const walletOf = (userId) => wallets.get(userId) ?? createEmptyWallet();
43
+ const economy = {
44
+ balance: (userId, currencyId) => walletBalance(walletOf(userId), currencyId),
45
+ grant(userId, currencyId, amount) {
46
+ wallets.set(userId, walletGrant(walletOf(userId), currencyId, amount));
47
+ signalNotify();
48
+ },
49
+ charge(userId, currencyId, amount, options) {
50
+ const result = walletCharge(walletOf(userId), currencyId, amount, options);
51
+ if (result.status === "rejected")
52
+ return { reason: result.reason };
53
+ wallets.set(userId, result.state);
54
+ signalNotify();
55
+ return null;
56
+ },
57
+ isOverdrawn: (userId, currencyId) => walletIsOverdrawn(walletOf(userId), currencyId),
58
+ };
59
+ function putIntoAnyInventory(userId, itemId, count) {
60
+ const inventory = inventoryFor(userId);
61
+ for (const inventoryId of inventoryIds) {
62
+ if (inventory.put(inventoryId, itemId, count).status === "ok")
63
+ return;
64
+ }
65
+ }
66
+ function seedUserPool(userId, statId, pool) {
67
+ const map = ensureInstanceStats(userId);
68
+ const next = setStatValue(map, statId, pool);
69
+ map[statId] = next[statId];
70
+ }
71
+ const loadouts = notifyAfter(createLoadouts({
72
+ inventory: {
73
+ begin(userId) {
74
+ const inventory = inventoryFor(userId);
75
+ const staged = new Map();
76
+ return {
77
+ put(inventoryId, itemId, count, slot) {
78
+ const layout = layouts[inventoryId];
79
+ if (layout === undefined)
80
+ return { reason: `unknown inventory "${inventoryId}"` };
81
+ const state = staged.get(inventoryId) ?? inventory.state(inventoryId);
82
+ const result = putItem(state, layout, traits, itemId, count, slot === undefined ? undefined : { slot });
83
+ if (result.status === "rejected")
84
+ return { reason: result.reason };
85
+ staged.set(inventoryId, result.state);
86
+ return null;
87
+ },
88
+ commit() {
89
+ for (const [inventoryId, state] of staged)
90
+ inventory.replaceState(inventoryId, state);
91
+ },
92
+ };
93
+ },
94
+ },
95
+ stats: { seed: seedUserPool },
96
+ economy: { grant: economy.grant },
97
+ unlocks: { grant: (userId, unlockId) => feature("unlocks")?.grant(userId, unlockId) },
98
+ }), ["applyLoadout"], signalNotify);
99
+ const motionByUser = new Map();
100
+ function motionFor(userId) {
101
+ let queue = motionByUser.get(userId);
102
+ if (queue === undefined) {
103
+ queue = createMotionIntents();
104
+ motionByUser.set(userId, queue);
105
+ }
106
+ return queue;
107
+ }
108
+ return {
109
+ inventoryIds,
110
+ inventoryByUser,
111
+ inventoryFor,
112
+ layouts,
113
+ wallets,
114
+ walletOf,
115
+ economy,
116
+ putIntoAnyInventory,
117
+ playerStatsFor,
118
+ pose,
119
+ possession,
120
+ motionByUser,
121
+ motionFor,
122
+ loadouts,
123
+ seedUserPool,
124
+ };
125
+ }
@@ -0,0 +1,59 @@
1
+ import type { CombatSpatialDeps } from "../../combat/effects.js";
2
+ import type { GameEvents } from "../../game/events.js";
3
+ import { type BodyBind } from "../../scene/bodyBind.js";
4
+ import { type EntityStatsApi, type StatValueMap } from "../../scene/entityStats.js";
5
+ import { type EntityPosition, type EntityStore, type SceneEntity, type SpawnOptions } from "../../scene/entityStore.js";
6
+ import { type Forms } from "../../scene/form.js";
7
+ import { type EntityColliderSet } from "../../scene/colliders.js";
8
+ import { type ObjectStore } from "../../scene/objectStore.js";
9
+ import { type PaintLayer } from "../../scene/paintLayer.js";
10
+ import { type SceneRaycastApi } from "../../scene/sceneRaycast.js";
11
+ import { type SpatialApi } from "../../scene/spatial.js";
12
+ import { type Targeting } from "../../scene/targeting.js";
13
+ import type { TerrainField } from "../../world/terrain.js";
14
+ import type { SimClock } from "../../time/simClock.js";
15
+ import type { GameContextContent, GameContextEntityEntry, GameContextModels, GameContextObjectEntry, MoveTowardCommitOptions, SceneObjectContext } from "../gameContext.js";
16
+ /** @internal Live scene stores + spatial/collider/raycast wiring handed in by `createGameContext`. */
17
+ export interface SceneSubsystemDeps {
18
+ content: GameContextContent;
19
+ signalNotify: () => void;
20
+ ground: TerrainField;
21
+ events: GameEvents;
22
+ /** Full sim clock — forms need `after` for timed shapeshift reversion. */
23
+ time: SimClock;
24
+ occluder?: (from: EntityPosition, to: EntityPosition) => boolean;
25
+ models?: GameContextModels;
26
+ }
27
+ /** @internal Always-on scene surface: entities, objects, colliders, spatial, raycast, targeting. */
28
+ export interface SceneSubsystem {
29
+ entities: EntityStore;
30
+ objects: ObjectStore;
31
+ statsByInstance: Map<string, StatValueMap>;
32
+ entityStats: EntityStatsApi;
33
+ ensureInstanceStats: (instanceId: string) => StatValueMap;
34
+ catalogEntry: (instanceId: string) => GameContextEntityEntry | null | undefined;
35
+ catalogObject: (instanceId: string) => GameContextObjectEntry | null | undefined;
36
+ spatial: SpatialApi;
37
+ combatSpatial: CombatSpatialDeps;
38
+ sceneRaycast: SceneRaycastApi;
39
+ targeting: Targeting;
40
+ entityColliders: Map<string, EntityColliderSet>;
41
+ objectColliders: Map<string, EntityColliderSet>;
42
+ entityCollidersOf: (instanceId: string) => EntityColliderSet | null;
43
+ objectCollidersOf: (instanceId: string) => EntityColliderSet | null;
44
+ entityVisualScaleOf: (instanceId: string) => number;
45
+ forms: Forms;
46
+ paintLayer: PaintLayer;
47
+ spawnEntity: (name: string, spawnOptions?: SpawnOptions) => string;
48
+ despawnEntity: (instanceId: string) => boolean;
49
+ moveEntityTowardCommit: (instanceId: string, target: EntityPosition | string, options: MoveTowardCommitOptions) => EntityPosition | null;
50
+ resetAllToSpawn: (filter?: (entity: SceneEntity) => boolean) => number;
51
+ bind: (key: string) => BodyBind;
52
+ sceneObjects: SceneObjectContext;
53
+ /** Install post-spawn side effects (death.revive) after combat is constructed. */
54
+ setOnAfterSpawn: (fn: (instanceId: string) => void) => void;
55
+ /** Install post-despawn side effects (pose.clear) after player is constructed. */
56
+ setOnAfterDespawn: (fn: (instanceId: string) => void) => void;
57
+ }
58
+ /** @internal */
59
+ export declare function createSceneSubsystem(d: SceneSubsystemDeps): SceneSubsystem;