Init Homebase

This commit is contained in:
2026-06-02 18:28:23 -07:00
parent 2ee30c01fd
commit dd0064c377
48 changed files with 1934 additions and 12 deletions
@@ -30,6 +30,12 @@ One design doc per gameplay system, linked here. Each should state: purpose, com
- **Systems:** `StatRecomputeSystem` (predicted, `[UpdateBefore]` Aim/Move; folds blob base + modifier buffer → `Effective*` **every tick** — rollback-correct); `AbilityFireSystem` rerouted (effective stats + prefab-by-id + snapshot-at-fire); `PlayerMoveSystem` → effective move; `UpgradePickupSpawnSystem` / `UpgradePickupSystem` (server; overlap-grant via `AppendToBuffer`); `DebugModifierInjectionSystem` (editor-only, server world); `HealthApplyDamageSystem` clamps to effective MaxHealth. Authoring: `AbilityDefinition`/`CharacterStatsDefinition` SOs + `AbilityDatabaseAuthoring` blob baker.
- **Netcode shape:** definitions = baked config (not replicated, identical both worlds); modifiers = **replicated ghost buffer** on the player → both worlds recompute identical effective stats (prediction-correct, validated under tick-batching); pickup = **interpolated** server-authoritative ghost. Status: **built + runtime-validated** (EditMode 38/38). Decisions: [[DR-004_M3_DataDriven_Abilities_Modifiers]].
### M5 — Home base: base-layer + shared storage · [[2026-06-02_M5_HomeBase_BaseLayer]]
- **Components** (`ProjectM.Simulation/HomeBase`): `BaseAnchor` (baked singleton — `AnchorPos`, `GridOrigin`, `CellSize`, `int2 GridDims`; flat/blittable, no entity refs); `BaseGridMath` (pure static — WorldToCell/CellToWorld/IsCellInPlot/IsPointInPlot/ClampCell/PlotCenter; corner-origin, center-returning, half-open, floor); `StorageEntry` (`[GhostField]` buffer — `ushort ItemId`, `int Count`); `SharedStorageContainer` (tag); `StorageSpawner` (baked singleton — prefab + `int2 Cell`); `StorageOpRequest` (`IRpcCommand` — byte Op/ItemId/Count) + `StorageOp` consts; `StorageMath` (deposit-merge / withdraw-clamp-drop, unit-tested).
- **Systems:** `SharedStorageSpawnSystem` (server one-shot — instantiate the container ghost at `CellToWorld(cell)`, destroy spawner); `StorageOpReceiveSystem` (server `SimulationSystemGroup`, NOT predicted — apply the RPC to the singleton container's buffer via `StorageMath`); `StorageOpSendSystem` (client managed `SystemBase` — E/Q keyboard + editor-only `Deposit`/`Withdraw` statics → `StorageOpRequest` RPC). `GoInGameServerSystem` re-rooted onto `BaseGridMath.PlotCenter(BaseAnchor)` (with a `TryGetSingleton` fallback).
- **Netcode shape:** base config = **baked, ghost-free, identical both worlds** (not replicated). Storage container = **ownerless interpolated** server-spawned ghost; its `StorageEntry` buffer is a `[GhostField]` (no `OwnerSendType`/`GhostOwner`) so server mutations replicate to all clients. Deposit/withdraw = **server-authoritative `IRpcCommand`** resolved against the single container singleton, applied outside the predicted loop (no rollback double-apply). Status: **built + runtime-validated** (server == client buffer; EditMode 62/62). Decisions: [[DR-008_M5_HomeBase_BaseLayer_Storage]]. M6 (grid placement) + M7 (production) build on `BaseGridMath` + the runtime-ghost-into-cell spawn path.
## Conventions
DOTS/ECS conventions live in repo `CLAUDE.md` and the `dots-dev` skill's `dots-conventions.md`. Don't duplicate volatile API details here — link to context7-derived notes instead.
+6 -2
View File
@@ -16,7 +16,7 @@ Unordered pool of candidate work. Promote to a [[Milestones|milestone]] when com
- [ ] Replace template `SampleScene` with a dedicated bootstrap scene + gameplay subscene.
- [ ] Optional template cleanup: remove `com.unity.visualscripting`, `Assets/TutorialInfo/`, `Assets/Readme.asset` (delete each asset **with** its `.meta`).
- [x] Decide **relay provider** before M4 — resolved: **Direct IP/LAN now, Unity Relay later** ([[DR-005_M4_Connection_Model_Direct_IP]], [[2026-06-01_M4_LAN_CoOp_And_Classification_Fix]]).
- [ ] Decide home-base **grid 2D vs 3D** before M6 (build/placement).
- [x] Decide home-base **grid 2D vs 3D** before M6 — resolved 2026-06-02: **planar single-level `int2` grid**, CellSize 1.0, 32×32 plot (full 3D/stacked deferred). Locked in `BaseGridMath` — [[DR-008_M5_HomeBase_BaseLayer_Storage]].
- [ ] Decide **production replication** (predicted vs server-only) before M7 (automation).
- [ ] **M2 follow-up — restart the editor to clear the corrupted Burst cache**, then confirm the console is clean on a warm play (no "not a known Burst entry point"). See [[2026-05-31_M2_Combat]] / [[DR-003_M2_Combat_Netcode_Architecture]].
- [ ] **M2 follow-up — live interactive fire test** (focused Play Mode: press Space / LMB / RT → predicted projectile + dummy HP drop). The server combat loop + replication are validated; the input→`AbilityFireSystem`→predicted-spawn→classification path is only validated structurally.
@@ -29,7 +29,11 @@ Unordered pool of candidate work. Promote to a [[Milestones|milestone]] when com
- [ ] **M3 follow-up — standalone-server debug modifier path** via `IRpcCommand` (current `DebugModifierInjectionSystem` is in-editor single-process only).
- [ ] **M3 follow-up — rate-limited turning** (`PlayerAimSystem` still snaps rotation; `EffectiveCharacterStats.TurnRate` is wired but unused).
- [ ] **M3 polish — pickup visuals** (primitive sphere/default material currently); pickup auto-grant feel (continuous overlap).
- [ ] **M5 — base subscene streaming** (the other half of M5): persistent home-base subscene that streams in/out around players. Physics-in-prediction slice is done ([[DR-006_M5_Physics_In_Prediction]]).
- [ ] **M5 follow-up — base/expedition subscene split + streaming (Option C)**: the persistent-space split the locked world design ultimately needs (`SceneSystem.LoadSceneAsync`/`UnloadScene`, per-world load on the listen-server, enter-expedition/return-to-base transition). Deferred to its own world-architecture milestone — M6/M7 only need the anchor + grid, now done ([[DR-008_M5_HomeBase_BaseLayer_Storage]]). The physics-in-prediction + base-layer slices of M5 are done ([[DR-006_M5_Physics_In_Prediction]], [[DR-008_M5_HomeBase_BaseLayer_Storage]]).
- [ ] **M5 follow-up — shared-storage disk persistence** (host-only): runtime structures don't exist until M6, so nothing to save yet; add a thin per-record serialization slice (replayed through M6's placement path) after M6. `BaseAnchor`/`StorageEntry` are already flat/serialization-friendly.
- [ ] **M5 follow-up — storage interaction polish**: proximity gate the deposit/withdraw (the container carries `HitRadius`); real item/UI model beyond the fixed test item; multi-writer ordering beyond first-come server apply.
- [ ] **M5 follow-up — multi-client shared storage**: validate two clients see identical shared-storage buffer state (pairs with the deferred M5b multi-client interpolation + M4 two-build tests).
- [ ] **M5 follow-up — home-base visuals**: only an editor plot-bounds gizmo + a placeholder primitive container today; real ground/walls/structures arrive with M6.
- [ ] ~~**M5 follow-up — same-tick movement**~~ — moot after M5b (the CC character runs in the predicted fixed-step group; M5's `PlayerMoveSystem` is deleted). See [[DR-007_M5b_Character_Controller_Package]].
- [ ] **M5b follow-up — multi-client CC interpolation validation**: live two-build / thin-client run to confirm remote-peer interpolation smoothness (predicted-only `CharacterInterpolation` variant is in; only single-client validated). Pairs with the deferred M4 real-LAN two-build test.
- [ ] **M5b follow-up — player-vs-player collision**: currently non-physical (`SimulateDynamicBody=false`); enable + handle masses if mutual push is wanted.
+2 -2
View File
@@ -14,8 +14,8 @@ permalink: gamevault/06-roadmap/milestones
| **M1 — Player slice** | Server-spawned owner-predicted player; twin-stick WASD + directional aim | ✅ Done 2026-05-31 — runtime-validated on Unity 6.4.7 (connect→spawn→owner-predicted ghost→replication; EditMode 3/3). The 6.6 failure was environment-specific, see [[DR-002_Unity66_Alpha_Netcode_Transport]] — [[2026-05-30_M1_Player_Slice]] |
| **M2 — Combat** | Directional ability fire + deterministic soft auto-target; server-authoritative damage/health | ✅ Done 2026-05-31 — runtime-validated on 6.4.7: input→fire→**predicted projectile**→**swept hit**→server damage→`Health` `[GhostField]` replicated server→client; movement + fire confirmed live; EditMode 22/22. Predicted-projectile + server auto-target + non-Burst classifier — [[DR-003_M2_Combat_Netcode_Architecture]], [[2026-05-31_M2_Combat]]. (Projectile ghost-map errors were later root-caused to a `[ReadOnly]`-write in `ProjectileClassificationSystem` — fixed 2026-06-01, see [[2026-06-01_M4_LAN_CoOp_And_Classification_Fix]] — NOT two-editor tick-batching as first thought.) |
| **M3 — Data-driven abilities & modifiers** | Ability **and** character stats authored in ScriptableObjects, baked to DOTS **blob assets**; runtime **flat + % modifier** stacks (upgrades/buffs) → effective stats, server-authoritative + prediction-correct. Pattern slice: refactor the current projectile ability + 12 sample abilities onto the data model. | ✅ Done 2026-05-31 — runtime-validated on 6.4.7: blob DB baked into both worlds; data-driven base + replicated `StatModifier` ghost buffer → **identical effective stats on server & owner-predicted client** (held under tick-batching); data-only ability swap; real pickup grant; EditMode 38/38. Blob DB + replicated modifier buffer + every-tick effective recompute — [[DR-004_M3_DataDriven_Abilities_Modifiers]], [[2026-05-31_M3_Data_Driven_Abilities]]. |
| **M4 — Co-op** | 24 players; client-hosted listen-server (Direct IP/LAN now, Unity Relay later) | 🚧 In progress 2026-06-01**LAN slice done + runtime-validated**: no-auto-connect `ConnectionConfig` + request-component host/join, editor auto-host + thin clients, deterministic ring spawn; 3 clients (1 real + 2 thin) connect→spawn (distinct slots)→replicate→clean disconnect; `ConnectionUI` for builds; EditMode 45/45. **Unity Relay + real two-build LAN join deferred** — [[DR-005_M4_Connection_Model_Direct_IP]], [[2026-06-01_M4_LAN_CoOp_And_Classification_Fix]]. |
| **M5 — Home base + physics** | Persistent base subscene streaming + Unity Physics in the predicted loop | 🚧 In progress 2026-06-01 — **physics-in-prediction slice done + runtime-validated** on 6.4.7: player is a velocity-driven dynamic Unity Physics body in the predicted loop (built-in `CapsuleCollider`+`Rigidbody` bake; `PhysicsVelocity` auto-replicated), collides with baked static walls (stops at the surface, no tunnel/climb-over), planar-pinned, **server == client** with no desync; EditMode 51/51. **Base subscene streaming deferred** to a later pass — [[DR-006_M5_Physics_In_Prediction]], [[2026-06-01_M5_Physics_In_Prediction]]. **M5b (same day): player movement re-founded on the Unity Character Controller package** (`com.unity.charactercontroller` 1.4.2) — kinematic collide-and-slide, owner-predicted, data-driven speed; replaces the dynamic-Rigidbody mover (keeps the DR-006 predicted-physics infra). Runtime-validated (collide-and-slide, planar, server==client, CharacterInterpolation predicted-only); EditMode 47/47 — [[DR-007_M5b_Character_Controller_Package]], [[2026-06-01_M5b_Character_Controller]]. |
| **M4 — Co-op** | 24 players; client-hosted listen-server (Direct IP/LAN now, Unity Relay later) | ✅ Done 2026-06-02**LAN slice + multi-client validated**: no-auto-connect `ConnectionConfig` + request-component host/join, editor auto-host + thin clients, deterministic ring spawn; 3 clients (1 real + 2 thin) connect→spawn (distinct slots)→replicate→clean disconnect; `ConnectionUI` for builds; EditMode 45/45. **Two controllable characters in-game confirmed 2026-06-02 via Unity Multiplayer Play Mode** (extra virtual player; full connection handshake not exercised end-to-end, but in-scene co-op looks good). **Unity Relay + real two-build LAN join deferred** — [[DR-005_M4_Connection_Model_Direct_IP]], [[2026-06-01_M4_LAN_CoOp_And_Classification_Fix]]. |
| **M5 — Home base + physics** | Persistent base subscene streaming + Unity Physics in the predicted loop | 🚧 In progress 2026-06-01 — **physics-in-prediction slice done + runtime-validated** on 6.4.7: player is a velocity-driven dynamic Unity Physics body in the predicted loop (built-in `CapsuleCollider`+`Rigidbody` bake; `PhysicsVelocity` auto-replicated), collides with baked static walls (stops at the surface, no tunnel/climb-over), planar-pinned, **server == client** with no desync; EditMode 51/51. **Base subscene streaming deferred** to a later pass — [[DR-006_M5_Physics_In_Prediction]], [[2026-06-01_M5_Physics_In_Prediction]]. **M5b (same day): player movement re-founded on the Unity Character Controller package** (`com.unity.charactercontroller` 1.4.2) — kinematic collide-and-slide, owner-predicted, data-driven speed; replaces the dynamic-Rigidbody mover (keeps the DR-006 predicted-physics infra). Runtime-validated (collide-and-slide, planar, server==client, CharacterInterpolation predicted-only); EditMode 47/47 — [[DR-007_M5b_Character_Controller_Package]], [[2026-06-01_M5b_Character_Controller]]. **Base-layer done 2026-06-02 + runtime-validated:** home base = baked ghost-free `BaseAnchor` + locked deterministic planar build-grid (`BaseGridMath`, 1.0u × 32²; M6 builds on it) + player spawn re-rooted onto the anchor + one **shared-storage ghost** (ownerless interpolated; deposit/withdraw via server-authoritative `IRpcCommand`; **server == client** buffer). EditMode 62/62. **Subscene split (base/expedition) + disk persistence still deferred** — [[DR-008_M5_HomeBase_BaseLayer_Storage]], [[2026-06-02_M5_HomeBase_BaseLayer]]. |
| **M6 — Build/placement** | Server-authoritative grid build placement via RPC | ⬜ |
| **M7 — Automation** | Self-running tick-based production chains (deterministic offline catch-up) | ⬜ |
@@ -0,0 +1,52 @@
---
date: 2026-06-02
type: session
tags: [session, dots, netcode, home-base, rpc, ghost, m5, m4]
permalink: gamevault/07-sessions/2026/2026-06-02-m5-homebase-baselayer
---
# Session 2026-06-02 — M5: home-base base-layer + shared storage
## Goal
Close out M4 and take the next milestone via `/dots-dev`. Operator closed **M4** (multi-client co-op validated via Unity **Multiplayer Play Mode** — two controllable characters in-scene; full handshake not exercised end-to-end, Relay still deferred). For **M5**, clarified what "home base" means for this game and built the operator-chosen **Option B**: a fixed Base Anchor + planar build-grid coordinate space + spawn re-root, **plus** one shared storage container ghost (deposit/withdraw via RPC). Locked in [[DR-008_M5_HomeBase_BaseLayer_Storage]].
## Process
- **Scoping research workflow** (4 parallel readers → synthesis): design/genre meaning, DOTS/Netcode subscene+streaming feasibility, codebase scene state, persistence/world-arch → a grounded "home base" definition + 3 scoped options (A/B/C). Operator picked **B**; grid **1.0u × 32²** planar; storage = **(itemId, count)**; persistence = in-session only (no disk); one shared base; manifest reconcile folded in.
- **Design/verify workflow** (3 parallel → I synthesized the plan): extracted exact in-repo templates (RPC `GoInGameRequest`, `StatModifier` ghost buffer, `UpgradePickup` server-spawned interpolated ghost, ring spawn, baked-singleton bakers); **context7 was unreachable** so Netcode 1.13.2 shapes were verified by reflection on the installed assemblies + the project's own proven usages; locked the deterministic grid math.
- **Implementation** (sequential via MCP `create_script`/`apply_text_edits` — single editor, domain-reload-ordered; NOT a parallel swarm): components+math → tests → server systems+spawn re-root → client send → authoring → prefab/subscene wiring → runtime validation.
## Done
- **New (`ProjectM.Simulation/HomeBase`):** `BaseAnchor`; `BaseGridMath` (WorldToCell/CellToWorld/IsCellInPlot/IsPointInPlot/ClampCell/PlotCenter — corner-origin, center-returning, half-open, floor); `StorageEntry` (`[GhostField]` buffer, ownerless); `SharedStorageContainer` tag; `StorageSpawner`; `StorageOpRequest` + `StorageOp` byte consts; `StorageMath` (deposit-merge / withdraw-clamp-drop).
- **New (`ProjectM.Server/HomeBase`):** `SharedStorageSpawnSystem` (one-shot, spawns the container ghost at `CellToWorld(cell)`); `StorageOpReceiveSystem` (server RPC apply via `StorageMath`, singleton container, outside the predicted loop).
- **New (`ProjectM.Client/HomeBase`):** `StorageOpSendSystem` (managed `SystemBase`; E/Q keyboard edge + editor-only `Deposit`/`Withdraw` statics → `StorageOpRequest` RPC to the server connection).
- **New (`ProjectM.Authoring/HomeBase`):** `BaseAnchorAuthoring` (bakes `BaseAnchor` from the GameObject position + plot gizmo); `SharedStorageContainerAuthoring`; `StorageSpawnerAuthoring`.
- **Modified:** `GoInGameServerSystem` — spawn ring re-rooted on `BaseGridMath.PlotCenter(BaseAnchor)` with a `TryGetSingleton` fallback. `Packages/manifest.json` — stale 6.6-era pins reconciled to the resolved 6.4.7 lock.
- **Assets:** `Storage.prefab` (duplicated from `UpgradePickup.prefab` → swapped to `SharedStorageContainerAuthoring`, kept the ownerless-interpolated `GhostAuthoringComponent`). `Gameplay.unity` subscene — added `BaseAnchor` (at (0,1,0)) + `StorageSpawner` (→ Storage.prefab, cell (19,19)) authoring GameObjects.
- **Tests:** `BaseGridMathTests` (7) + `StorageMathTests` (8). **EditMode 62/62.**
## Validation
- **EditMode 62/62 green** (+15 vs M5b's 47).
- **Runtime (single in-editor client, 6.4.7):** `BaseAnchor` baked **identically into both worlds**; player **spawn re-rooted** onto the anchor (spawned at (2.5,1,0)); storage ghost **server-spawned + replicated** to the client at (3.5,1,3.5) = `CellToWorld(19,19)`; **deposit (1×5, 2×3)** then **withdraw (decrement + clamp + drop-empty)****server == client buffer** every time, driven through the real RPC→server→replication path; RPCs survived the tick-batching artifact. Console clean of code/Burst/ghost/RPC errors — only the known unfocused-editor tick-batching warning.
## Decisions
- [[DR-008_M5_HomeBase_BaseLayer_Storage]] — home base = baked ghost-free `BaseAnchor` + locked deterministic `BaseGridMath` grid (M6 builds on it) + spawn re-root + one ownerless-interpolated shared-storage ghost mutated by a server-authoritative RPC (singleton-resolved, byte op, outside the predicted loop). Streaming + disk persistence deferred.
## Diagnosis notes (for future me)
- **`execute_code` runs as a method body** — no `using` directives allowed (they parse as statements → "Identifier expected"); **fully-qualify** all types (`Unity.Entities.World`, `ProjectM.Simulation.BaseAnchor`, …).
- **Ownerless interpolated ghost ≠ owner-predicted ghost for buffer replication:** a server-spawned ownerless chest needs **no `OwnerSendType`/`GhostOwner`**`[GhostField]` alone replicates server mutations to all. `OwnerSendType.All` (per `StatModifier`) is only for the predicting *owner* to recompute.
- **RPC > predicted InputEvent for one-off shared-state actions:** reliable delivery meant deposit/withdraw landed even while the editor tick-batched (the M2 one-shot `Fire` InputEvent drops under batching).
- **The editor hung mid-session** (unresponsive bridge: queued commands accepted, pings unanswered) while unfocused — Edit-mode throttles to near-idle when the window lacks OS focus (`Application.runInBackground` only helps in Play mode). Operator **restarted the editor**; it recovered clean. Avoid piling `refresh_unity` calls onto a blocked main thread; wait or ask to focus/restart.
- **`scope=all force` refresh is heavy** — fine on a fresh editor, but it (plus an unfocused throttle) likely contributed to the hang. Prefer `scope=scripts` for code-only changes.
## Open / deferred
- **Option C** (base/expedition subscene split + async `SceneSystem` streaming) — own world-architecture milestone; M6/M7 don't need it.
- **Disk persistence** — nothing to save until M6 produces structures; thin host-only slice afterward.
- **Storage polish** — proximity gate (container has `HitRadius`), real item/UI model, multi-writer ordering beyond first-come.
- **Multi-client storage** — validate two clients see identical shared state (pairs with the deferred M5b multi-client interpolation + M4 two-build tests).
## Next
Begin **M6 — server-authoritative grid build placement via RPC**, reusing `BaseGridMath` (legality + snap) and the runtime-ghost-into-base-cell spawn path from this slice.
@@ -0,0 +1,45 @@
---
id: DR-008
title: M5 — Home-base base-layer (BaseAnchor + build grid) + shared storage container
status: accepted
date: 2026-06-02
tags:
- decision
- netcode
- home-base
- rpc
- ghost
- m5
permalink: gamevault/07-sessions/decisions/dr-008-m5-homebase-baselayer-storage
---
# DR-008 — M5 Home-base base-layer + shared storage (Option B)
## Context
M5's physics half (predicted physics [[DR-006_M5_Physics_In_Prediction]] + Character Controller [[DR-007_M5b_Character_Controller_Package]]) was done; the remaining half — sketched loosely as "persistent home-base subscene that streams in/out" — needed its meaning pinned down. A read-only research workflow grounded "home base" in the [[Pillars]] (co-op ARPG + V Rising-style shared persistent buildable base + Factorio automation; base vs instanced expeditions) and produced three scoped options: **A** anchor + claimed plot + build-grid config + spawn re-root; **B** = A + one shared-storage ghost; **C** = A + base/expedition subscene split + async streaming. The operator chose **Option B** with: planar `int2` build grid, **CellSize 1.0**, **32×32** plot; storage items as **(itemId, count)** pairs; in-session ("survives logout") persistence only — **no disk save/load**; one **shared** world-owned base. Subscene split + streaming (**C**) explicitly deferred — M6/M7 need only the anchor + grid, not streaming.
## Decision
1. **Home base = a baked, ghost-free config singleton + one runtime shared-storage ghost — NOT a building/save/streaming system.** `BaseAnchor` (`IComponentData`, flat/blittable, no entity refs) carries `AnchorPos`, `GridOrigin` (min-XZ corner of cell (0,0)), `CellSize`, `GridDims`. Baked into the gameplay subscene via `BaseAnchorAuthoring` (`TransformUsageFlags.None`; reads the GameObject position as `AnchorPos`, derives `GridOrigin` centered on it). Present identically in both worlds (baked, not replicated) — the same pattern as `PlayerSpawner`/`AbilityDatabase`/`NetCodePhysicsConfig`.
2. **`BaseGridMath` is the locked, deterministic, unit-tested coordinate space M6 builds on.** Corner-origin, center-returning, **half-open** cell bounds, `math.floor` world→cell (negative-correct). `WorldToCell`/`CellToWorld`/`IsCellInPlot`/`IsPointInPlot`/`ClampCell`/`PlotCenter`. M6's server placement handler will call `IsPointInPlot` (legality) + `CellToWorld` (snap) directly. **CellSize/PlotSize are treated as a locked coordinate space** — changing them after M6 builds invalidates placement.
3. **Spawn is re-rooted onto the anchor.** `GoInGameServerSystem` now uses `BaseGridMath.PlotCenter(BaseAnchor)` as the ring center when the anchor singleton is present, falling back to `PlayerSpawner.SpawnPoint` if the base subscene hasn't streamed in yet (`SystemAPI.TryGetSingleton`). Ring radius/slots stay on `PlayerSpawner`. (Anchor placed at the existing spawn plane (0,1,0) so the re-root is behaviour-preserving.)
4. **Shared storage = an ownerless INTERPOLATED ghost with a replicated `[GhostField]` buffer.** `StorageEntry` (`IBufferElementData`, `[GhostField] ushort ItemId` + `int Count`, `[InternalBufferCapacity(16)]`) on a `SharedStorageContainer`-tagged ghost. The container is **ownerless****no `OwnerSendType`, no `GhostOwner`** (those are only for owner-predicted ghosts like the player's `StatModifier` buffer); server mutations auto-replicate to all clients. The container is **server-spawned at runtime** (one-shot `SharedStorageSpawnSystem` reads a baked `StorageSpawner` + `BaseAnchor`, instantiates the ghost prefab at `CellToWorld(cell)`, destroys the spawner) — NOT baked into the subscene (keeps the subscene ghost-free, dodges the prespawn section-ack handshake). It is **not** added to any connection's `LinkedEntityGroup`, so it survives player disconnects.
5. **Deposit/withdraw = an `IRpcCommand`, applied server-authoritatively outside the predicted loop.** `StorageOpRequest { byte Op; ushort ItemId; int Count }` — a one-off action, so an RPC, not a per-tick predicted input. **Op is a byte** (not an enum) to keep the generated serializer trivial / dodge the cross-assembly enum-Burst hazard; **plain blittable fields, no `[GhostField]`** (RPC payloads auto-serialize). **No target entity is carried** — M5 has a single shared container the server resolves as a **singleton** (entity refs aren't stable cross-world; this avoids the ghost-id+spawn-tick `SpawnedGhostEntityMap` lookup, which also keeps the handler Burst-clean). `StorageOpReceiveSystem` runs in the server `SimulationSystemGroup` (NOT the prediction loop → no rollback double-apply), applies via the pure `StorageMath.Deposit/Withdraw`, and destroys the request. `StorageOpSendSystem` (client, managed `SystemBase`) sends on a keyboard edge (E/Q, fully-qualified Input System to dodge the `PlayerInput` name collision) or an editor-only static (`Deposit`/`Withdraw`) for headless validation.
## Consequences
- **Validated at runtime on 6.4.7 (single in-editor client).** `BaseAnchor` baked identically into **both** worlds (AnchorPos (0,1,0), GridOrigin (-16,1,-16), 1.0u × 32²). Player **spawn re-rooted** onto the anchor → spawned at (2.5,1,0) (PlotCenter + ring slot 0). Storage ghost **server-spawned and replicated to the client** at (3.5,1,3.5) = `CellToWorld(cell 19,19)`. **Deposit** (1×5, 2×3) and **Withdraw** (decrement + clamp-to-available + drop-empty-row) applied server-side and replicated — **server == client buffer** in every case. The RPCs propagated correctly **despite the in-editor tick-batching artifact** (RPCs are reliable, unlike the one-shot `Fire` InputEvent that can drop under batching). EditMode **62/62** (+15: 7 `BaseGridMath`, 8 `StorageMath`). Console clean of code/Burst/ghost/RPC errors — only the known unfocused-editor tick-batching warning.
- **Foundation for M6/M7.** M6 grid placement reuses `BaseGridMath` (legality + snap) and the runtime-ghost-into-base-cell spawn path verbatim; M7 production machines generalise `StorageEntry` into input/output buffers + a server tick. The flat, entity-ref-free `BaseAnchor`/`StorageEntry` shapes are serialization-ready for the deferred disk-persistence slice.
- **No new asmdefs.** Everything fits the existing Simulation/Server/Client/Authoring split; all needed references (`Unity.NetCode`, `Unity.Physics`, `Unity.Transforms`) were already declared. New code lives under `…/HomeBase/` in each assembly.
- **Housekeeping:** stale `manifest.json` pins from the brief 6.6 upgrade (entities/entities.graphics 6.5.0, URP 17.6.0, test-framework 1.8.0, ugui 2.6.0, multiplayer.center 2.0.0) **reconciled to the resolved 6.4.7 lock** (a no-op re-resolve; lock unchanged, console clean) — closes the [[CLAUDE]] pending-reconcile TODO.
## Open / deferred
- **Base/expedition subscene split + async streaming (Option C)** — the persistent-space split the locked world design ultimately needs (`SceneSystem.LoadSceneAsync`/`UnloadScene`, per-world load on the listen-server). Deferred to its own world-architecture milestone; M6/M7 don't depend on it.
- **Disk persistence** — runtime structures don't exist until M6, so there's nothing to save yet. A thin host-only per-structure serialization slice (replayed through M6's placement path) comes after M6.
- **Storage interaction polish** — deposit/withdraw is non-proximity-gated and uses a fixed test item; add an interact-range check (the container carries `HitRadius`) and a real item/UI model later. Multi-writer ordering is currently first-come server apply (fine for the prototype).
- **Plot footprint / base visuals** — only an editor gizmo (plot bounds) + a placeholder primitive container; real ground/walls/structures arrive with M6.
- **Multi-client storage** — validated single-client; a live two-build / Multiplayer-Play-Mode check that two clients see identical shared-storage state pairs with the deferred M5b multi-client interpolation test.
Mirrors the server-authoritative + deterministic + co-op pillars from [[Pillars]]. Builds on [[DR-006_M5_Physics_In_Prediction]] / [[DR-007_M5b_Character_Controller_Package]] (kept infra), [[DR-005_M4_Connection_Model_Direct_IP]] (spawn/co-op), [[DR-004_M3_DataDriven_Abilities_Modifiers]] (the replicated-buffer + byte-enum patterns reused here).