Files
Project-M/Docs/Vault/02_Game_Design/Systems_Index.md
T
kronic a7405c3f38 Docs: reconcile vault roadmap + indexes to current state
Bring the living docs up to 2026-06-08 reality (Milestones/Backlog/Home/Systems_Index were stalled at 2026-06-06): add the 6 missing Milestones rows (animation, enemy animation, HUD Synty skin, world redo, world collision, Inventory+Equipment Phase 0/1); declutter Backlog to open-work-only (remove shipped [x] items whose context lives in their DRs/session logs, resolve items completed by later slices, surface Inventory/Equipment Phase 2-4 as next); refresh Home links (DR range -> DR-027, latest sessions); add a Systems_Index Items/Inventory/Equipment section + fix the stale 'StructureType 2-4 reserved for M7' note + a pointer to systems documented only in their DRs.

Reconcile DR-026's roadmap (mark Phase 0/1 done; the Phase-1 line now carries the DR-027 event-driven supersession note). Trim Data_Driven_Abilities resolved open-questions + fix cross-links. DRs + session logs (the historical record) untouched apart from that one DR-026 note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:33:46 -07:00

17 KiB
Raw Blame History

tags, updated, permalink
tags updated permalink
design
index
2026-06-08 gamevault/02-game-design/systems-index

Systems Design — Index

One design doc per gameplay system, linked here. Each should state: purpose, components (IComponentData), systems (ISystem), netcode shape (ghost? predicted vs interpolated? inputs / RPCs), and open questions.

Systems

M1 — Player (twin-stick predicted movement) · 2026-05-30_M1_Player_Slice

  • Components (ProjectM.Simulation): PlayerTag; PlayerInput (IInputComponentData — float2 Move/Aim, [GhostField], flows via AutoCommandTarget); PlayerMoveStats (baked tunables); PlayerFacing ([GhostField] Direction); PlayerSpawner (baked prefab singleton); GoInGameRequest (IRpcCommand).
  • Systems: PlayerMoveSystem, PlayerAimSystem (PredictedSimulationSystemGroup, .WithAll<Simulate>(), deterministic — SystemAPI.Time.DeltaTime only); PlayerInputGatherSystem (client, GhostInputSystemGroup); GoInGameClientSystem (client) / GoInGameServerSystem (server — spawns the owner-predicted ghost, stamps GhostOwner, LinkedEntityGroup auto-despawn).
  • Netcode shape: player = owner-predicted ghost; client sends input only; server is authoritative. Status: code-complete + EditMode-verified; live runtime blocked by DR-002_Unity66_Alpha_Netcode_Transport.

M2 — Combat (predicted projectile, server damage) · 2026-05-31_M2_Combat

  • Components (ProjectM.Simulation): Health ([GhostField] Current; baked Max); HitRadius; DamageEvent (IBufferElementData); AbilityStats (auto-target range/cone, cooldown ticks — baked); AbilityCooldown ([GhostField] NextFireTick); Projectile ([GhostField] Direction + SpawnId; baked Speed/Damage/Range); ProjectileSpawner / TrainingDummySpawner (baked singletons); TrainingDummyTag. PlayerInput gains Fire (InputEvent).
  • Systems: AbilityFireSystem (predicted; IsFirstTimeFullyPredictingTick-gated predict-spawn; server branch applies AutoTarget); ProjectileMoveSystem (predicted); ProjectileClassificationSystem (client; predicted-spawn match by SpawnId; non-Burst); ProjectileDamageSystem (server; swept segment-vs-sphere hit); HealthApplyDamageSystem (server; DamageEvent → Health, dummy death-despawn); TrainingDummySpawnSystem (server; one-shot). Input: PlayerInputGatherSystem rewritten as managed SystemBase over the generated ProjectMInput action-map wrapper.
  • Netcode shape: projectile = owner-predicted ghost, client predict-spawns + classifies against server truth by SpawnId=(ownerNetId<<16)|absoluteFireCount; auto-target & damage server-authoritative; Health.Current/Projectile.Direction replicate. Status: foundation built + runtime-validated (server loop + replication); live keypress-fire pending an interactive test. Decisions: DR-003_M2_Combat_Netcode_Architecture.

