Init Homebase
This commit is contained in:
@@ -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).
|
||||
Reference in New Issue
Block a user