@jgengine/core 0.26.0 → 0.27.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 (55) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/game/playableGame.d.ts +19 -2
  3. package/dist/gameplay.d.ts +5 -0
  4. package/dist/gameplay.js +5 -0
  5. package/dist/inventory/carryLoad.d.ts +48 -0
  6. package/dist/inventory/carryLoad.js +44 -0
  7. package/dist/inventory/equipment.d.ts +95 -0
  8. package/dist/inventory/equipment.js +169 -0
  9. package/dist/inventory/inventoryModel.d.ts +15 -6
  10. package/dist/inventory/inventoryModel.js +54 -5
  11. package/dist/inventory/shapedGrid.d.ts +26 -0
  12. package/dist/inventory/shapedGrid.js +47 -0
  13. package/dist/loot/lootGen.d.ts +186 -0
  14. package/dist/loot/lootGen.js +437 -0
  15. package/dist/loot/lootWeapon.d.ts +35 -0
  16. package/dist/loot/lootWeapon.js +70 -0
  17. package/dist/meta/changelog.d.ts +1 -1
  18. package/dist/meta/changelog.js +37 -1
  19. package/dist/nav/laneGraph.d.ts +178 -0
  20. package/dist/nav/laneGraph.js +671 -0
  21. package/dist/nav/traffic.d.ts +223 -0
  22. package/dist/nav/traffic.js +919 -0
  23. package/dist/physics/kinematicVehicle.d.ts +36 -0
  24. package/dist/physics/kinematicVehicle.js +15 -1
  25. package/dist/physics/vehicleDamage.d.ts +136 -0
  26. package/dist/physics/vehicleDamage.js +214 -0
  27. package/dist/physics/vehicleParts.d.ts +62 -0
  28. package/dist/physics/vehicleParts.js +65 -0
  29. package/dist/progression/gearScore.d.ts +56 -0
  30. package/dist/progression/gearScore.js +59 -0
  31. package/dist/progression/skillTree.d.ts +129 -0
  32. package/dist/progression/skillTree.js +265 -0
  33. package/dist/progression/xpCurve.d.ts +60 -0
  34. package/dist/progression/xpCurve.js +141 -0
  35. package/dist/scene/builtinSceneKinds.js +2 -0
  36. package/dist/world/decalKind.d.ts +48 -0
  37. package/dist/world/decalKind.js +86 -0
  38. package/dist/world/features.d.ts +3 -1
  39. package/dist/world/features.js +1 -0
  40. package/dist/world/grassKind.d.ts +5 -0
  41. package/dist/world/grassKind.js +3 -0
  42. package/dist/world/terraform.d.ts +8 -0
  43. package/dist/world.d.ts +5 -1
  44. package/dist/world.js +4 -0
  45. package/package.json +1 -1
  46. package/skills/jgengine/api.md +2 -2
  47. package/skills/jgengine-combat/SKILL.md +3 -1
  48. package/skills/jgengine-gameplay/SKILL.md +31 -0
  49. package/skills/jgengine-gameplay/api.md +150 -6
  50. package/skills/jgengine-gameplay/capabilities.md +48 -3
  51. package/skills/jgengine-gameplay/reference-systems.md +118 -0
  52. package/skills/jgengine-world/SKILL.md +4 -0
  53. package/skills/jgengine-world/api.md +115 -5
  54. package/skills/jgengine-world/capabilities.md +20 -0
  55. package/skills/jgengine-world/recipes/vehicle-feel.md +5 -0
@@ -776,12 +776,13 @@
776
776
  - `ModelAnimationConfig` (interface): interface ModelAnimationConfig — Rig playback for a `ModelConfig`'s GLTF animation clips — looping idles, one-shots, and held poses.
777
777
  - `ModelAnimationStates` (interface): interface ModelAnimationStates — Movement-state clip set for `ModelAnimationConfig.states`: the shell reads the entity's live speed each frame and crossfades between these clips, so a walking mob animates without any game-side driver.
778
778
  - `ModelAttachment` (interface): interface ModelAttachment — Parents a prop/weapon model to a named bone or node on the host model's rig — a sword on `handslot.r`, a spellbook offhand — following the bone's animated transform each frame.