M3 — Data-driven abilities & modifiers · 2026-05-31_M3_Data_Driven_Abilities

  • Components (ProjectM.Simulation): AbilityDatabase (singleton BlobAssetReference<AbilityDatabaseBlob>; AbilityDefBlob/CharacterStatsBlob keyed by AbilityId/CharacterId byte) + AbilityPrefabElement (companion entity-ref buffer for projectile prefabs); AbilityRef ([GhostField] id) / CharacterStatsRef; StatModifier (replicated [GhostField] buffer, OwnerSendType.All, raw-byte StatTarget/ModOp); EffectiveAbilityStats / EffectiveCharacterStats (derived, not replicated); UpgradePickup / UpgradePickupSpawner. StatMath (pure fold). Removed M2's AbilityStats / PlayerMoveStats.
  • 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.

M5.5 — Game feel & identity ("First Blood") · 2026-06-02_GameFeel_Identity

  • Components (ProjectM.Simulation): EnemyTag/EnemyStats/EnemyAttackCooldown/EnemySpawner + pure EnemyAIMath; Dead (enableable, derived) + RespawnState + pure RespawnMath; TickUtil.NonZero (the cooldown 0-sentinel guard).
  • Systems: EnemyAISystem (server-only, plain SimulationSystemGroup, [UpdateAfter(PredictedSimulationSystemGroup)] — the interpolated Husk ghost seeks the nearest living player, deals contact DamageEvent) + the WaveSystem threat director (escalating waves of Husk variants Grunt/Swarmer/Brute — replaced the flat sustain; see 2026-06-02_GameFeel_Deepening); HealthApplyDamageSystem +EnemyTag death; PlayerDeathStateSystem (both worlds, predicted — derives Dead from Health<=0, gates movement/aim/fire via .WithDisabled<Dead>()); PlayerRespawnSystem (server-only — schedule + refill + reposition). Client presentation (managed, PresentationSystemGroup): CombatFeedbackSystem (damage numbers / VFX / procedural SFX / camera shake by edge-detecting replicated Health) + HudSystem (code-built uGUI health / cooldown / threat / DOWNED) + PrototypeCameraRig.AddShake.
  • Netcode shape: Husk = ownerless interpolated server-driven ghost (stock LocalTransform replication; Health [GhostField]); Dead = local derived enableable (NOT replicated — pure function of replicated Health); juice/HUD observe replicated state only (client world, never the sim). Identity: Identity (sci-fi frontier colony). Status: built + runtime-validated (Husks spawn(6)/replicate/chase/strike; death→respawn loop; HUD; emissive dark-sci-fi look); EditMode 74/74. Decisions: DR-009_GameFeel_Identity_FirstBlood.

Post-M8 — World-space cohesion pass (clearing + buildable Wall/Pylon + Blightfield dressing) · 2026-06-04_World_Space_Cohesion_Pass

  • Components (ProjectM.Simulation): BlightClutter (ownerless-interpolated ghost — [GhostField] Remaining+Variant, server-only ScrapResourceId/ScrapPerHit; RegionTag{Expedition} sibling of ResourceNode) + ClutterFieldSpawner (optional baked singleton); StructureType.Wall=5/Pylon=6 (byte consts; 24 are now in use by the M7 production machines — Harvester/Conveyor/Fabricator).
  • Systems: ResourceHarvestSystem unified to sweep nodes AND clutter in one best-target loop (required — two separate sweeps would double-destroy an overlapping projectile at ECB playback; math.max(1,(int)yield) guards the immortal-sink); ExpeditionFieldSystem scatters/clears clutter beside the node field (distinct seed); BuildPlaceSystem unchanged (already type-generic) — new generic StructureAuthoring{byte Kind} + two additive StructureCatalog rows + BuildSendSystem V/N keys. Client: WorldFeedbackSystem (observe-only PresentationSystemGroup — chip/shatter juice, proximity-gated so region-transit stays silent) + live-tunable WorldFeelConfig.
  • Netcode/world shape: clutter = ownerless interpolated, region-scoped via GhostRelevancy (like nodes); Wall/Pylon = ownerless interpolated structure ghosts (PlacedStructure.Type byte-additive → no re-bake); Wall carries a PhysicsCollider (CC-blocking), Pylon cosmetic. Visual: M_Aether_Wild/M_Aether_Ordered palette materials + a classic-URP Blightfield rock basin in Game.unity (relief via props, no terrain). Status: built + validated (EditMode 142/142; in-editor Play introspected via execute_code; 4-lens adversarial review → 4 findings fixed). Decisions: DR-018_World_Space_Cohesion_Pass (builds on the M6 region split / build pipeline + M8 persistent base — those systems are documented in their DRs).

