10 KiB
10 KiB
tags, updated, permalink
| tags | updated | permalink | ||
|---|---|---|---|---|
|
2026-06-04 | 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 —float2Move/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.DeltaTimeonly);PlayerInputGatherSystem(client,GhostInputSystemGroup);GoInGameClientSystem(client) /GoInGameServerSystem(server — spawns the owner-predicted ghost, stampsGhostOwner,LinkedEntityGroupauto-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.PlayerInputgainsFire(InputEvent). - Systems:
AbilityFireSystem(predicted;IsFirstTimeFullyPredictingTick-gated predict-spawn; server branch appliesAutoTarget);ProjectileMoveSystem(predicted);ProjectileClassificationSystem(client; predicted-spawn match bySpawnId; non-Burst);ProjectileDamageSystem(server; swept segment-vs-sphere hit);HealthApplyDamageSystem(server; DamageEvent → Health, dummy death-despawn);TrainingDummySpawnSystem(server; one-shot). Input:PlayerInputGatherSystemrewritten as managedSystemBaseover the generatedProjectMInputaction-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.Directionreplicate. 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(singletonBlobAssetReference<AbilityDatabaseBlob>;AbilityDefBlob/CharacterStatsBlobkeyed byAbilityId/CharacterIdbyte) +AbilityPrefabElement(companion entity-ref buffer for projectile prefabs);AbilityRef([GhostField]id) /CharacterStatsRef;StatModifier(replicated[GhostField]buffer,OwnerSendType.All, raw-byteStatTarget/ModOp);EffectiveAbilityStats/EffectiveCharacterStats(derived, not replicated);UpgradePickup/UpgradePickupSpawner.StatMath(pure fold). Removed M2'sAbilityStats/PlayerMoveStats. - Systems:
StatRecomputeSystem(predicted,[UpdateBefore]Aim/Move; folds blob base + modifier buffer →Effective*every tick — rollback-correct);AbilityFireSystemrerouted (effective stats + prefab-by-id + snapshot-at-fire);PlayerMoveSystem→ effective move;UpgradePickupSpawnSystem/UpgradePickupSystem(server; overlap-grant viaAppendToBuffer);DebugModifierInjectionSystem(editor-only, server world);HealthApplyDamageSystemclamps to effective MaxHealth. Authoring:AbilityDefinition/CharacterStatsDefinitionSOs +AbilityDatabaseAuthoringblob 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) +StorageOpconsts;StorageMath(deposit-merge / withdraw-clamp-drop, unit-tested). - Systems:
SharedStorageSpawnSystem(server one-shot — instantiate the container ghost atCellToWorld(cell), destroy spawner);StorageOpReceiveSystem(serverSimulationSystemGroup, NOT predicted — apply the RPC to the singleton container's buffer viaStorageMath);StorageOpSendSystem(client managedSystemBase— E/Q keyboard + editor-onlyDeposit/Withdrawstatics →StorageOpRequestRPC).GoInGameServerSystemre-rooted ontoBaseGridMath.PlotCenter(BaseAnchor)(with aTryGetSingletonfallback). - Netcode shape: base config = baked, ghost-free, identical both worlds (not replicated). Storage container = ownerless interpolated server-spawned ghost; its
StorageEntrybuffer is a[GhostField](noOwnerSendType/GhostOwner) so server mutations replicate to all clients. Deposit/withdraw = server-authoritativeIRpcCommandresolved 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 onBaseGridMath+ 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+ pureEnemyAIMath;Dead(enableable, derived) +RespawnState+ pureRespawnMath;TickUtil.NonZero(the cooldown 0-sentinel guard). - Systems:
EnemyAISystem(server-only, plainSimulationSystemGroup,[UpdateAfter(PredictedSimulationSystemGroup)]— the interpolated Husk ghost seeks the nearest living player, deals contactDamageEvent) + theWaveSystemthreat director (escalating waves of Husk variants Grunt/Swarmer/Brute — replaced the flat sustain; see 2026-06-02_GameFeel_Deepening);HealthApplyDamageSystem+EnemyTagdeath;PlayerDeathStateSystem(both worlds, predicted — derivesDeadfromHealth<=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
LocalTransformreplication;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 ofResourceNode) +ClutterFieldSpawner(optional baked singleton);StructureType.Wall=5/Pylon=6(byte consts; 2–4 stay reserved for M7). - Systems:
ResourceHarvestSystemunified 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);ExpeditionFieldSystemscatters/clears clutter beside the node field (distinct seed);BuildPlaceSystemunchanged (already type-generic) — new genericStructureAuthoring{byte Kind}+ two additiveStructureCatalogrows +BuildSendSystemV/N keys. Client:WorldFeedbackSystem(observe-onlyPresentationSystemGroup— chip/shatter juice, proximity-gated so region-transit stays silent) + live-tunableWorldFeelConfig. - Netcode/world shape: clutter = ownerless interpolated, region-scoped via GhostRelevancy (like nodes); Wall/Pylon = ownerless interpolated structure ghosts (
PlacedStructure.Typebyte-additive → no re-bake); Wall carries aPhysicsCollider(CC-blocking), Pylon cosmetic. Visual:M_Aether_Wild/M_Aether_Orderedpalette materials + a classic-URP Blightfield rock basin inGame.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).
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.