779
- - `ModelConfig` (interface): interface ModelConfig { url: string; scale?: number; targetHeight?: number; y?: number; anchor?: "center" | "origin"; dims?: ModelDims; collisionMesh?: CollisionMeshData; material?: ModelMaterialOverride; surfaceDetail?: boolean; materialAssets?: readonly Materi… — ⚠ undocumented · used by `PartMotionRig` (@jgengine/shell/render/PartMotion): Procedural motion rig for a rig-less part-composed character (`ModelPart.role` — see `@jgengine/core/game/partAnimation`).
779
+ - `ModelConfig` (interface): interface ModelConfig { url: string; scale?: number; targetHeight?: number; y?: number; anchor?: "center" | "origin"; dims?: ModelDims; collisionMesh?: CollisionMeshData; material?: ModelMaterialOverride; surfaceDetail?: boolean; wind?: ModelWindConfig | false; … — ⚠ undocumented · used by `PartMotionRig` (@jgengine/shell/render/PartMotion): Procedural motion rig for a rig-less part-composed character (`ModelPart.role` — see `@jgengine/core/game/partAnimation`).
780
780
  - `ModelIkConfig` (interface): interface ModelIkConfig — Bone names and limits for {@link ModelConfig.ik}.
781
781
  - `ModelMaterialMaps` (interface): interface ModelMaterialMaps extends Partial<Record<MaterialTextureRole, string>> — Real PBR map URLs (e.g. `buildMaterialCatalog(...).resolve(id)!.maps` from `@jgengine/assets`) layered onto a model's material — the seam for texturing an otherwise-flat/untextured GLB. Any supported MaterialTextureRole may be omitted to keep the model's own map. Physical roles use the renderer's physical material; map URLs and response parameters remain caller-owned.
782
782
  - `ModelMaterialOverride` (interface): interface ModelMaterialOverride extends MaterialSurfaceParameters — Per-entity PBR material override (#151.3) applied to every standard or physical material in the model's cloned scene graph.
783
783
  - `ModelPart` (interface): interface ModelPart — Static child model stacked at a fixed local offset under its parent's transform — no bone/rig resolution, unlike `ModelAttachment`. Assembles a compound entity (e.g. a modular castle wall + tower + roof) from several single-mesh kit pieces.
784
784
  - `ModelRimLight` (interface): interface ModelRimLight — Fresnel rim-light term added on top of a model's lighting — see {@link ModelMaterialOverride.rim}.
785
+ - `ModelWindConfig` (interface): interface ModelWindConfig — Wind sway for a foliage model; see {@link ModelConfig.wind}.
785
786
  - `MovementCommitFrame` (interface): interface MovementCommitFrame — One frame's movement resolution handed to `PlayerMovementConfig.beforeCommit`.
786
787
  - `MovementFeelConfig` (interface): interface MovementFeelConfig — Feel knobs for the built-in walk controller; see {@link PlayerMovementConfig.feel}.
787
788
  - `NoticePresentationConfig` (interface): interface NoticePresentationConfig extends NoticeFeedConfig — How the shell presents `ctx.game.notices`; every field is optional.
@@ -1225,6 +1226,9 @@
1225
1226
  - `GameLifecycle` (type): type GameLifecycle<TState = unknown> = LifecycleConfig<TState> | "always-live" — How a game declares its run-phase story to {@link defineGameDefinition}. Every game states this explicitly so the shell never has to guess — a silent game used to default to `"playing"`, which painted the touch dock over title/menu/results screens (#1337, Harbor Heat #1329).
1226
1227
  - `GameLoop` (interface): interface GameLoop<TContext = unknown> — Lifecycle hooks a game implements to drive init, per-tick simulation, and player join/leave.
1227
1228
  - `GamePhase` (type): type GamePhase = "menu" | "playing" | "paused" | "ended" — Canonical run phase every game moves through. `menu` (title/main menu), `playing` (live), `paused` (mid-run pause), `ended` (win/lose/results). Touch controls are shown only while `playing`; menus and results never paint the touch dock over themselves.
1229
+ - `GearScore` (interface): interface GearScore — A scored loadout: the total, each slot's share, each stat's signed contribution, and how complete it is.
1230
+ - `GearScoreOptions` (interface): interface GearScoreOptions extends Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> — Caller policy for a loadout score: stat weights, which stats read better small, and what level, rarity and an empty slot are worth.
1231
+ - `GearSlotItem` (interface): interface GearSlotItem — The least a worn item must expose to be scored: its stat block, plus optional level and rarity for the gear-score terms.
1228
1232
  - `GenChoiceRecord` (interface): interface GenChoiceRecord — Provenance for one resolved step: which option stuck, its effective weight, how many options were eligible after constraints, and how many eligible options backtracking rejected before this one. A skipped optional step is recorded with an empty `optionId`.
1229
1233
  - `GenChoices` (interface): interface GenChoices — Read-only view of the choices resolved so far, threaded into a later step's pool, constraint, weight function, or transform so a decision can depend on earlier picks (dependent choice).
1230
1234
  - `GenDraft` (interface): interface GenDraft — The resolved-but-unvalidated draft passed to each {@link GenValidator}: the step choices, their values, and the transformed fields. Returning false from any validator rerolls the whole generation up to the attempt budget.
@@ -1245,7 +1249,7 @@
1245
1249
  - `InstalledPart` (interface): interface InstalledPart { slotId: string; part: PartDef } — ⚠ undocumented · used by `identityOf`: Assemble an {@link ItemIdentity} from a family, tags, and installed parts.
1246
1250
  - `InventoryDeclaration` (interface): interface InventoryDeclaration — Shape of one named inventory a game declares — slot count, accepted item types, HUD binding.
1247
1251
  - `InventorySlot` (type): type InventorySlot = { itemId: string; count: number } | null — ⚠ undocumented
1248
- - `InventoryState` (interface): interface InventoryState { slots: InventorySlot[] } — ⚠ undocumented · used by `countItem` (@jgengine/core/inventory/inventoryModel): Total quantity of `itemId` summed across every stack in the inventory.
1252
+ - `InventoryState` (interface): interface InventoryState { slots: InventorySlot[] } — ⚠ undocumented · used by `carriedEntries` (@jgengine/core/inventory/carryLoad): Every filled stack in the given inventories as load entries, in slot order.
1249
1253
  - `ItemIdentity` (interface): interface ItemIdentity — The declarative identity of a built modular item: a caller-named family, its provenance tags, and the parts occupying its slots. This is the "what an item is" layer above the raw stat rollup in ./modularItem — a plain, serializable value with no game noun baked in ("gun", "manufacturer", "potion" are all caller data in `family`/`tags`).
1250
1254
  - `ItemProvenance` (interface): interface ItemProvenance — The serializable record of how an item was generated: its family, tags, per-slot part selection, applied set-bonus ids, and the deterministic seed. Enough for UI provenance display and byte-exact regeneration.
1251
1255
  - `ItemUseHandler` (interface): interface ItemUseHandler<TState> { can?(state: TState, input: ItemUseInput): ItemUseRejection | null; apply(state: TState, input: ItemUseInput): ItemUseResult<TState> } — ⚠ undocumented
@@ -1274,22 +1278,40 @@
1274
1278
  - `Listing` (interface): interface Listing — One active post in a {@link ListingBook}: an item stack a seller offered at a fixed price until it expires.
1275
1279
  - `LoadoutDef` (interface): interface LoadoutDef { inventories?: Record<string, LoadoutItemEntry[]>; stats?: Record<string, {current: number; max?: number; min?: number}>; economy?: Record<string, number>; unlocks?: string[] } — ⚠ undocumented
1276
1280
  - `LockOnCameraConfig` (interface): interface LockOnCameraConfig — Lock-on / strafe rig (#26) — yaw bound to player→target, move axis becomes strafe.
1281
+ - `LootBaseDef` (interface): interface LootBaseDef extends ItemBaseDef — A base family (frame, housing, chassis — caller nouns) with its slots, stats and affix pools.
1282
+ - `LootCatalog` (interface): interface LootCatalog — Everything a roll reads: bases, parts, affixes, rarity odds, makers and the level curve.
1283
+ - `LootComparison` (interface): interface LootComparison — How two items compare: every stat delta plus one weighted score for each.
1284
+ - `LootComparisonOptions` (interface): interface LootComparisonOptions — Caller policy for the comparison score.
1277
1285
  - `LootDropProvenance` (interface): interface LootDropProvenance — Why one drop is in the result: the stage/table/entry that produced it, its weights, and the modifiers that shaped it.
1278
1286
  - `LootFilterRule` (interface): interface LootFilterRule { id: string; when: LootFilterCondition; hide?: boolean; color?: string; beam?: boolean; label?: string } — ⚠ undocumented · used by `evaluateLootFilter`: First matching rule wins (PoE/Last Epoch block semantics) — later rules never override an earlier match.
1287
+ - `LootInstalledPart` (interface): interface LootInstalledPart — One part occupying one slot of a rolled item.
1288
+ - `LootIssueCode` (type): type LootIssueCode = | "invalid-level" | "unknown-base" | "unknown-manufacturer" | "manufacturer-base" | "unknown-rarity" | "duplicate-slot" | "unknown-slot" | "missing-slot-part" | "unknown-part" | "part-category" | "unknown-affix" | "duplicate-affix" | "invalid-affix" | "non-finite-stat" | "empty-… — A defect found by {@link validateLootItem}.
1289
+ - `LootItem` (interface): interface LootItem — One rolled item: the base, maker, rarity, level, composed name, chosen parts, rolled affixes and resolved stats. Plain JSON, so a save stores it and the instance registry can key it.
1290
+ - `LootManufacturerDef` (interface): interface LootManufacturerDef — A maker with a stat personality: its mark goes in the name, `bias` shifts stats additively, `scale` multiplies them, and favored affix pools weigh more when this maker rolls the item.
1279
1291
  - `LootModifier` (interface): interface LootModifier<TCtx = unknown> — A registered, id-tagged loot policy applied to one stage. `plan` transforms eligibility, weights, and roll counts before rolling (luck, difficulty, gating); `drops` post-processes the rolled drops (quantity multipliers, dedupe, caps). Neither hook mutates table definitions, and the id is recorded in provenance so a resolved drop can be traced back to the policies that shaped it.
1292
+ - `LootPartDef` (interface): interface LootPartDef extends PartDef — A part a base can mount: its stats, a pick weight, and an optional word for the item name.
1280
1293
  - `LootPipeline` (interface): interface LootPipeline<TCtx = unknown> — A resolved, reusable loot pipeline. Call {@link LootPipeline.resolve} with a per-drop context and RNG.
1281
1294
  - `LootPipelineDef` (interface): interface LootPipelineDef<TCtx = unknown> — A named, ordered loot-resolution pipeline: the stages to run plus result-shaping policy (duplicate stacking, a total drop cap). Serializable except for its stage gate/modifier functions, mirroring how {@link LootTableDef} entries may carry a `generate` function.
1282
1295
  - `LootPipelineDeps` (interface): interface LootPipelineDeps — Dependencies a pipeline needs at build time — chiefly how to turn a table id into its definition.
1283
1296
  - `LootPlanEntry` (interface): interface LootPlanEntry — One eligible entry inside a {@link LootRollPlan}: the original table entry plus its live eligibility and effective weight/chance. Modifiers rewrite these fields; the source `entry` object is never mutated, so table definitions stay shared and reusable.
1284
1297
  - `LootResolution` (interface): interface LootResolution — The fully serializable outcome of a resolution: the drops, their provenance, per-stage traces, and the replay seed.
1285
1298
  - `LootResolveContext` (interface): interface LootResolveContext<TCtx = unknown> — The caller-owned context, injected RNG, and optional seed threaded through a single resolution.
1299
+ - `LootRollOptions` (interface): interface LootRollOptions — Pin any part of a roll; omitted fields are drawn from the catalog.
1286
1300
  - `LootRollPlan` (interface): interface LootRollPlan — The mutable roll plan a stage derives from its table before rolling. Modifiers transform this — gating entries, reweighting them, or changing the roll count — instead of touching table defs.
1301
+ - `LootRoller` (interface): interface LootRoller — A roll counter over one catalog: every roll goes through {@link rollLoot}, and the handle keeps counters (a distribution readout in the debug overlay) that snapshot and restore as plain data.
1302
+ - `LootRollerOptions` (interface): interface LootRollerOptions — Options for {@link createLootRoller}.
1303
+ - `LootRollerState` (interface): interface LootRollerState — Saved counters of a {@link LootRoller}.
1287
1304
  - `LootStage` (interface): interface LootStage<TCtx = unknown> — One ordered step in a {@link LootPipelineDef}: a source pool (a table id or inline table), an optional context gate, a fold mode, and the modifiers that reshape its roll. Stages compose the genre concepts — world pool, dedicated pool, luck, pity — without any of them living in core.
1288
1305
  - `LootStageKind` (type): type LootStageKind = "contribute" | "fallback" | "replace" — How a stage folds its rolled drops into the accumulating result: `"contribute"` appends, `"fallback"` rolls only when nothing has dropped yet, `"replace"` discards prior drops and overrides with its own (quest/boss overrides).
1289
1306
  - `LootStageStatus` (type): type LootStageStatus = "rolled" | "skipped" | "empty" | "replaced" | "fell-through" — The disposition of a stage after resolution — did it roll, get gated, fall through, override, or find nothing.
1290
1307
  - `LootStageTrace` (interface): interface LootStageTrace — Per-stage record of what happened during resolution, for debugging and replay auditing.
1308
+ - `LootStatDelta` (interface): interface LootStatDelta — A per-stat comparison entry; `delta` is `b - a`, so positive means the second item is larger.
1291
1309
  - `LootTableDef` (interface): interface LootTableDef — A named, validated loot table — its roll count, weighted-vs-independent mode, and candidate entries.
1292
- - `ModelConfig` (interface): interface ModelConfig { url: string; scale?: number; targetHeight?: number; y?: number; anchor?: "center" | "origin"; dims?: ModelDims; collisionMesh?: CollisionMeshData; material?: ModelMaterialOverride; surfaceDetail?: boolean; materialAssets?: readonly Materi… — ⚠ undocumented · used by `PartMotionRig` (@jgengine/shell/render/PartMotion): Procedural motion rig for a rig-less part-composed character (`ModelPart.role` — see `@jgengine/core/game/partAnimation`).
1310
+ - `LootValidationIssue` (interface): interface LootValidationIssue — One reason a rolled item is not legal against its catalog, with the offending value named.
1311
+ - `LootWeaponProjection` (interface): interface LootWeaponProjection — A base weapon definition plus the stat map a rolled item overrides.
1312
+ - `LootWeaponStatMap` (interface): interface LootWeaponStatMap — Which rolled stat feeds which part of a {@link WeaponDefinition}. Every field is a stat id from the item; an omitted field keeps the template's value, so a catalog only maps what it rolls.
1313
+ - `MAX_LOOT_AFFIXES` (const): const MAX_LOOT_AFFIXES: 8 — Cap on affixes one item may roll, so a bad catalog cannot make a drop cost unbounded work.
1314
+ - `ModelConfig` (interface): interface ModelConfig { url: string; scale?: number; targetHeight?: number; y?: number; anchor?: "center" | "origin"; dims?: ModelDims; collisionMesh?: CollisionMeshData; material?: ModelMaterialOverride; surfaceDetail?: boolean; wind?: ModelWindConfig | false; … — ⚠ undocumented · used by `PartMotionRig` (@jgengine/shell/render/PartMotion): Procedural motion rig for a rig-less part-composed character (`ModelPart.role` — see `@jgengine/core/game/partAnimation`).
1293
1315
  - `ModelMaterialMaps` (interface): interface ModelMaterialMaps extends Partial<Record<MaterialTextureRole, string>> — Real PBR map URLs (e.g. `buildMaterialCatalog(...).resolve(id)!.maps` from `@jgengine/assets`) layered onto a model's material — the seam for texturing an otherwise-flat/untextured GLB. Any supported MaterialTextureRole may be omitted to keep the model's own map. Physical roles use the renderer's physical material; map URLs and response parameters remain caller-owned.
1294
1316
  - `ModelMaterialOverride` (interface): interface ModelMaterialOverride extends MaterialSurfaceParameters — Per-entity PBR material override (#151.3) applied to every standard or physical material in the model's cloned scene graph.
1295
1317
  - `ModularItemDef` (interface): interface ModularItemDef { id: string; baseStats: Record<string, number>; slots: readonly MountSlotDef[] } — ⚠ undocumented
@@ -1429,6 +1451,16 @@
1429
1451
  - `ShopStockSnapshot` (interface): interface ShopStockSnapshot — Serializable state of a shop's stock, for save/restore.
1430
1452
  - `ShoulderCameraConfig` (interface): interface ShoulderCameraConfig — Over-the-shoulder combat rig (#25) — offset, ADS, shoulder swap, decoupled reticle.
1431
1453
  - `SideScrollCameraConfig` (interface): interface SideScrollCameraConfig — Fixed lateral 2.5D follow (side-on platformer cam): the camera sits perpendicular to the travel axis, tracks the followed entity, and never reads player look input.
1454
+ - `SkillActiveDef` (interface): interface SkillActiveDef — An action a node or synergy unlocks, shaped to bind straight into `createAbilityKit`'s slot config.
1455
+ - `SkillNodeDef` (interface): interface SkillNodeDef<TStat extends string = string> — One authored tree node. Every field except `id` is optional data, so a game states its own tree: what a rank costs, what must come first, the passive it folds in and the actions it opens.
1456
+ - `SkillRequirement` (interface): interface SkillRequirement — One node that must be at or above `rank` for a node or synergy to open; `rank` defaults to `1`.
1457
+ - `SkillResolution` (interface): interface SkillResolution<TStat extends string = string> — The whole outcome of a build: merged passives, unlocked actions, live synergies and point accounting.
1458
+ - `SkillSpendReason` (type): type SkillSpendReason = | "unknown-node" | "max-rank" | "no-points" | "requires" | "branch-points" | "excluded" — Why a spend was refused; each reason is reachable from authored data, so a UI can say which rule bit.
1459
+ - `SkillSpendResult` (type): type SkillSpendResult = | { ok: true; spent: number; pointsAvailable: number } | { ok: false; reason: SkillSpendReason; detail: string } — The outcome of one spend or legality check: what it costs and the balance after it, or the rule that stopped it.
1460
+ - `SkillSynergyDef` (interface): interface SkillSynergyDef<TStat extends string = string> — A payoff that exists only when two or more nodes are ranked together. This is what rewards breadth over dumping every point into one branch.
1461
+ - `SkillTree` (interface): interface SkillTree<TStat extends string = string> — A live skill tree over one authored config, with respec, snapshot/restore and an optional devtools probe.
1462
+ - `SkillTreeConfig` (interface): interface SkillTreeConfig<TStat extends string = string> — Everything {@link createSkillTree} reads: nodes, optional synergies, and starting points.
1463
+ - `SkillTreeState` (interface): interface SkillTreeState — Saved tree state: unspent points and the rank of every ranked node.
1432
1464
  - `SlotGrid` (type): type SlotGrid<T> = readonly Slot<T>[] — ⚠ undocumented
1433
1465
  - `Social` (interface): interface Social { friends: Friends; party: Party; presence: {get(userId: string): PresenceInfo}; emotes: Emotes; worldInvites: WorldInvites; snapshot(): SocialSnapshot; hydrate(data: SocialSnapshot): void } — ⚠ undocumented · used by `createSocial`: Emotes and lightweight social interactions between nearby players.
1434
1466
  - `SocialDeps` (interface): interface SocialDeps { events: GameEvents; presence?: (userId: string) => PresenceInfo; now?: () => number; emotes?: EmotesDeps; worldInviteTtlMs?: number } — ⚠ undocumented · used by `createSocial`: Emotes and lightweight social interactions between nearby players.
@@ -1519,6 +1551,10 @@
1519
1551
  - `WorldItemRecord` (interface): interface WorldItemRecord { instanceId: string; itemId: string; rarity: string; baseType: string; count: number; affixTier?: number; source?: string; droppedAt: number } — ⚠ undocumented · used by `useNearestWorldItem` (@jgengine/react): Nearest ground item within `radius` of the local player — drives a pickup prompt/highlight.
1520
1552
  - `WorldItemRenderConfig` (interface): interface WorldItemRenderConfig { rarityStyle?: Record<string, RarityStyle>; filter?: readonly LootFilterRule[]; pickupRadius?: number; beamHeight?: number; autoPickup?: boolean | {radius?: number} } — ⚠ undocumented · used by `WorldItems` (@jgengine/shell/world/WorldItems): Rarity→beam/color/label render binding + loot-filter overlay (#32/#33) for every dropped `worldItem`.
1521
1553
  - `WorldOverlayProps` (interface): interface WorldOverlayProps — Props handed to a `WorldOverlay` component (#542): explicit `ctx` access so canvas-layer VFX read live engine state directly, without an extra hook or a module-global workaround.
1554
+ - `XpCurve` (interface): interface XpCurve — A resolved, validated {@link XpCurveDef}: thresholds, lifetime totals, a stated rule, and a leveling track bound to the same numbers.
1555
+ - `XpCurveDef` (interface): interface XpCurveDef — Authorable XP curve data. Thresholds are generated from one growth rule, so a game states its curve and its cap instead of hand-writing a table it cannot rebalance, and every consumer of the same data agrees on the numbers.
1556
+ - `XpGrowthRule` (type): type XpGrowthRule = "flat" | "linear" | "power" | "geometric" | "table" — Named rule a level threshold grows by. `flat` repeats `step`; `linear` adds `growthRate` per level; `power` uses `level ^ growthRate`; `geometric` multiplies by `growthRate`; `table` reads `table`.
1557
+ - `XpLevelStep` (interface): interface XpLevelStep — One row of a resolved curve: the threshold at `level`, and the lifetime XP it sits at.
1522
1558
  - `activeJobs` (function): function activeJobs<TSpec, TReserve>(state: WorkQueueState<TSpec, TReserve>): Job<TSpec, TReserve>[] — Jobs currently progressing.
1523
1559
  - `activeSetBonuses` (function): function activeSetBonuses(identity: ItemIdentity, bonuses: readonly SetBonus[]): SetBonus[] — Select the set bonuses whose membership count meets their threshold, in the order they were declared.
1524
1560
  - `activeTouchControlsMode` (function): function activeTouchControlsMode(ctx: GameContext): string | null — The active touch control mode, `null` when the base config applies.
@@ -1533,6 +1569,9 @@
1533
1569
  - `applyBindingOverrides` (function): function applyBindingOverrides<TAction extends string, TCode extends string>(input: ActionCodesMap<TAction, TCode>, overrides: BindingOverrides): ActionCodesMap<TAction, TCode> — Merge player rebinds over a game's authored `input` map. Only actions the game already declares can be overridden; unknown override keys are ignored so a stale localStorage entry can't inject phantom actions.
1534
1570
  - `applySetBonuses` (function): function applySetBonuses(stats: Record<string, number>, bonuses: readonly SetBonus[]): Record<string, number> — Fold a set of active bonuses' additive stats onto a stat map, returning a new map (the input is not mutated).
1535
1571
  - `applyWear` (function): function applyWear(state: DurabilityState, amount: number): DurabilityState — Apply wear to an item, tracking breakage and repair eligibility.
1572
+ - `assertLootCatalog` (function): function assertLootCatalog(catalog: LootCatalog): void — Check a catalog before rolling; throws `RangeError` naming the offending entry.
1573
+ - `assertSkillTreeConfig` (function): function assertSkillTreeConfig<TStat extends string>(config: SkillTreeConfig<TStat>): void — Check tree data before play; throws `RangeError` naming the offending node, requirement or synergy.
1574
+ - `assertXpCurve` (function): function assertXpCurve(def: XpCurveDef): void — Check curve data before use; throws `RangeError` naming the offending field.
1536
1575
  - `balance` (function): function balance(state: WalletState, currency: string | CurrencyDefinition): number — ⚠ undocumented
1537
1576
  - `balanceOf` (function): function balanceOf(ledger: ResourceLedger, account: string, currency: string): number — Read a single balance; unknown account/currency pairs read as `0`.
1538
1577
  - `canAfford` (function): function canAfford(state: WalletState, costs: Readonly<Record<string, number>>): boolean — True when every currency in `costs` has at least that much balance (a pure, non-mutating check).
@@ -1546,6 +1585,8 @@
1546
1585
  - `chargeAll` (function): function chargeAll(state: WalletState, costs: Readonly<Record<string, number>>, options?: ChargeOptions): ChargeResult — ⚠ undocumented
1547
1586
  - `clampValue` (function): function clampValue(value: number, bounds?: NumericBounds): number — Clamp a scalar to `bounds` (identity when `bounds` is omitted). Pure — touches no record.
1548
1587
  - `clearBindingOverride` (function): function clearBindingOverride(gameId: string, action: string, storage: Pick<WebStorageLike, "getItem" | "setItem" | "removeItem"> | null | undefined = defaultStorage()): BindingOverrides — ⚠ undocumented
1588
+ - `compareGearSets` (function): function compareGearSets(a: Readonly<Record<string, GearSlotItem | null | undefined>>, b: Readonly<Record<string, GearSlotItem | null | undefined>>, options: GearScoreOptions = {}): { a: GearScore; b: GearScore; delta: number; better: "a" | "b" | "equal" } — Compare two loadouts; negative means `a` scores lower.
1589
+ - `compareLootItems` (function): function compareLootItems(a: LootItem, b: LootItem, options: LootComparisonOptions = {}): LootComparison — Compare two rolled items: every stat delta plus one weighted score for each, with caller-named weights and which stats read better when small.
1549
1590
  - `compileSystemSchedule` (function): function compileSystemSchedule(systems: readonly SystemDefinition[], options?: CompileSystemScheduleOptions): CompiledSystemSchedule — Compile system definitions into a deterministic schedule. Validates unique ids, `dependsOn`, and before/after cycles.
1550
1591
  - `composeGameLoop` (function): function composeGameLoop(systems: readonly SystemDefinition[] | undefined, loop: GameLoop<GameContext> | undefined, options?: ComposeGameLoopOptions): GameLoop<GameContext> — Merge a system list with an optional classic `GameLoop` into one loop the shell/runners drive. Systems install on first `onInit`; classic hooks still run for incremental migration.
1551
1592
  - `computeEffectiveStats` (function): function computeEffectiveStats(def: ModularItemDef, installed: readonly InstalledPart[]): Record<string, number> — ⚠ undocumented
@@ -1585,6 +1626,7 @@
1585
1626
  - `createLoadouts` (function): function createLoadouts(deps: LoadoutDeps): Loadouts — Save, name, and swap equipment loadouts.
1586
1627
  - `createLootPipeline` (function): function createLootPipeline<TCtx = unknown>(def: LootPipelineDef<TCtx>, deps: LootPipelineDeps = {}): LootPipeline<TCtx> — Build a composable loot-resolution pipeline: ordered source pools, context gates, fallbacks when a pool yields nothing, roll modifiers (luck, quantity, difficulty) applied as registered policies, and full provenance for every drop (stage, table, entry, original vs effective weights, modifier ids, seed). Rolling consumes the injected RNG in the same order as the base loot table, so a seeded resolution is deterministic and server-authoritatively replayable. Genre concepts (world, dedicated, boss, luck, rarity, pity) stay out of core and ship as stage/modifier compositions.
1587
1628
  - `createLootRegistry` (function): function createLootRegistry(options: LootRegistryOptions = {}): LootRegistry — Register named loot tables and roll weighted randomized drops from them.
1629
+ - `createLootRoller` (function): function createLootRoller(catalog: LootCatalog, options: LootRollerOptions = {}): LootRoller — Wrap a catalog in a roll counter: same results as {@link rollLoot}, plus counts by rarity for a debug overlay, snapshot/restore of those counts, and an optional devtools probe.
1588
1630
  - `createModularItem` (function): function createModularItem(def: ModularItemDef, initial: readonly InstalledPart[] = []): ModularItem — ⚠ undocumented
1589
1631
  - `createMoodleStack` (function): function createMoodleStack(): MoodleStack — A stateful holder for timed status moodles (food buffs, temporary shelter, warmth). Meters and multi-region health derive their own moodles on read; combine all three through `stackMoodles(stack.list(), meterMoodles, ailmentMoodles)` for one display.
1590
1632
  - `createMultiRegionHealth` (function): function createMultiRegionHealth(config: MultiRegionHealthConfig): MultiRegionHealth — Per-region/limb health tracked separately, so each body part takes and heals damage on its own.
@@ -1609,6 +1651,7 @@
1609
1651
  - `createSaveSlots` (function): function createSaveSlots(config: SaveSlotsConfig = {}): SaveSlots — Create a {@link SaveSlots} index — the serializable, observable list of save/profile slot metadata a save-select menu renders. It is a thin companion to `createSaveStore`: the store owns the real save payload keyed by slot, this owns only the display fields (name, empty, last-saved, and a free-string `meta` bag) the menu shows. `write` stamps a slot saved on the injected clock, `clear` empties one, `rename` relabels one, `mostRecent()` returns the newest non-empty slot to power a "Continue" button, and `list()` feeds the New / Continue / Load / Delete grid. `meta` keys are free strings the game owns — the model never interprets them — and `snapshot`/`restore` round-trips the whole index through a save.
1610
1652
  - `createSaveStore` (function): function createSaveStore<T>(config: SaveStoreConfig<T>): SaveStore<T> — Create a {@link SaveStore}. Same call for offline and cloud — only the `backend` differs (localStorage, memory, or an async DB/Convex endpoint). Turn on `autosave` and every `set`/`patch` persists on a debounce; leave it off and call `save()` at checkpoints. Bump `version` + pass `migrate` when the save shape changes so old players keep their progress.
1611
1653
  - `createShopStock` (function): function createShopStock(config: ShopStockConfig = {}): ShopStock — A serializable, observable vendor/shop stock. Each entry carries a game-owned free-string `kind`, a `price` in a free-string `currency`, a finite or unlimited (`qty: null`) count, and an optional `sellPrice` buy-back. All currency math is delegated to the existing pure `wallet` model — `buy` charges and `sell` grants a **caller-owned** {@link WalletState} (the shop never holds the wallet, it returns the new wallet for the caller to adopt), so the same stock composes with any wallet the game owns. Nothing here is genre-specific: `kind` and `currency` are free labels the game styles and the model never branches on. `restock`/`setPrice`/`add`/`remove` mutate the catalog, `subscribe` observes every change, and `snapshot`/`restore` round-trips the whole stock through a save.
1654
+ - `createSkillTree` (function): function createSkillTree<TStat extends string = string>(config: SkillTreeConfig<TStat>, options: { devtoolsName?: string } = {}): SkillTree<TStat> — A data-authored skill tree: nodes carry a point cost, prerequisites, passives and unlocked actions, synergies pay off two or more nodes together, and `respec` refunds exactly the points spent. Spend against `canSpend` for a reason a UI can show; read `resolved()` for the stat modifiers and action list to feed `createStats` / `createAbilityKit`.
1612
1655
  - `createSocial` (function): function createSocial(deps: SocialDeps): Social — Emotes and lightweight social interactions between nearby players.
1613
1656
  - `createSpawnPoints` (function): function createSpawnPoints(): SpawnPoints — Register spawn locations and choose where entities spawn or respawn.
1614
1657
  - `createStatGraph` (function): function createStatGraph(def: StatGraphDef): StatGraph — A data-driven stat graph: game-owned named inputs feed caller-authored derived formulas, with contribution provenance, uncommitted previews, cycle detection, and selective recomputation. Formula semantics and numeric tables stay entirely game-defined.
@@ -1633,6 +1676,7 @@
1633
1676
  - `defaultObjectiveLabel` (function): function defaultObjectiveLabel(objective: QuestObjective): string — Default objective label: a readable "verb count noun" from a {@link QuestObjective}.
1634
1677
  - `defineLootPipeline` (function): function defineLootPipeline<TCtx = unknown>(def: LootPipelineDef<TCtx>): LootPipelineDef<TCtx> — Validate a loot pipeline definition and return it unchanged, for use with {@link createLootPipeline}.
1635
1678
  - `defineSystem` (function): function defineSystem(definition: SystemDefinition): SystemDefinition — Declare a composable game system. Pure data + hooks — the engine compiles the schedule and installs lifecycle when the game boots.
1679
+ - `defineXpCurve` (function): function defineXpCurve(def: XpCurveDef): XpCurve — Resolve authored XP curve data: the threshold per level, the lifetime XP it sits at, a plain-words growth rule, and the level an XP total reaches under the cap. One data record with one growth rule keeps a rebalance in the save-visible definition rather than in formulas scattered across a game.
1636
1680
  - `deriveTouchScheme` (function): function deriveTouchScheme(input: ActionCodesMap | undefined, { reserved, firstPerson, config: rawConfig, mode }: DeriveTouchSchemeOptions): TouchScheme | null — Null means "render no touch controls" — either the game opted out or there is nothing to synthesize.
1637
1681
  - `describeTrackedQuest` (function): function describeTrackedQuest(def: QuestDef, instance: QuestInstance, label: (objective: QuestObjective) => string = defaultObjectiveLabel): TrackedQuestView — Join a quest's static {@link QuestDef} with a player's live {@link QuestInstance} into a flat, renderer-free view a HUD tracker draws (title, status, labelled objective progress). Pass `label` to override the derived objective text.
1638
1682
  - `dialogueSlot` (const): const dialogueSlot: StoreHandle<string | undefined> — Typed handle onto the open-dialogue slot — React reads it via `useOpenDialogueId`; game code uses `ctx.game.dialogue`.
@@ -1652,6 +1696,7 @@
1652
1696
  - `finishRaceSession` (function): function finishRaceSession(session: RaceSessionState): RaceSessionState — Cross the flag: move a `racing` session to `finished`, freezing its `elapsed`. A no-op in any other phase.
1653
1697
  - `firstPastPost` (function): function firstPastPost(count = 1): RaceWinCondition — Race ends when `count` racers have crossed the finish; ranking is the current standings order.
1654
1698
  - `gamePhase` (function): function gamePhase(ctx: GameContext): GamePhase — Current phase; defaults to `playing` when unset — a game that runs live from boot declares that intent explicitly with `defineGame({ lifecycle: "always-live" })` rather than relying on the silent default.
1699
+ - `gearScore` (function): function gearScore(set: Readonly<Record<string, GearSlotItem | null | undefined>>, options: GearScoreOptions = {}): GearScore — Score a whole loadout with one documented formula:
1655
1700
  - `generate` (function): function generate(schema: GenSchema, rng: () => number, options: GenerateOptions = {}): GenOutcome — Run a caller-defined {@link GenSchema} against an injected `rng` into a deterministic, serializable {@link GenResult} with full provenance. Composes weighted/uniform choice, dependent choice, constraints with bounded backtracking, field transforms, and validation reroll over plain data — the generic seam procedural loot, affix, and modular-part rollers assemble on. Identical schema, seed, and pins reproduce an identical result across server/client and save/load.
1656
1701
  - `getRuleEffect` (function): function getRuleEffect(id: string): RuleEffectDefinition | undefined — Look up a declared rule effect, or `undefined` when the id was never registered — lets callers reject unknown effect references in authored content.
1657
1702
  - `getValue` (function): function getValue(record: Record<string, number>, key: string, fallback = 0): number — Current value for `key`, or `fallback` (default `0`) when the record has no entry.
@@ -1673,7 +1718,10 @@
1673
1718
  - `loadBindingOverrides` (function): function loadBindingOverrides(gameId: string, storage: Pick<WebStorageLike, "getItem"> | null | undefined = defaultStorage()): BindingOverrides — ⚠ undocumented
1674
1719
  - `localSaveBackend` (function): function localSaveBackend(storage?: KeyValueStorage | null, policy: KeyValueStorageErrorPolicy = {}): SaveBackend — A {@link SaveBackend} over a synchronous {@link KeyValueStorage} — the browser's `localStorage` by default (offline, on-device saves), a test stub, or `null` for a no-op backend. Default fallback ignores failures; `errorMode: "throw"` rejects failed operations and unavailable storage, so SaveStore status/onError can report them. Use {@link memorySaveBackend} for deliberate ephemeral persistence.
1675
1720
  - `lootFilter` (function): function lootFilter(rules: readonly LootFilterRule[]): readonly LootFilterRule[] — Validating factory — rule ids must be unique so authoring mistakes fail loudly.
1721
+ - `lootLevelScale` (function): function lootLevelScale(catalog: LootCatalog, level: number): number — Stat multiplier at `level` from the catalog's level curve; `1` when the catalog declares none.
1722
+ - `lootStatContributions` (function): function lootStatContributions(stats: Readonly<Record<string, number>>, options: Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> = {}): Record<string, number> — Per-stat weighted contribution: `value × weight`, negated for stats listed in `lowerIsBetter`. This is the single weight policy behind {@link scoreLootStats}, {@link compareLootItems} and a gear score, so an item and a whole loadout are never ranked by different arithmetic.
1676
1723
  - `lootTable` (function): function lootTable(def: LootTableDef): LootTableDef — Validates a loot table definition and returns it unchanged, for use with {@link createLootRegistry}.
1724
+ - `lootWeaponDefinition` (function): function lootWeaponDefinition(item: LootItem, projection: LootWeaponProjection): WeaponDefinition — Project a rolled item onto a plain weapon definition: copy the template, then write the mapped stats into rate, damage, magazine, spread, range and projectile speed. The result is a {@link WeaponDefinition}, so `assertWeaponDefinition` and `createWeaponSystem` accept it unchanged.
1677
1725
  - `matchesQuery` (function): function matchesQuery(identity: ItemIdentity, query: IdentityQuery): boolean — Test whether an identity satisfies a declarative {@link IdentityQuery}.
1678
1726
  - `medalFor` (function): function medalFor(rank: number): "gold" | "silver" | "bronze" | null — A free-string medal token for the top-three ranks — `"gold"` (1), `"silver"` (2), `"bronze"` (3) — or `null` for any other rank. It is a semantic token, not a color: the UI maps it to an icon/color/theme, and the model never styles. Feed it a {@link RankedEntry.rank}.
1679
1727
  - `memorySaveBackend` (function): function memorySaveBackend(): SaveBackend — A memory-only {@link SaveBackend} — saves survive a reload only within the same session. For tests, SSR, or a "no persistence" mode that still exercises the same save code path.
@@ -1722,8 +1770,10 @@
1722
1770
  - `resumeRule` (function): function resumeRule(ledger: ResourceLedger, ruleId: string): ResourceLedger — Resume a paused rule. Its `nextDueSeconds` is unchanged, so the paused span counts as missed cycles that the rule's {@link CatchUpPolicy} decides how to settle on the next advance.
1723
1771
  - `returnExpeditionNow` (function): function returnExpeditionNow<TCtx>(config: ExpeditionConfig<TCtx>, state: ExpeditionState, nowMs: number): ExpeditionState — Settle first, then complete a live return instantly. Caller owns payment/authorization; pending ticks remain unsettled when the budget is exhausted, and dead/home state is never changed.
1724
1772
  - `ringSampleAt` (function): function ringSampleAt(config: RingConfig, time: number): RingSample — ⚠ undocumented
1773
+ - `rollLoot` (function): function rollLoot(catalog: LootCatalog, rng: () => number, options: LootRollOptions = {}): LootItem — Roll one item from a catalog: a base and maker, a weighted rarity, one part per slot, rarity-count affixes, level-scaled stats and a name composed from maker, rarity, part, affix words and base. Deterministic for a given `rng`; every field it reads is caller data. Validate a catalog built outside {@link createLootRoller} once with {@link assertLootCatalog} before rolling from it.
1725
1774
  - `runPipeline` (function): function runPipeline<V>(base: V, modifiers: readonly Modifier<V>[], equals: (a: V, b: V) => boolean = Object.is): PipelineResult<V> — ⚠ undocumented
1726
1775
  - `saveBindingOverride` (function): function saveBindingOverride(gameId: string, action: string, codes: ActionCodes, storage: Pick<WebStorageLike, "getItem" | "setItem" | "removeItem"> | null | undefined = defaultStorage()): BindingOverrides — ⚠ undocumented
1776
+ - `scoreLootStats` (function): function scoreLootStats(stats: Readonly<Record<string, number>>, options: Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> = {}): number — One stat block's weighted score: the sum of `value × weight`, negated for stats listed in `lowerIsBetter`. Pair it with {@link lootStatContributions} when a caller also needs the split.
1727
1777
  - `seededRng` (function): function seededRng(seed: string | number): SeededRng — Deterministic pseudo-random generator seeded from a string or number — same seed, same sequence.
1728
1778
  - `seededStreams` (function): function seededStreams(seed: string | number): (stream: string) => () => number — Derives independent, deterministic {@link seededRng} streams from one base seed, keyed by stream name.
1729
1779
  - `selectDialogueView` (function): function selectDialogueView(graph: DialogueGraph, nodeId: string): DialogueGraphView | null — Project a {@link DialogueGraph} node id to its render-ready {@link DialogueGraphView} — the pure "current view" selector a stateless renderer reads (speaker, line, choices, done). Returns `null` when the id is not in the graph. No traversal or mutation.
@@ -1756,10 +1806,20 @@
1756
1806
  - `unitTrainingConfig` (function): function unitTrainingConfig(options: UnitTrainingOptions): WorkQueueConfig<UnitTrainingSpec, UnitReservation, UnitSpawnOrder> — Build a {@link WorkQueueConfig} for training units from a catalog. Reservation is the unit's cost + population; duration is its train time; completion output is a {@link UnitSpawnOrder} for the caller to route to its spawn primitive.
1757
1807
  - `validateIdentity` (function): function validateIdentity(identity: ItemIdentity, rules: readonly CompatibilityRule[]): ConstraintViolation[] — Collect every compatibility rule a completed identity violates. An empty result means the identity is legal.
1758
1808
  - `validateLayers` (function): function validateLayers(layers: readonly ParamLayer[]): readonly LayerConflict[] — Detect design conflicts in a layer set before resolving: duplicate ids, and competing `set` ops on the same parameter at the same priority (order-dependent, so worth surfacing). Returns an empty array when the set is clean, so callers can gate a difficulty/session build on it.
1809
+ - `validateLootItem` (function): function validateLootItem(item: LootItem, catalog: LootCatalog): LootValidationIssue[] — Check one rolled item against its catalog: base, maker and rarity exist, every slot holds an eligible part, affixes belong to the base's pools, stats are finite and the item is named.
1759
1810
  - `wear` (function): function wear(spec: DurabilitySpec, state: DurabilityState, kind: WearKind, times = 1): DurabilityState — ⚠ undocumented
1760
1811
  - `withTouchCodes` (function): function withTouchCodes(map: ActionCodesMap | undefined): ActionCodesMap — Every action gains a synthetic touch code alongside its physical codes.
1761
1812
  - `worldHealthBarAllowsRole` (function): function worldHealthBarAllowsRole(roles: readonly CatalogEntityRole[] | undefined, role: CatalogEntityRole | undefined): boolean — ⚠ undocumented
1762
1813
 
1814
+ ## @jgengine/core/inventory/carryLoad
1815
+
1816
+ - `CharacterLoad` (interface): interface CharacterLoad extends EncumbranceState — A carrier's load: the encumbrance state, how it splits into carried and worn, and capacity left.
1817
+ - `CharacterLoadInput` (interface): interface CharacterLoadInput — Everything a {@link characterLoad} reading takes: carried inventories, worn gear and the capacity they are measured against.
1818
+ - `WeightLookup` (type): type WeightLookup = (itemId: string) => number — Per-item mass in the caller's weight unit; an unknown item weighs 0.
1819
+ - `canCarry` (function): function canCarry(load: Pick<CharacterLoad, "mass" | "capacity">, addedMass: number): boolean — Whether `addedMass` more weight still fits under `capacity`. Pure: the caller decides what to do with a refusal, so an overload policy stays with the game.
1820
+ - `carriedEntries` (function): function carriedEntries(states: readonly InventoryState[]): LoadEntry[] — Every filled stack in the given inventories as load entries, in slot order. Only the inventories the caller passes count, so a stash left out of the list adds no weight.
1821
+ - `characterLoad` (function): function characterLoad(input: CharacterLoadInput): CharacterLoad — Weigh a character: carried stacks plus worn gear against a capacity, resolved into the shared encumbrance bands. Movement reads `moveMultiplier`, `canSprint`, `canJump` and `immobile`; pickup gates read `remaining` or {@link canCarry}. The stash is not part of `carried`, so it adds nothing.
1822
+
1763
1823
  ## @jgengine/core/inventory/encumbrance
1764
1824
 
1765
1825
  - `EncumbranceConfig` (interface): interface EncumbranceConfig — Tuning for where the encumbrance bands sit and how hard the carrier is slowed. Both fields are load *fractions* / multipliers in `0..1`, never absolute mass.
@@ -1771,16 +1831,32 @@
1771
1831
  - `resolveEncumbrance` (function): function resolveEncumbrance(mass: number, capacity: number, config?: EncumbranceConfig): EncumbranceState — Resolve carried mass against carrying capacity into a serializable encumbrance state: load fraction, tier, and the movement consequences (`canSprint`/`canJump`/`immobile` gates plus a progressive `moveMultiplier`). Below `soft` the carrier is unencumbered and unhindered; from `soft` up to capacity it is encumbered — no sprint or jump, and speed decays from 1 toward `floor`; at or above capacity it is immobile (multiplier 0). Deterministic and allocation-light; pair with {@link totalLoad} to derive `mass` from an inventory and a mass table. A capacity of 0 or less pins any positive load as immobile (`fraction` 1) so the state stays finite/serializable.
1772
1832
  - `totalLoad` (function): function totalLoad(entries: readonly LoadEntry[], massOf: MassResolver): number — Sum the mass of every carried stack using an injected per-unit mass resolver. Bounded by the number of entries; allocates nothing. Missing or negative unit masses are treated as 0.
1773
1833
 
1834
+ ## @jgengine/core/inventory/equipment
1835
+
1836
+ - `EquipItemDef` (interface): interface EquipItemDef — The gear facts the equip rules read from an item. A rolled item resolves its runtime id through the instance registry to its base and answers from that base's data — this is not a second item type.
1837
+ - `EquipItemLookup` (type): type EquipItemLookup = (itemId: string) => EquipItemDef | null | undefined — Item lookup the rules read; returns nothing for an unknown id.
1838
+ - `EquipRejection` (type): type EquipRejection = "unknown-slot" | "not-equippable" | "wrong-slot" | "already-equipped" | "blocked" — Why an equip attempt was refused; `detail` names the item and slots involved.
1839
+ - `EquipResult` (type): type EquipResult = | { status: "ok"; state: EquipmentState; slotId: string; replaced: string | null } | { status: "rejected"; reason: EquipRejection; detail: string } — An equip attempt: the new worn set plus the replaced item, or the refusal with its reason and detail.
1840
+ - `EquipSlotDef` (interface): interface EquipSlotDef — One body slot: the item kinds it accepts and the slots it occupies while filled.
1841
+ - `Equipment` (interface): interface Equipment — A worn-gear set: one item per slot, rules from slot and item data, state out and back in.
1842
+ - `EquipmentOptions` (interface): interface EquipmentOptions — Options for {@link createEquipment}.
1843
+ - `EquipmentProbeReading` (interface): interface EquipmentProbeReading — What the debug overlay reads about a worn set.
1844
+ - `EquipmentState` (interface): interface EquipmentState — Serializable equip state: the filled slots as `[slotId, itemId]` pairs.
1845
+ - `UnequipResult` (type): type UnequipResult = | { status: "ok"; state: EquipmentState; itemId: string } | { status: "rejected"; reason: "unknown-slot" | "empty-slot"; detail: string } — An unequip attempt: the new worn set plus the item taken off, or the refusal with its detail.
1846
+ - `WornEntry` (interface): interface WornEntry — One worn item and the slot holding it.
1847
+ - `createEquipment` (function): function createEquipment(slotDefs: readonly EquipSlotDef[], lookup: EquipItemLookup, options: EquipmentOptions = {}): Equipment — A worn-gear set over caller slot and item data.
1848
+
1774
1849
  ## @jgengine/core/inventory/inventoryModel
1775
1850
 
1776
1851
  - `InventoryLayout` (interface): interface InventoryLayout { slots: number; accepts?: string | readonly string[] } — ⚠ undocumented · used by `createEmptyInventory`: A bag of stackable items supporting add, remove, count, and move — the base inventory model.
1777
1852
  - `InventorySet` (interface): interface InventorySet<TId extends string> { put(id: TId, itemId: string, count: number, options?: {slot?: number}): PutResult; take(id: TId, itemId: string, count: number): TakeResult; move(fromId: TId, fromSlot: number, toId: TId, toSlot?: number): MoveResult; count(id: TId, itemI… — ⚠ undocumented · used by `createInventorySet`: Build a keyed set of named inventories from their `layouts`, sharing one `traits` table — the stateful façade over put/take/move/count.
1853
+ - `InventorySetState` (type): type InventorySetState<TId extends string> = Record<TId, InventoryState> — Every inventory in a set, keyed by id, as plain slot data.
1778
1854
  - `InventorySlot` (type): type InventorySlot = { itemId: string; count: number } | null — ⚠ undocumented
1779
1855
  - `InventoryState` (interface): interface InventoryState { slots: InventorySlot[] } — ⚠ undocumented · used by `countItem`: Total quantity of `itemId` summed across every stack in the inventory.
1780
1856
  - `ItemTraits` (interface): interface ItemTraits { stackLimit(itemId: string): number; kind?(itemId: string): string | null } — ⚠ undocumented · used by `createInventorySet`: Build a keyed set of named inventories from their `layouts`, sharing one `traits` table — the stateful façade over put/take/move/count.
1781
1857
  - `MoveResult` (type): type MoveResult = | { status: "ok"; from: InventoryState; to: InventoryState } | { status: "rejected"; reason: "invalid-slot" | "empty-slot" | "wrong-kind" | "no-space" } — ⚠ undocumented · used by `moveItem`: Move the stack at `fromSlot` to `toSlot` (or auto-stack when omitted) — swapping, merging with remainder, or relocating across two inventori…
1782
1858
  - `PutResult` (type): type PutResult = | { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "no-space" | "wrong-kind" | "slot-occupied" | "invalid-slot" } — ⚠ undocumented · used by `putItem`: Add `count` of `itemId` — into a specific `options.slot`, or auto-stacked across free/partial slots.
1783
- - `SplitResult` (type): type SplitResult = | { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "invalid-slot" | "empty-slot" | "invalid-amount" | "no-space" | "slot-occupied" } — Outcome of {@link splitStack}: the new state, or a rejection reason.
1859
+ - `SplitResult` (type): type SplitResult = | { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "invalid-slot" | "empty-slot" | "invalid-amount" | "no-space" | "slot-occupied" | "over-stack" } — Outcome of {@link splitStack}: the new state, or a rejection reason.
1784
1860
  - `TakeResult` (type): type TakeResult = { status: "ok"; state: InventoryState } | { status: "rejected"; reason: "insufficient" } — ⚠ undocumented · used by `takeItem`: Remove `count` of `itemId`, draining from the last matching stacks first.
1785
1861
  - `countItem` (function): function countItem(state: InventoryState, itemId: string): number — Total quantity of `itemId` summed across every stack in the inventory.
1786
1862
  - `createEmptyInventory` (function): function createEmptyInventory(layout: InventoryLayout): InventoryState — A bag of stackable items supporting add, remove, count, and move — the base inventory model.
@@ -1788,7 +1864,7 @@
1788
1864
  - `hasItem` (function): function hasItem(state: InventoryState, itemId: string, count: number): boolean — True when the inventory holds at least `count` of `itemId`.
1789
1865
  - `moveItem` (function): function moveItem(from: InventoryState, fromSlot: number, to: InventoryState, toLayout: InventoryLayout, traits: ItemTraits, toSlot?: number): MoveResult — Move the stack at `fromSlot` to `toSlot` (or auto-stack when omitted) — swapping, merging with remainder, or relocating across two inventories. Rejects on empty source, wrong kind, or no space.
1790
1866
  - `putItem` (function): function putItem(state: InventoryState, layout: InventoryLayout, traits: ItemTraits, itemId: string, count: number, options?: { slot?: number }): PutResult — Add `count` of `itemId` — into a specific `options.slot`, or auto-stacked across free/partial slots. Rejects on no space or wrong kind.
1791
- - `splitStack` (function): function splitStack(state: InventoryState, slot: number, amount: number, toSlot?: number): SplitResult — Splits `amount` off the stack in `slot` into `toSlot` (or the first empty slot when `toSlot` is omitted). Limit-agnostic: it does not consult {@link ItemTraits}, so a same-item target merges by simple addition. Rejects an empty source, `amount <= 0`, `amount >= stack count` (a split must leave both sides non-empty), no free slot, or a target holding a different item.
1867
+ - `splitStack` (function): function splitStack(state: InventoryState, slot: number, amount: number, toSlot?: number, limit?: number): SplitResult — Splits `amount` off the stack in `slot` into `toSlot` (or the first empty slot when `toSlot` is omitted). Without `limit` it is limit-agnostic: a same-item target merges by simple addition. Pass the item's stack limit to keep the merge legal, which rejects `over-stack`. Rejects an empty source, `amount <= 0`, `amount >= stack count` (a split must leave both sides non-empty), no free slot, or a target holding a different item.
1792
1868
  - `takeItem` (function): function takeItem(state: InventoryState, itemId: string, count: number): TakeResult — Remove `count` of `itemId`, draining from the last matching stacks first. Rejects if fewer than `count` are held.
1793
1869
 
1794
1870
  ## @jgengine/core/inventory/shapedGrid
@@ -1798,8 +1874,10 @@
1798
1874
  - `GridAdjacencyQuery` (interface): interface GridAdjacencyQuery { cellsOf(id: string): readonly Cell[]; neighborsOf(id: string): readonly string[]; touching(idA: string, idB: string): boolean; adjacentCells(cells: readonly Cell[]): readonly Cell[] } — ⚠ undocumented
1799
1875
  - `Placement` (interface): interface Placement<T> { id: string; value: T; footprint: Footprint; origin: Cell; rotation: Rotation } — ⚠ undocumented
1800
1876
  - `Rotation` (type): type Rotation = 0 | 1 | 2 | 3 — ⚠ undocumented · used by `canPlace`: Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved.
1877
+ - `ShapedFitRejection` (type): type ShapedFitRejection = "duplicate-id" | "invalid-footprint" | "too-large" | "no-space" | "budget-exceeded" — Why a first-fit placement failed: the footprint cannot fit an empty board at any offered rotation (`too-large`), the board is genuinely full (`no-space`), or the search hit its budget.
1878
+ - `ShapedFitResult` (type): type ShapedFitResult<T> = | { status: "ok"; grid: ShapedGrid<T>; origin: Cell; rotation: Rotation; checks: number } | { status: "rejected"; reason: ShapedFitRejection; detail: string; checks: number } — A placed board, or a refusal that names why the item does not fit.
1801
1879
  - `ShapedGrid` (interface): interface ShapedGrid<T> { readonly width: number; readonly height: number; readonly placements: readonly Placement<T>[] } — ⚠ undocumented · used by `canPlace`: Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved.
1802
- - `ShapedItem` (interface): interface ShapedItem<T> { id: string; value: T; footprint: Footprint } — ⚠ undocumented · used by `placeShaped`: Place a caller-defined item without mutating the source board; duplicate ids reject.
1880
+ - `ShapedItem` (interface): interface ShapedItem<T> { id: string; value: T; footprint: Footprint } — ⚠ undocumented · used by `fitShapedItem`: Place a shaped item at the first legal spot, or say why it does not fit.
1803
1881
  - `ShapedPlaceResult` (interface): interface ShapedPlaceResult<T> { status: "ok"; grid: ShapedGrid<T> } — ⚠ undocumented
1804
1882
  - `ShapedPlacementSearchOptions` (interface): interface ShapedPlacementSearchOptions — Search policy: rotation order first, then rows and columns; the work budget counts candidate origins.
1805
1883
  - `ShapedPlacementSearchResult` (type): type ShapedPlacementSearchResult = | { status: "found"; origin: Cell; rotation: Rotation; checks: number } | { status: "no-space"; checks: number } | { status: "budget-exceeded"; checks: number } — A detached placement, a completed no-fit search, or an incomplete search that reached its budget.
@@ -1809,6 +1887,7 @@
1809
1887
  - `canPlace` (function): function canPlace<T>(grid: ShapedGrid<T>, footprint: Footprint, origin: Cell, rotation: Rotation, ignoreId?: string): ShapedRejection | null — Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved.
1810
1888
  - `createShapedGrid` (function): function createShapedGrid<T>(width: number, height: number): ShapedGrid<T> — An immutable spatial inventory board holding caller-defined multi-cell items.
1811
1889
  - `findShapedPlacement` (function): function findShapedPlacement<T>(grid: ShapedGrid<T>, footprint: Footprint, options: ShapedPlacementSearchOptions = {}): ShapedPlacementSearchResult — Find the first legal placement without mutating the board; occupancy is indexed once per search.
1890
+ - `fitShapedItem` (function): function fitShapedItem<T>(grid: ShapedGrid<T>, item: ShapedItem<T>, options: ShapedPlacementSearchOptions = {}): ShapedFitResult<T> — Place a shaped item at the first legal spot, or say why it does not fit. Distinguishes an item that can never fit the board (`too-large`, with its footprint and the board size in `detail`) from a full board (`no-space`) and an exhausted search (`budget-exceeded`, not a full bag).
1812
1891
  - `moveShaped` (function): function moveShaped<T>(grid: ShapedGrid<T>, id: string, origin: Cell, rotation?: Rotation): ShapedResult<T> — Move or rotate one placement, ignoring its current cells during overlap checks.
1813
1892
  - `placeShaped` (function): function placeShaped<T>(grid: ShapedGrid<T>, item: ShapedItem<T>, origin: Cell, rotation: Rotation = 0): ShapedResult<T> — Place a caller-defined item without mutating the source board; duplicate ids reject.
1814
1893
  - `removeShaped` (function): function removeShaped<T>(grid: ShapedGrid<T>, id: string): ShapedResult<T> — Remove one placement without mutating the source board.
@@ -1969,6 +2048,62 @@
1969
2048
  - `createWeaponStats` (function): function createWeaponStats(resolveEntry: (itemId: string) => WeaponEntry | null | undefined): WeaponStats — Resolve per-weapon stat values — damage, fire rate, spread — for combat math.
1970
2049
  - `getWeaponStat` (function): function getWeaponStat(entry: WeaponEntry | null | undefined, stat: string): number | null — ⚠ undocumented
1971
2050
 
2051
+ ## @jgengine/core/loot/lootGen
2052
+
2053
+ - `LootBaseDef` (interface): interface LootBaseDef extends ItemBaseDef — A base family (frame, housing, chassis — caller nouns) with its slots, stats and affix pools.
2054
+ - `LootCatalog` (interface): interface LootCatalog — Everything a roll reads: bases, parts, affixes, rarity odds, makers and the level curve.
2055
+ - `LootComparison` (interface): interface LootComparison — How two items compare: every stat delta plus one weighted score for each.
2056
+ - `LootComparisonOptions` (interface): interface LootComparisonOptions — Caller policy for the comparison score.
2057
+ - `LootInstalledPart` (interface): interface LootInstalledPart — One part occupying one slot of a rolled item.
2058
+ - `LootIssueCode` (type): type LootIssueCode = | "invalid-level" | "unknown-base" | "unknown-manufacturer" | "manufacturer-base" | "unknown-rarity" | "duplicate-slot" | "unknown-slot" | "missing-slot-part" | "unknown-part" | "part-category" | "unknown-affix" | "duplicate-affix" | "invalid-affix" | "non-finite-stat" | "empty-… — A defect found by {@link validateLootItem}.
2059
+ - `LootItem` (interface): interface LootItem — One rolled item: the base, maker, rarity, level, composed name, chosen parts, rolled affixes and resolved stats. Plain JSON, so a save stores it and the instance registry can key it.
2060
+ - `LootManufacturerDef` (interface): interface LootManufacturerDef — A maker with a stat personality: its mark goes in the name, `bias` shifts stats additively, `scale` multiplies them, and favored affix pools weigh more when this maker rolls the item.
2061
+ - `LootPartDef` (interface): interface LootPartDef extends PartDef — A part a base can mount: its stats, a pick weight, and an optional word for the item name.
2062
+ - `LootRollOptions` (interface): interface LootRollOptions — Pin any part of a roll; omitted fields are drawn from the catalog.
2063
+ - `LootRoller` (interface): interface LootRoller — A roll counter over one catalog: every roll goes through {@link rollLoot}, and the handle keeps counters (a distribution readout in the debug overlay) that snapshot and restore as plain data.
2064
+ - `LootRollerOptions` (interface): interface LootRollerOptions — Options for {@link createLootRoller}.
2065
+ - `LootRollerState` (interface): interface LootRollerState — Saved counters of a {@link LootRoller}.
2066
+ - `LootStatDelta` (interface): interface LootStatDelta — A per-stat comparison entry; `delta` is `b - a`, so positive means the second item is larger.
2067
+ - `LootValidationIssue` (interface): interface LootValidationIssue — One reason a rolled item is not legal against its catalog, with the offending value named.
2068
+ - `MAX_LOOT_AFFIXES` (const): const MAX_LOOT_AFFIXES: 8 — Cap on affixes one item may roll, so a bad catalog cannot make a drop cost unbounded work.
2069
+ - `assertLootCatalog` (function): function assertLootCatalog(catalog: LootCatalog): void — Check a catalog before rolling; throws `RangeError` naming the offending entry.
2070
+ - `compareLootItems` (function): function compareLootItems(a: LootItem, b: LootItem, options: LootComparisonOptions = {}): LootComparison — Compare two rolled items: every stat delta plus one weighted score for each, with caller-named weights and which stats read better when small.
2071
+ - `createLootRoller` (function): function createLootRoller(catalog: LootCatalog, options: LootRollerOptions = {}): LootRoller — Wrap a catalog in a roll counter: same results as {@link rollLoot}, plus counts by rarity for a debug overlay, snapshot/restore of those counts, and an optional devtools probe.
2072
+ - `lootLevelScale` (function): function lootLevelScale(catalog: LootCatalog, level: number): number — Stat multiplier at `level` from the catalog's level curve; `1` when the catalog declares none.
2073
+ - `lootStatContributions` (function): function lootStatContributions(stats: Readonly<Record<string, number>>, options: Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> = {}): Record<string, number> — Per-stat weighted contribution: `value × weight`, negated for stats listed in `lowerIsBetter`. This is the single weight policy behind {@link scoreLootStats}, {@link compareLootItems} and a gear score, so an item and a whole loadout are never ranked by different arithmetic.
2074
+ - `rollLoot` (function): function rollLoot(catalog: LootCatalog, rng: () => number, options: LootRollOptions = {}): LootItem — Roll one item from a catalog: a base and maker, a weighted rarity, one part per slot, rarity-count affixes, level-scaled stats and a name composed from maker, rarity, part, affix words and base. Deterministic for a given `rng`; every field it reads is caller data. Validate a catalog built outside {@link createLootRoller} once with {@link assertLootCatalog} before rolling from it.
2075
+ - `scoreLootStats` (function): function scoreLootStats(stats: Readonly<Record<string, number>>, options: Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> = {}): number — One stat block's weighted score: the sum of `value × weight`, negated for stats listed in `lowerIsBetter`. Pair it with {@link lootStatContributions} when a caller also needs the split.
2076
+ - `validateLootItem` (function): function validateLootItem(item: LootItem, catalog: LootCatalog): LootValidationIssue[] — Check one rolled item against its catalog: base, maker and rarity exist, every slot holds an eligible part, affixes belong to the base's pools, stats are finite and the item is named.
2077
+
2078
+ ## @jgengine/core/loot/lootWeapon
2079
+
2080
+ - `LootWeaponProjection` (interface): interface LootWeaponProjection — A base weapon definition plus the stat map a rolled item overrides.
2081
+ - `LootWeaponStatMap` (interface): interface LootWeaponStatMap — Which rolled stat feeds which part of a {@link WeaponDefinition}. Every field is a stat id from the item; an omitted field keeps the template's value, so a catalog only maps what it rolls.
2082
+ - `lootWeaponDefinition` (function): function lootWeaponDefinition(item: LootItem, projection: LootWeaponProjection): WeaponDefinition — Project a rolled item onto a plain weapon definition: copy the template, then write the mapped stats into rate, damage, magazine, spread, range and projectile speed. The result is a {@link WeaponDefinition}, so `assertWeaponDefinition` and `createWeaponSystem` accept it unchanged.
2083
+
2084
+ ## @jgengine/core/progression/gearScore
2085
+
2086
+ - `GearScore` (interface): interface GearScore — A scored loadout: the total, each slot's share, each stat's signed contribution, and how complete it is.
2087
+ - `GearScoreOptions` (interface): interface GearScoreOptions extends Pick<LootComparisonOptions, "weights" | "lowerIsBetter"> — Caller policy for a loadout score: stat weights, which stats read better small, and what level, rarity and an empty slot are worth.
2088
+ - `GearSlotItem` (interface): interface GearSlotItem — The least a worn item must expose to be scored: its stat block, plus optional level and rarity for the gear-score terms.
2089
+ - `compareGearSets` (function): function compareGearSets(a: Readonly<Record<string, GearSlotItem | null | undefined>>, b: Readonly<Record<string, GearSlotItem | null | undefined>>, options: GearScoreOptions = {}): { a: GearScore; b: GearScore; delta: number; better: "a" | "b" | "equal" } — Compare two loadouts; negative means `a` scores lower.
2090
+ - `gearScore` (function): function gearScore(set: Readonly<Record<string, GearSlotItem | null | undefined>>, options: GearScoreOptions = {}): GearScore — Score a whole loadout with one documented formula:
2091
+
2092
+ ## @jgengine/core/progression/skillTree
2093
+
2094
+ - `SkillActiveDef` (interface): interface SkillActiveDef — An action a node or synergy unlocks, shaped to bind straight into `createAbilityKit`'s slot config.
2095
+ - `SkillNodeDef` (interface): interface SkillNodeDef<TStat extends string = string> — One authored tree node. Every field except `id` is optional data, so a game states its own tree: what a rank costs, what must come first, the passive it folds in and the actions it opens.
2096
+ - `SkillRequirement` (interface): interface SkillRequirement — One node that must be at or above `rank` for a node or synergy to open; `rank` defaults to `1`.
2097
+ - `SkillResolution` (interface): interface SkillResolution<TStat extends string = string> — The whole outcome of a build: merged passives, unlocked actions, live synergies and point accounting.
2098
+ - `SkillSpendReason` (type): type SkillSpendReason = | "unknown-node" | "max-rank" | "no-points" | "requires" | "branch-points" | "excluded" — Why a spend was refused; each reason is reachable from authored data, so a UI can say which rule bit.
2099
+ - `SkillSpendResult` (type): type SkillSpendResult = | { ok: true; spent: number; pointsAvailable: number } | { ok: false; reason: SkillSpendReason; detail: string } — The outcome of one spend or legality check: what it costs and the balance after it, or the rule that stopped it.
2100
+ - `SkillSynergyDef` (interface): interface SkillSynergyDef<TStat extends string = string> — A payoff that exists only when two or more nodes are ranked together. This is what rewards breadth over dumping every point into one branch.
2101
+ - `SkillTree` (interface): interface SkillTree<TStat extends string = string> — A live skill tree over one authored config, with respec, snapshot/restore and an optional devtools probe.
2102
+ - `SkillTreeConfig` (interface): interface SkillTreeConfig<TStat extends string = string> — Everything {@link createSkillTree} reads: nodes, optional synergies, and starting points.
2103
+ - `SkillTreeState` (interface): interface SkillTreeState — Saved tree state: unspent points and the rank of every ranked node.
2104
+ - `assertSkillTreeConfig` (function): function assertSkillTreeConfig<TStat extends string>(config: SkillTreeConfig<TStat>): void — Check tree data before play; throws `RangeError` naming the offending node, requirement or synergy.
2105
+ - `createSkillTree` (function): function createSkillTree<TStat extends string = string>(config: SkillTreeConfig<TStat>, options: { devtoolsName?: string } = {}): SkillTree<TStat> — A data-authored skill tree: nodes carry a point cost, prerequisites, passives and unlocked actions, synergies pay off two or more nodes together, and `respec` refunds exactly the points spent. Spend against `canSpend` for a reason a UI can show; read `resolved()` for the stat modifiers and action list to feed `createStats` / `createAbilityKit`.
2106
+
1972
2107
  ## @jgengine/core/progression/statGraph
1973
2108
 
1974
2109
  - `StatContribution` (interface): interface StatContribution — One folded contribution to a value, retained so a sheet can explain "why is this value 42?" — every step carries the source that produced it.
@@ -1986,6 +2121,15 @@
1986
2121
  - `createStatGraph` (function): function createStatGraph(def: StatGraphDef): StatGraph — A data-driven stat graph: game-owned named inputs feed caller-authored derived formulas, with contribution provenance, uncommitted previews, cycle detection, and selective recomputation. Formula semantics and numeric tables stay entirely game-defined.
1987
2122
  - `statModifierContributions` (function): function statModifierContributions<TStat extends string>(source: string, set: StatModifierSet<TStat>): Record<string, StatContribution[]> — Bridge the shared {@link StatModifierSet} shape (add/multiply, used by talents, items, and buffs) into stat-graph contributions, so a ranked talent tree or gear roll feeds the graph as one named source instead of a parallel store.
1988
2123
 
2124
+ ## @jgengine/core/progression/xpCurve
2125
+
2126
+ - `XpCurve` (interface): interface XpCurve — A resolved, validated {@link XpCurveDef}: thresholds, lifetime totals, a stated rule, and a leveling track bound to the same numbers.
2127
+ - `XpCurveDef` (interface): interface XpCurveDef — Authorable XP curve data. Thresholds are generated from one growth rule, so a game states its curve and its cap instead of hand-writing a table it cannot rebalance, and every consumer of the same data agrees on the numbers.
2128
+ - `XpGrowthRule` (type): type XpGrowthRule = "flat" | "linear" | "power" | "geometric" | "table" — Named rule a level threshold grows by. `flat` repeats `step`; `linear` adds `growthRate` per level; `power` uses `level ^ growthRate`; `geometric` multiplies by `growthRate`; `table` reads `table`.
2129
+ - `XpLevelStep` (interface): interface XpLevelStep — One row of a resolved curve: the threshold at `level`, and the lifetime XP it sits at.
2130
+ - `assertXpCurve` (function): function assertXpCurve(def: XpCurveDef): void — Check curve data before use; throws `RangeError` naming the offending field.
2131
+ - `defineXpCurve` (function): function defineXpCurve(def: XpCurveDef): XpCurve — Resolve authored XP curve data: the threshold per level, the lifetime XP it sits at, a plain-words growth rule, and the level an XP total reaches under the cap. One data record with one growth rule keeps a rebalance in the save-visible definition rather than in formulas scattered across a game.
2132
+
1989
2133
  ## @jgengine/core/puzzle/cellGrid
1990
2134
 
1991
2135
  - `CellGrid` (interface): interface CellGrid<T> — A fixed-size 2D board of cells, row-major from the top-left; `null` is an empty cell. Plain data, so it serializes and restores as-is.
@@ -36,12 +36,15 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
36
36
 
37
37
  - `cancelJob` (function) · `import { cancelJob } from "@jgengine/core/gameplay"`
38
38
 
39
- ## carry-encumbrance — carried mass vs capacity into movement penalty tiers and a speed multiplier
39
+ ## carry-encumbrance — gate a pickup against the carrier's remaining capacity
40
40
 
41
+ - `canCarry` (function) · `import { canCarry } from "@jgengine/core/inventory/carryLoad"`
42
+ - `characterLoad` (function) · `import { characterLoad } from "@jgengine/core/inventory/carryLoad"`
41
43
  - `resolveEncumbrance` (function) · `import { resolveEncumbrance } from "@jgengine/core/inventory/encumbrance"`
42
44
 
43
- ## carry-load — sum carried mass from item stacks via an injected mass resolver
45
+ ## carry-load — read carried stacks out of one or more inventories for a weight query
44
46
 
47
+ - `carriedEntries` (function) · `import { carriedEntries } from "@jgengine/core/inventory/carryLoad"`
45
48
  - `totalLoad` (function) · `import { totalLoad } from "@jgengine/core/inventory/encumbrance"`
46
49
 
47
50
  ## cell-grid — immutable 2D board of cells with row clears, gravity collapse, and match runs
@@ -148,6 +151,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
148
151
 
149
152
  - `enqueue` (function) · `import { enqueue } from "@jgengine/core/gameplay"`
150
153
 
154
+ ## equipment-slot-inventory — wear gear by slot with item-kind rules, two-handed blocking and replace-on-equip
155
+
156
+ - `createEquipment` (function) · `import { createEquipment } from "@jgengine/core/inventory/equipment"`
157
+
151
158
  ## event-bus — typed publish/subscribe bus for gameplay events
152
159
 
153
160
  - `createGameEvents` (function) · `import { createGameEvents } from "@jgengine/core/gameplay"`
@@ -188,6 +195,10 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
188
195
 
189
196
  - `SystemDefinition` (interface) · `import { SystemDefinition } from "@jgengine/core/gameplay"`
190
197
 
198
+ ## gear-score — score a loadout from stat weights, level, rarity and slot coverage
199
+
200
+ - `gearScore` (function) · `import { gearScore } from "@jgengine/core/gameplay"`
201
+
191
202
  ## group-credits — turn a flat list of credited things into ordered credit sections
192
203
 
193
204
  - `groupCredits` (function) · `import { groupCredits } from "@jgengine/core/game/credits"`
@@ -212,7 +223,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
212
223
 
213
224
  - `createEmptyInventory` (function) · `import { createEmptyInventory } from "@jgengine/core/inventory/inventoryModel"`
214
225
 
215
- ## inventory-split — split a stack into a target or first-empty slot
226
+ ## inventory-split — split a stack into a target or first-empty slot, honoring a stack limit
216
227
 
217
228
  - `splitStack` (function) · `import { splitStack } from "@jgengine/core/inventory/inventoryModel"`
218
229
 
@@ -287,20 +298,45 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
287
298
 
288
299
  - `RarityStyle` (interface) · `import { RarityStyle } from "@jgengine/core/gameplay"`
289
300
 
301
+ ## loot-comparison — compare two rolled items with per-stat deltas and a weighted score
302
+
303
+ - `compareLootItems` (function) · `import { compareLootItems } from "@jgengine/core/gameplay"`
304
+
290
305
  ## loot-filter — filter and highlight drops by rarity/name rules
291
306
 
292
307
  - `evaluateLootFilter` (function) · `import { evaluateLootFilter } from "@jgengine/core/gameplay"`
293
308
 
309
+ ## loot-generation — roll a named item from bases, parts, affixes, rarity weights, manufacturer personalities and level scaling
310
+
311
+ - `rollLoot` (function) · `import { rollLoot } from "@jgengine/core/gameplay"`
312
+
294
313
  ## loot-pipeline — compose ordered loot pools with fallbacks, roll modifiers, and provenance
295
314
 
296
315
  - `createLootPipeline` (function) · `import { createLootPipeline } from "@jgengine/core/gameplay"`
297
316
  - `defineLootPipeline` (function) · `import { defineLootPipeline } from "@jgengine/core/gameplay"`
298
317
 
318
+ ## loot-roller — roll items from one validated catalog while counting drops per rarity for a debug probe
319
+
320
+ - `createLootRoller` (function) · `import { createLootRoller } from "@jgengine/core/gameplay"`
321
+
322
+ ## loot-score — split one stat block into per-stat weighted contributions
323
+
324
+ - `lootStatContributions` (function) · `import { lootStatContributions } from "@jgengine/core/gameplay"`
325
+ - `scoreLootStats` (function) · `import { scoreLootStats } from "@jgengine/core/gameplay"`
326
+
299
327
  ## loot-table — register loot tables and roll weighted randomized drops
300
328
 
301
329
  - `createLootRegistry` (function) · `import { createLootRegistry } from "@jgengine/core/gameplay"`
302
330
  - `lootTable` (function) · `import { lootTable } from "@jgengine/core/gameplay"`
303
331
 
332
+ ## loot-validation — check a rolled item against its catalog and report each defect with its code
333
+
334
+ - `validateLootItem` (function) · `import { validateLootItem } from "@jgengine/core/gameplay"`
335
+
336
+ ## loot-weapon — feed a rolled item into a weapon definition so generated gear fires through the weapon system
337
+
338
+ - `lootWeaponDefinition` (function) · `import { lootWeaponDefinition } from "@jgengine/core/gameplay"`
339
+
304
340
  ## maturation-stage — resolve the baby/juvenile/adolescent/adult stage from elapsed maturation
305
341
 
306
342
  - `maturationStage` (function) · `import { maturationStage } from "@jgengine/core/game/breeding"`
@@ -523,11 +559,16 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
523
559
  ## shaped-placement — find a shaped inventory item's first fit with ordered rotations, row-major placement, and an explicit search budget
524
560
 
525
561
  - `findShapedPlacement` (function) · `import { findShapedPlacement } from "@jgengine/core/inventory/shapedGrid"`
562
+ - `fitShapedItem` (function) · `import { fitShapedItem } from "@jgengine/core/inventory/shapedGrid"`
526
563
 
527
564
  ## shop-grid — serializable vendor/shop stock with buy/sell over a caller-owned wallet — finite/unlimited qty, sell-back, restock/setPrice, snapshot/restore
528
565
 
529
566
  - `createShopStock` (function) · `import { createShopStock } from "@jgengine/core/gameplay"`
530
567
 
568
+ ## skill-tree — spend, refuse and refund points over an authored tree with passives, actives and synergies
569
+
570
+ - `createSkillTree` (function) · `import { createSkillTree } from "@jgengine/core/gameplay"`
571
+
531
572
  ## social-emotes — emotes and social interactions between nearby players
532
573
 
533
574
  - `createSocial` (function) · `import { createSocial } from "@jgengine/core/gameplay"`
@@ -660,3 +701,7 @@ Reach for these before hand-rolling. Each row is *the thing you need* → *the p
660
701
  ## work-queued-jobs — list jobs waiting for a slot, in selection order
661
702
 
662
703
  - `queuedJobs` (function) · `import { queuedJobs } from "@jgengine/core/gameplay"`
704
+
705
+ ## xp-curve — resolve an authored xp growth rule into per-level thresholds, totals and a level cap
706
+
707
+ - `defineXpCurve` (function) · `import { defineXpCurve } from "@jgengine/core/gameplay"`