M7 — Automation: self-running production chains (Harvester → Conveyor → Fabricator) · 2026-06-05_M7_Automation

  • Components (ProjectM.Simulation/Automation): Harvester{byte ResourceId;int Yield;int PeriodTicks} (fixed-yield generator), Fabricator{byte In/OutResourceId;int In/OutAmount;int PeriodTicks} (recipe → global ledger), Conveyor{byte Direction;int PeriodTicks}; server-only buffers MachineInput/MachineOutput (DISTINCT types, NO [GhostField]); ConveyorItem (enableable, baked DISABLED); RuntimePlacedTag (player-built = persistable, single source of truth). Pure math: ProductionMath (NeedsInit/CyclesDue/RemainingTicks/RestoreNextTick), ConveyorMath (DirOffset/CellKey/ResolveMoves — deterministic), MachineSlotMath (byte-id deposit/withdraw, the non-replicated twin of StorageMath).
  • Systems (ProjectM.Server/Automation, server-only, plain SimulationSystemGroup ordered chain [UpdateAfter(PredictedSimulationSystemGroup)] → Harvester → Conveyor → Fabricator): HarvesterProductionSystem (→ own MachineOutput), ConveyorTransportSystem (pull adjacent upstream output → ResolveMoves → settle ConveyorItem/deposit sink MachineInput), FabricatorProductionSystem (input-limited recipe → global ledger). Persistence: SaveStructureScan (shared autosave/quit scan of RuntimePlacedTag structures), one-shot BaseRestoreSystem (charge-free replay), SaveData v2 (StructureSave[] + flat StructureIoRow[], cooldown as epoch-independent REMAINING ticks). Build: BuildPlaceSystem stamps conveyor Direction+RuntimePlacedTag; BuildSendSystem H/F/C keys + [/] rotate; BuildPlaceRequest +byte Direction. Authoring: 3 MonoBehaviours + bakers; Harvester/Fabricator/Conveyor.prefab (dup Turret.prefab) in the catalog.
  • Netcode shape: production is SERVER-ONLY (no prediction/rollback); results replicate ONLY via the global StorageEntry ledger ([GhostField], untagged CycleDirector ghost) + PlacedStructure.Type; per-machine I/O buffers never hit the wire. "Offline catch-up" = within-session tick math + a preserved stockpile across quit (NO wall-clock minting). Status: built + runtime-validated (real netcode worlds, server==client; quit→Continue restore; EditMode 190/190). Decisions: DR-020_M7_Automation_Production_Chains (builds on DR-014_M6_Build_Structures_Automation_Foundation).

Presentation — UI Toolkit HUD + build-palette HUD · 2026-06-05_HUD_Rework_UITK_BuildPalette

  • Client (presentation/UI): HudSystem rewritten on UI Toolkit (client PresentationSystemGroup, observe-only) — a runtime UIDocument (shared RuntimePanelSettings, sortingOrder 50 behind the pause overlay; root pickingMode=Ignore) in the Aether-cyan MenuUi/HudUi palette: health+cooldown bars, HUSKS, phase/cycle/countdown + resources + location + goal, DOWNED overlay, and a build-palette bar lazy-built from the client StructureCatalog (cost + affordability + selection highlight). BuildPaletteState (client-local), BuildPreviewMath (pure validity, unit-tested), BuildSendSystem (cursor→cell ground ghost green/red, left-click place, right-click/Esc cancel, [/]/R rotate; self-contained guards), PlayerInputGatherSystem (fire suppressed in build mode), PauseMenuController.Open (pause guard). CombatFeedbackSystem damage numbers re-skinned to the Aether/Blight palette.
  • Netcode shape: none new — UI observes replicated state (ledger/cycle/goal/player ghost) + the client-baked catalog; placement reuses the M7 BuildPlaceRequest RPC (server-authoritative). Status: built + runtime-validated (UITK render + palette + selection + ground ghost + placement; EditMode 194/194). Decisions: DR-021_HUD_UITK_BuildPalette (builds on DR-019_Frontend_Menu_Settings_Saves_Build UITK frontend + DR-020_M7_Automation_Production_Chains structures).

