Docs: DR-026 inventory/equipment/progression foundation + session log
DR-026 records the architecture (data-driven catalog + replicated buffers + reuse of the StatModifier/RPC machinery), the Phase 0-4 roadmap, the 7 validated decisions, and the deliberate gameplay choices (personal-harvest pivot, automation asymmetry, session-only inventory). Adds the Build-section inventory pointer to CLAUDE.md net-zero (39923 bytes, >1KB headroom) by condensing the persistence/world/HUD bullets; trimmed wording archived to the gotchas archive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
---
|
||||
date: 2026-06-08
|
||||
type: session
|
||||
tags:
|
||||
- session
|
||||
- inventory
|
||||
- equipment
|
||||
- progression
|
||||
- items
|
||||
- netcode
|
||||
- ghostfield
|
||||
- harvesting
|
||||
- ultracode
|
||||
permalink: gamevault/07-sessions/2026/2026-06-08-inventory-equipment-progression-phase0
|
||||
---
|
||||
|
||||
# Session 2026-06-08 — Inventory · Equipment · Progression (Phase 0 backbone)
|
||||
|
||||
> Driven by `/dots-dev` in **ultracode** mode. Operator: *"Expand combat and harvesting for resources… first the
|
||||
> concept of an inventory and equipment (granting spells/affects); harvesting should require axes/pickaxes/tools;
|
||||
> this may require a progression system to be scalable — think carefully, handle scale + flexibility."*
|
||||
|
||||
## Decision & design
|
||||
|
||||
Architecture, phased roadmap, and the 7 Phase-0 decisions live in [[DR-026_Inventory_Equipment_Progression_Foundation]].
|
||||
Intake forks (one `AskUserQuestion`): **fully per-player inventory · gear-tier + crafting progression ·
|
||||
weapon-granted swappable abilities · plan + build the Phase 0 backbone**.
|
||||
|
||||
Before any code, a **5-lens adversarial design-review Workflow** (netcode/relevancy · determinism-Burst ·
|
||||
architecture-scale · economy-regression · test-strategy → synthesis) audited the design against the real code.
|
||||
Verdict **GO_WITH_CHANGES**. Key catches that changed the implementation:
|
||||
|
||||
- **Blocker (4/5 lenses):** the planned "add `RefRO<GhostOwner>` to the projectile query" would make GhostOwner a
|
||||
*required* component → the 8 existing harvest tests seed owner-less projectiles → they'd never match and **fail**,
|
||||
and the ledger-fallback never runs. **Fix:** optional cached `ComponentLookup<GhostOwner>`, query unchanged.
|
||||
- **Reachable deposit trigger required** — harvest→bag severs the build economy until deposit; the dev `#if` hook is
|
||||
not enough → added a real `G` key.
|
||||
- **Hoist** the catalog/owner-map/buffer lookups out of the per-hit sweep (stays `[BurstCompile]`).
|
||||
- **Mandatory player re-bake** (new `[GhostField]` buffer changes the ghost hash) + a **headless Play gate** (EditMode
|
||||
can't test replication; `NetCodeTestWorld` is internal).
|
||||
- **Scalability add-now:** bake `byte Tier` into `ItemDefBlob` (free re-bake later, but content-only Phase 1/2 now).
|
||||
|
||||
## What was built (code-complete, clean compile, 228/228 EditMode green)
|
||||
|
||||
**Simulation (`_Project/Scripts/Simulation/Items/`):** `ItemCategory` (byte consts), `ItemDefBlob`/`ItemDatabaseBlob`
|
||||
(ID-keyed), `ItemDatabase` (blob singleton), `InventorySlot` (`[GhostField]` `OwnerSendType.All` buffer, twin of
|
||||
`StatModifier`), `InventoryMath` (pure stack/cap/withdraw), `InventoryDepositRequest` (`IRpcCommand`). +
|
||||
`Tuning.InventoryMaxSlots=24` / `DefaultStackMax=999`.
|
||||
|
||||
**Authoring:** `ItemDefinition` SO (byte `Category`/`Tier` to dodge the MCP enum-drop + Burst-enum hazards) +
|
||||
`ItemDatabaseAuthoring` baker (mirrors `AbilityDatabaseAuthoring`). `PlayerAuthoring` now `AddBuffer<InventorySlot>`.
|
||||
|
||||
**Server:** `InventoryDepositSystem` (RPC → withdraw from bag, deposit to `ResourceLedger`, owner-map idiom,
|
||||
`ItemId==0` = deposit-all). `ResourceHarvestSystem` rerouted: owner read via optional `ComponentLookup<GhostOwner>`,
|
||||
deposit into the harvesting player's `InventorySlot` (stackMax from `ItemDatabase` if present), remainder/unresolvable
|
||||
→ ledger.
|
||||
|
||||
**Client:** `InventoryDepositSendSystem` (`G` = deposit all + `#if UNITY_EDITOR` static hook, mirrors
|
||||
`StorageOpSendSystem`). `HudSystem` folds in a read-only inventory panel (bottom-right, toggle `I`, names from the
|
||||
catalog, `G` hint) — null-safe, observe-only.
|
||||
|
||||
**Tests (`_Project/Tests/EditMode/`):** `InventoryMathTests`, `InventoryHarvestTests` (owned→bag / full-bag→ledger /
|
||||
no-matching-player→ledger), `InventoryDepositSystemTests` (specific / deposit-all / unresolvable). The 8
|
||||
`ResourceHarvestSystemTests` stay green via the genuine no-owner fallback.
|
||||
|
||||
## Play validation (done — task #9 closed)
|
||||
|
||||
Focused-editor finish completed this session:
|
||||
1. Authored 4 `ItemDefinition` assets (Aether/Ore/Biomass + Stone Pickaxe tool, id 10) under `Assets/_Project/Items/`
|
||||
and wired `ItemDatabaseAuthoring` (Items list assigned via `execute_code` to dodge the MCP ref-drop) into the
|
||||
`Gameplay` subscene; `refresh_unity scope=all force` re-baked.
|
||||
2. Headless `execute_code` Play gate (host+client), all green:
|
||||
- `ItemDatabase` baked into BOTH worlds (4 items); player ghost carries `InventorySlot` in both → **re-bake +
|
||||
handshake clean** (no ghost-hash desync).
|
||||
- Server write to the player bag (`InventoryMath.Deposit` Ore x7 + Aether x3) **replicated to the `ClientWorld`
|
||||
owner** — the proof EditMode can't make.
|
||||
- `InventoryDepositSendSystem.Deposit(0,0)` (the `G` path) **round-tripped**: server ledger ← bag, both
|
||||
inventories emptied, empty state replicated back.
|
||||
- HUD panel (`_invOpen` forced) rendered 4 rows with **catalog names** incl. `Stone Pickaxe x1` (non-resource
|
||||
lookup works).
|
||||
|
||||
## Deliberate decisions to remember (so a later session doesn't "fix" them)
|
||||
|
||||
- Manual harvest is personal-then-deposit; **automation stays base-direct** (asymmetry is intentional).
|
||||
- Inventory is **session-only** — undeposited haul is lost on Continue (Phase 3 adds `SaveData` v3).
|
||||
- Don't unify `ResourceLedger` / `SharedStorageContainer` / `InventorySlot` (separate decision); don't touch
|
||||
`SharedStorageContainer`.
|
||||
|
||||
## Next-session intent
|
||||
|
||||
Finish the focused-editor Play gate above, then **Phase 1 — Equipment slots + `EquipmentEffectSystem`** (weapon →
|
||||
`AbilityRef.Id`, gear → `StatModifier` bundle by slot `SourceId`), then **Phase 2 — tool-gated harvesting**.
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
---
|
||||
id: DR-026
|
||||
title: Inventory · Equipment · Progression foundation — data-driven ItemDatabase catalog + per-player replicated inventory, gear-tier progression, weapon-granted abilities (Phase 0 backbone)
|
||||
status: accepted
|
||||
date: 2026-06-08
|
||||
tags:
|
||||
- decision
|
||||
- inventory
|
||||
- equipment
|
||||
- progression
|
||||
- items
|
||||
- netcode
|
||||
- ghostfield
|
||||
- economy
|
||||
- harvesting
|
||||
- architecture
|
||||
permalink: gamevault/07-sessions/decisions/dr-026-inventory-equipment-progression-foundation
|
||||
---
|
||||
|
||||
# DR-026 — Inventory · Equipment · Progression foundation
|
||||
|
||||
## Context
|
||||
|
||||
Combat shipped with **one generic pre-assigned attack** (a single `AbilityRef.Id` byte → `AbilityFireSystem` →
|
||||
projectile), and harvesting was a side effect of that attack: a projectile hitting a `ResourceNode`/`BlightClutter`
|
||||
deposited yield straight into the **global shared `ResourceLedger`** ([[DR-018_World_Space_Cohesion_Pass]]). There was
|
||||
**no inventory, no items, no equipment, no tools, no character progression**. The operator asked to expand combat +
|
||||
harvesting into a system that "handles scale well and allows flexibility": inventory + equipment (granting spells /
|
||||
effects), tool-gated harvesting (axes/pickaxes), and a progression system.
|
||||
|
||||
Intake forks (one `AskUserQuestion`): **fully per-player inventory** (harvested resources land in each player's
|
||||
personal bag, then are deposited to base) · progression = **gear tiers + crafting** (reuses the Fabricator economy) ·
|
||||
"various spells" = **weapon-granted, swappable** ability now, multi-slot loadout later · this session = **plan + build
|
||||
the Phase 0 backbone**. A 5-lens adversarial design-review workflow (netcode / determinism-Burst / architecture-scale
|
||||
/ economy-regression / test-strategy) pressure-tested the Phase 0 design before any code — verdict **GO_WITH_CHANGES**,
|
||||
which this DR's decisions incorporate.
|
||||
|
||||
## Decision — the architecture (the "scale + flexibility" answer)
|
||||
|
||||
**Extend the existing data-driven spine; do not build a parallel system.** Four in-repo mechanisms do the work:
|
||||
|
||||
1. **`ItemDatabase` blob catalog** (sibling of `AbilityDatabase`) — the single source of truth for every item
|
||||
(resource, tool, weapon, gear, consumable). `ItemDefBlob { ushort ItemId; byte Category; byte Tier; int StackMax;
|
||||
FixedString64Bytes Name }`, **ID-keyed** `TryGetItem` (never index-keyed → inserting items never renumbers).
|
||||
Adding content = one authoring row + re-bake, **zero code**. This is the flexibility lever.
|
||||
2. **`ushort ItemId` id space subsumes the byte `ResourceId`** (Aether=1/Ore=2/Biomass=3 keep their ids; reserve
|
||||
>3 for new items; 0 = none). `StorageEntry.ItemId` / `StorageOpRequest.ItemId` are already `ushort` → no wire
|
||||
change. A resource is just a low-id item of `ItemCategory.Resource`.
|
||||
3. **Equipment effects fold through the existing `StatModifier` machinery** (Phase 1) — a gear piece is a bundle of
|
||||
`StatModifier`s tagged by a slot-derived `SourceId` (the M6 replace-by-SourceId pattern); a weapon sets the active
|
||||
`AbilityRef.Id`. No new folding code.
|
||||
4. **RPC-driven, server-authoritative mutation** — every player action (harvest deposit, equip, craft) is a blittable
|
||||
`IRpcCommand` applied server-only outside the predicted loop.
|
||||
|
||||
**Progression = gear tiers (catalog rows) crafted via the existing Fabricator economy.** `byte Tier` is baked into
|
||||
`ItemDefBlob` **now** (cheap; avoids a near-term re-bake) so Phase 2/3 tier gating is a content-only edit.
|
||||
|
||||
### Phased roadmap (each phase = its own approved slice)
|
||||
|
||||
- **Phase 0 — backbone (THIS session):** `ItemDatabase` catalog + per-player replicated `InventorySlot` buffer +
|
||||
harvest reroute to personal inventory + deposit-to-base RPC + read-only HUD inventory panel.
|
||||
- **Phase 1 — equipment + effects:** `EquipmentSlot` buffer (Weapon/Tool/Armor/Trinket); `EquipmentEffectSystem`
|
||||
syncs catalog modifiers into the `StatModifier` stack + sets `AbilityRef.Id` from the equipped weapon; equip RPC.
|
||||
- **Phase 2 — tool-gated harvesting:** `RequiredToolType`/`RequiredToolTier` baked on `ResourceNode`; harvest gates +
|
||||
scales yield by the owner's equipped tool tier.
|
||||
- **Phase 3 — progression / crafting:** gear-tier content; extend the Fabricator to craft *items* into inventory;
|
||||
equip-tier gates; per-player inventory persistence (additive `SaveData` v3).
|
||||
- **Phase 4 — (optional) multi-slot spell loadout:** expand `AbilityRef` into primary + hotkey spells.
|
||||
|
||||
## Phase 0 decisions (D1–D7, as validated/adjusted by the review)
|
||||
|
||||
- **D1 — `InventorySlot` is a `[GhostField]` `IBufferElementData` with `OwnerSendType.All`**, `[InternalBufferCapacity(24)]`,
|
||||
baked empty on the player (the exact `StatModifier` pattern). BOTH fields carry `[GhostField]` (the `[GhostComponent]`
|
||||
attribute alone does not replicate fields). Server-only writers → no predicted double-apply; the owner reads a pure
|
||||
snapshot and never mutates it locally. **Adding it changes the player ghost hash → a player-prefab re-bake is
|
||||
mandatory** (consistent across both worlds).
|
||||
- **D2 — harvest reroute (`ResourceHarvestSystem`, stays `[BurstCompile]`):** the projectile owner is read via an
|
||||
**optional cached `ComponentLookup<GhostOwner>`** (NOT a required query component — that would exclude owner-less
|
||||
projectiles and break the 8 existing harvest tests). Yield deposits into the owner's `InventorySlot` via
|
||||
`InventoryMath`; **unresolvable owner OR a full bag spills the remainder to the `ResourceLedger`** (no-loss valve).
|
||||
Owner-map + catalog + lookups are hoisted out of the per-hit sweep.
|
||||
- **D3 — `InventoryDepositRequest { ushort ItemId; int Count }` `IRpcCommand`** (`ItemId==0` = deposit all, handled
|
||||
before any withdraw; unconditional wire type). Server `InventoryDepositSystem` (plain server group, owner-map idiom)
|
||||
withdraws from the sender's bag and deposits into the **`ResourceLedger`** (what the build/upgrade/automation economy
|
||||
actually spends — NOT `SharedStorageContainer`). Client `InventoryDepositSendSystem` has a **real player key (`G` =
|
||||
deposit all)** plus an `#if UNITY_EDITOR` static hook — a reachable trigger is required or the economy is severed.
|
||||
- **D4 — inventory is session-only; `SaveData.CurrentVersion` is NOT bumped** (stays 2). **Deliberate, documented
|
||||
regression:** resources harvested-but-not-deposited are lost on a Continue (the haul-to-base loop means undeposited
|
||||
≠ committed). The `SharedStorageContainer` "no persistence yet" analogy was noted as imperfect (inventory now holds
|
||||
the whole manual-harvest economy); per-player persistence is an additive `SaveData` v3 in Phase 3.
|
||||
- **D5 — tests:** the 8 existing `ResourceHarvestSystemTests` stay green via the genuine no-owner ledger fallback (the
|
||||
projectile query is unchanged). New EditMode coverage: `InventoryMathTests`, owned-harvest→inventory,
|
||||
full-bag→ledger spill, owned-but-no-matching-player→ledger fallback, and the deposit-RPC server logic. Replication +
|
||||
RPC round-trip are **Play-only** (`NetCodeTestWorld` is internal) → a headless `execute_code` Play gate.
|
||||
- **D6 — `ushort ItemId` subsumes the `ResourceId` byte space** (validated; no collision, ids 1–3 stable).
|
||||
- **D7 — `InventoryMath` is a NEW pure helper** (does NOT collapse into `StorageMath`, which has no stack/slot cap):
|
||||
`Deposit(→remainder)` top-up-then-append with `stackMax`/`maxSlots`, `Withdraw(→taken)` back-to-front, `CountOf`.
|
||||
`Tuning.InventoryMaxSlots=24`, `Tuning.DefaultStackMax=999`.
|
||||
|
||||
## Deliberate gameplay consequences (stated, not accidental)
|
||||
|
||||
- **Personal-harvest pivot:** harvested resources no longer auto-credit the shared HUD strip / build affordability —
|
||||
they're personal until deposited (`G`). This is the intended gather→carry→deposit loop, not a bug.
|
||||
- **Automation asymmetry:** the Fabricator/machines still deposit **base-direct** to the ledger; only **manual** harvest
|
||||
is personal-then-deposit. Defensible (machines are base infrastructure) — do not "fix" the Fabricator to route
|
||||
through inventory.
|
||||
- **Co-op gap (latent):** each player's haul is private; there is no shared-deposit affordance yet (single-player-fine;
|
||||
flag for a co-op pass).
|
||||
|
||||
## What is locked / not touched
|
||||
|
||||
- Frozen: grid, `PlacedStructure` archetype, server-only production, `ResourceLedger` resolution discipline
|
||||
(`GetSingletonEntity<ResourceLedger>` → `GetBuffer<StorageEntry>`, **never** `GetSingleton<StorageEntry>`).
|
||||
- **Not unified this session:** the three buffers (`ResourceLedger` + `SharedStorageContainer` `StorageEntry`, and the
|
||||
new `InventorySlot`). `SharedStorageContainer` is a retirement candidate — a separate decision, untouched here.
|
||||
|
||||
## Validation status (2026-06-08)
|
||||
|
||||
Code-complete + clean compile. **228/228 EditMode tests pass** (server logic: InventoryMath, harvest reroute incl.
|
||||
the 8 legacy fallbacks, deposit RPC). **Play-validated (2026-06-08, host+client):** the catalog asset baked into BOTH
|
||||
worlds (`ItemDatabase` = 4 items); the re-baked player ghost carries `InventorySlot` in both worlds with a clean
|
||||
connect handshake; a server write to the player bag replicated to the `ClientWorld` owner (`Ore x7, Aether x3`); the
|
||||
`G` deposit-all RPC round-tripped (server ledger ← bag, both inventories emptied, empty state replicated back); and
|
||||
the HUD panel rendered the 4 carried items with names from the catalog — including the non-resource `Stone Pickaxe`
|
||||
(id 10, the catalog name lookup). The slice is shippable.
|
||||
|
||||
## Links
|
||||
|
||||
[[DR-004_Data_Driven_Abilities_Modifiers]] (StatModifier machinery reused for equipment) ·
|
||||
[[DR-008_M5_HomeBase_BaseLayer]] (StorageEntry / RPC storage pattern) ·
|
||||
[[DR-014_M6_Build_Structures_Automation_Foundation]] (data-driven catalog pattern) ·
|
||||
[[DR-018_World_Space_Cohesion_Pass]] (harvest sweep this reroutes) ·
|
||||
[[DR-020_M7_Automation_Production_Chains]] (server-only economy, Fabricator) ·
|
||||
[[DR-021_HUD_UITK_BuildPalette]] (HudSystem this extends).
|
||||
Reference in New Issue
Block a user