Docs: DR-027 equipment slots (Phase 1) + session log
DR-027 records the architecture (reuse AbilityRef/StatModifier; event-driven server-only equip supersedes DR-026's per-tick sketch), the 7 validated decisions, the inline-mod + reserved-SourceId fixes, and the MaxHealth-gear / session-only notes. Folds the equipment pointer into CLAUDE.md's inventory bullet net-zero (39925 bytes, >1KB headroom) by trimming the persistence/M7/ability-tier bullets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,10 +89,10 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui
|
||||
- **`PlacedStructure{[GhostField] byte Type; int2 Cell (server-only); uint NextTick; uint LastProcessedTick}`** on an ownerless interpolated ghost. **Bake the two tick fields** (turret reuses `NextTick` as fire cooldown; they're the offline-catch-up linchpin). Only `Type` replicates (client derives `Cell` via `BaseGridMath.WorldToCell`). Data-driven `StructureCatalog` buffer. **Occupancy is DERIVED** by scanning live structure ghosts into a Temp `NativeHashSet<int2>`, never a mutable buffer on the baked `BaseAnchor`. See [[DR-014_M6_Build_Structures_Automation_Foundation]].
|
||||
- **Co-op placement atomicity:** commit the `StorageMath.Withdraw` + cell-reservation **in-place inside the RPC foreach** (only `Instantiate` goes through the ECB) so two same-tick requests for one cell can't both pass.
|
||||
- **Buildable turret = hitscan = reversed `EnemyAISystem`:** nearest living Husk in-region within Range, on `NextTick` cooldown append a direct `DamageEvent{Damage, SourceNetworkId=-1}` → reuses `HealthApplyDamageSystem`. No projectile → no tunnelling, no team model.
|
||||
- **Resource-gated ability tiers reuse `StatModifier`** — grow ONE `StatModifier{Target=Damage, Op=PercentAdd, SourceId=<sentinel>}` (replace-by-SourceId → bounded buffer); `StatRecomputeSystem` folds it into `EffectiveAbilityStats` on both worlds. `GoalProgress{[GhostField] int Charge, Target}` rides the global CycleDirector ghost.
|
||||
- **M7 Automation (server-only, never predicted) ★:** `Harvester`/`Conveyor`/`Fabricator` are buildable machines on the `PlacedStructure` ghost storing `PeriodTicks` + **server-only** `MachineInput`/`MachineOutput` buffers (NOT `[GhostField]`). Production runs in the plain server group `[UpdateAfter(PredictedSimulationSystemGroup)]` (Harvester→Conveyor→Fabricator), replicating only via the global ledger + `PlacedStructure`. Deterministic catch-up via `ProductionMath.CyclesDue` (**lower-bound 0, never 1**; period-0 guarded). Byte-only EditMode-tested math; `ConveyorMath` order-independent (`CellKey` stable-sort → at-most-one claim → losers stall). `RuntimePlacedTag` = player-built (save-scan). See [[DR-020_M7_Automation_Production_Chains]].
|
||||
- **Per-player inventory + data-driven items ★:** harvest → the firing player's PERSONAL `InventorySlot` (`[GhostField]` `OwnerSendType.All`, a `StatModifier` twin; owner read via an OPTIONAL `ComponentLookup<GhostOwner>` in `ResourceHarvestSystem`, remainder/un-owned → the ledger). Items = an `ItemDatabase` blob (`AbilityDatabase` twin; `ushort ItemId` subsumes `ResourceId`; ID-keyed; `byte Category`/`Tier`). Deposit personal→ledger via the `G`-key `InventoryDepositRequest` RPC (the build economy spends the ledger). `InventoryMath`(stack/slot-cap) ≠ `StorageMath`. Session-only (no SaveData bump). See [[DR-026_Inventory_Equipment_Progression_Foundation]].
|
||||
- **Disk persistence (`SaveData`, single-slot atomic JSON, versioned/additive) ★:** **born-correct load** (`CycleDirectorSpawnSystem` applies a staged `PendingSave` AT SPAWN so the director never replicates a default first); host-only autosave on Siege→Calm + quit; `BaseRestoreSystem` replays structures **charge-free** with REMAINING-tick cooldowns (one `SaveStructureScan.Collect` path). See [[DR-019_Frontend_Menu_Settings_Saves_Build]].
|
||||
- **Resource-gated ability tiers / buffs reuse `StatModifier`** (replace/clear-by-SourceId → bounded buffer; `StatRecomputeSystem` folds it into `EffectiveAbilityStats` both worlds). `GoalProgress{[GhostField] int Charge, Target}` rides the global CycleDirector ghost.
|
||||
- **M7 Automation (server-only, never predicted) ★:** `Harvester`/`Conveyor`/`Fabricator` are buildable machines on the `PlacedStructure` ghost storing `PeriodTicks` + **server-only** `MachineInput`/`MachineOutput` buffers (NOT `[GhostField]`). Production runs in the plain server group `[UpdateAfter(PredictedSimulationSystemGroup)]` (Harvester→Conveyor→Fabricator), replicating only via the global ledger + `PlacedStructure`. Deterministic catch-up via `ProductionMath.CyclesDue` (**lower-bound 0, never 1**; period-0 guarded). `ConveyorMath` order-independent (`CellKey` stable-sort); `RuntimePlacedTag` = player-built. See [[DR-020_M7_Automation_Production_Chains]].
|
||||
- **Per-player inventory + equipment + items ★:** harvest → the firing player's PERSONAL `InventorySlot` (`[GhostField]` `OwnerSendType.All`; owner via OPTIONAL `ComponentLookup<GhostOwner>` in `ResourceHarvestSystem`, remainder/un-owned → ledger); deposit personal→ledger via the `G`-key `InventoryDepositRequest` RPC. Items = an `ItemDatabase` blob (`AbilityDatabase` twin; `ushort ItemId` subsumes `ResourceId`; ID-keyed; item mods **INLINE on `ItemDefBlob` (Mod0..3), NOT a nested BlobArray** — by-value `TryGetItem` reads a nested one empty). Equip = `EquipmentSlot{[GhostField] ushort ItemId}` (index=slot) + server-only event-driven `EquipSystem`: weapon→`AbilityRef.Id` (swaps prefab+base stats), gear→`StatModifier`s tagged `Tuning.EquipSourceIdBase+slot`, stripped via target-agnostic `RemoveBySourceId`; atomic swap. Session-only. See [[DR-026_Inventory_Equipment_Progression_Foundation]] · [[DR-027_Equipment_Slots_Phase1]].
|
||||
- **Disk persistence (`SaveData`, single-slot atomic JSON, versioned/additive) ★:** **born-correct load** (`CycleDirectorSpawnSystem` stages `PendingSave` AT SPAWN); host-only autosave; `BaseRestoreSystem` replays structures charge-free with REMAINING-tick cooldowns. See [[DR-019_Frontend_Menu_Settings_Saves_Build]].
|
||||
|
||||
### Presentation / juice / VFX
|
||||
- **All juice/HUD = client-only managed `SystemBase` in `PresentationSystemGroup`** (once/frame, no rollback double-fire) that OBSERVES replicated state, never mutates the sim. Read ECS via `SystemAPI.Query` + `EntityManager.CompleteDependencyBeforeRO<T>()` — NOT a MonoBehaviour `LateUpdate` (job-safety throw). `Entity` is a stable client dict key for a ghost's lifetime — **prune the cache each frame** (a pruned enemy = a kill → death VFX); **never `DestroyEntity` a ghost from the client** (`GhostDespawnSystem` owns despawn). Hit-stop = a camera punch, **never `Time.timeScale`** (corrupts the sim).
|
||||
|
||||
Reference in New Issue
Block a user