Items · Inventory · Equipment — Phase 0 (backbone) + Phase 1 (slots) · 2026-06-08_Inventory_Equipment_Progression_Phase0 · 2026-06-08_Equipment_Slots_Phase1

  • Components (ProjectM.Simulation/Items): ItemDatabase (singleton BlobAssetReference<ItemDatabaseBlob>; ItemDefBlob{ushort ItemId; byte Category; byte Tier; int StackMax; FixedString64Bytes Name; byte EquipSlot; byte GrantedAbilityId; 4× INLINE ItemModSpec} — ID-keyed TryGetItem; inline mods, NOT a nested BlobArray since TryGetItem returns by value; zero-code content adds); InventorySlot ([GhostField] buffer, OwnerSendType.All — per-player bag) + pure InventoryMath (deposit/withdraw/stack-cap/CanDeposit); EquipmentSlot ([GhostField] buffer, index = slot — Weapon=0/Armor=1/Trinket=2/Tool=3) + EquipSlotId; DefaultAbility (baked, non-replicated, unarmed fallback); InventoryDepositRequest/EquipRequest/UnequipRequest (IRpcCommands). Equip stat-mods tagged Tuning.EquipSourceIdBase+slot.
  • Systems: server (ProjectM.Server/Economy, plain SimulationSystemGroup, NOT predicted) — ResourceHarvestSystem rerouted to deposit harvest into the owner's bag (owner via optional ComponentLookup<GhostOwner>; remainder/un-owned → global ledger); InventoryDepositSystem (bag → ledger via the G RPC); EquipSystem (event-driven equip/unequip: move item bag↔slot ATOMICALLY — no item loss; weapon→AbilityRef.Id, gear→StatModifiers stripped target-agnostically via RemoveBySourceId; DefaultAbility on weapon-unequip). Client: InventoryDepositSendSystem (G) / EquipSendSystem (keys 1-9 / U + build-safe static hooks); HudSystem inventory + equipment panels (observe-only, click-to-equip). Authoring: ItemDefinition SO + ItemDatabaseAuthoring baker.
  • Netcode shape: InventorySlot/EquipmentSlot/StatModifier = [GhostField] OwnerSendType.All buffers on the owner-predicted player → replicate to the owner; the server is the sole writer (event-driven, plain group → no rollback double-apply). Equip effects fold through the predicted StatRecomputeSystem + the already-replicated AbilityRef, so the swap is prediction-correct with no client equip-prediction. Catalog = baked config (identical both worlds, NOT replicated). Session-only (no save persistence yet — Phase 3). Status: built + Play-validated (236/236 EditMode). Decisions: DR-026_Inventory_Equipment_Progression_Foundation (Phase 0), DR-027_Equipment_Slots_Phase1 (Phase 1). Forward roadmap (Phase 2 tool-gated harvesting → Phase 4) in Backlog.

Systems documented in their DRs/session logs but not yet given a full section here: aim controls (DR-012_Aim_Controls_Cursor_Gamepad), persistent base + player-driven pacing (DR-017_Persistent_Base_Player_Driven_Pacing), frontend menu/settings/saves (DR-019_Frontend_Menu_Settings_Saves_Build), animation pipeline (DR-022_Animation_Pipeline_Rukhanka_Synty / DR-023_Enemy_Animation_MonsterMash), HUD Synty skin (DR-024_HUD_Synty_Skin_Theme), world environment + collision (DR-025_World_Environment_Redo_Natural_Frontier / 2026-06-08_World_Collision_HUD_Scaling).

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.