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:
+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