--- date: 2026-06-02 type: session tags: [session, dots, netcode, game-feel, identity, enemy-ai, presentation, hud, urp, m5] permalink: gamevault/07-sessions/2026/2026-06-02-gamefeel-identity --- # Session 2026-06-02 — "First Blood": game-feel & identity slice ## Goal Before more systems work, **make the project feel like a game — in feel, look, and play — and give it an identity.** Diagnosis: M1–M5 are systems-complete but identity-less — pillars are mechanical (no fiction), and there is **no feedback layer** (grep confirmed zero audio/particles/VFX/shake/UI), the only enemy is an inert dummy, and there's no death/respawn or HUD. Operator chose: identity = **sci-fi frontier colony**, first slice = the **full game-feel pass** ("First Blood"), proceed plan→approve→build. Locked in [[DR-009_GameFeel_Identity_FirstBlood]]; fiction in [[Identity]]. ## Process - **Explore + propose** (read-only): diagnosed the look/feel/play gap against [[Pillars]] + the M1–M5 vault, surfaced the "mechanics without a fiction or a feedback layer" core, and proposed a spine (Identity) + 3 axes (Feel/Look/Play) with a recommended first slice. Operator picked the fiction + full slice via a 3-question gate. - **context7 WAS reachable this session** (vs [[DR-008_M5_HomeBase_BaseLayer_Storage]]). A **4-agent ctx7 verification workflow** (Netcode / Entities-enableable / URP / DOTS→GameObject presentation) returned high-confidence verified API shapes + a key correction: **an interpolated enemy ghost must be moved server-only in the plain `SimulationSystemGroup`, NOT in the predicted loop**, and `Dead` is cleanest **derived** from replicated Health (no replicated enabled bit). - **Implementation** (sequential via MCP `create_script`/`script_apply_edits` — single editor, domain-reload-ordered; NOT a parallel swarm) in 5 stages, each compiled + console-checked + (where applicable) EditMode-tested + runtime-validated before the next. - **Adversarial review workflow** over the diff (12 agents: 4 dimensions → per-finding adversarial verification). It **verified the architecture correct** — the Husk is a properly interpolated/ownerless ghost (`HasOwner=0`, `DefaultGhostMode=Interpolated`), the derived `Dead` is rollback-safe (no GhostField), the local player's Health is never client-predicted, and presentation randomness is outside the sim — every netcode-desync suspicion came back **not-a-bug**. **3 confirmed low-severity findings**, all the same root: the `uint` cooldown "0 = ready" sentinel can collide at tick wraparound (`RespawnMath` already guarded it; the three cooldown writers + the HUD didn't). **Fixed** via a shared `TickUtil.NonZero` guard at the three writes (`EnemyAISystem`/`EnemySpawnSystem`/`AbilityFireSystem`) and a wrap-safe `NetworkTick` compare in the HUD cooldown bar. ## Done **Stage 1 — Husk enemy** (`ProjectM.Simulation/Combat`, `ProjectM.Server/Combat`, `ProjectM.Authoring/Combat`): `EnemyTag`/`EnemyStats`/`EnemyAttackCooldown`/`EnemySpawner` + pure `EnemyAIMath` (SeekVelocity/InAttackRange/RingPosition); `EnemyAISystem` (server-only, plain `SimulationSystemGroup` `[UpdateBefore(PredictedSimulationSystemGroup)]` — seeks nearest living player, writes `LocalTransform`, appends `DamageEvent` on a `NetworkTick`-gated strike); `EnemySpawnSystem` (persistent server spawner, sustains `MaxAlive` on a deterministic ring around `BaseAnchor`); `HealthApplyDamageSystem` +`EnemyTag` death; `EnemyAuthoring` + `EnemySpawnerAuthoring`. `Enemy.prefab` duplicated from `UpgradePickup.prefab` (interpolated ownerless ghost) → `EnemyAuthoring`; `EnemySpawner` GameObject added to `Gameplay.unity`. **Stage 2 — Player death/respawn** (`ProjectM.Simulation/Player`, `ProjectM.Server/Combat`): `Dead` (enableable, **derived** from Health) + `RespawnState` + pure `RespawnMath`; `PlayerDeathStateSystem` (both worlds, predicted, before movement/aim/fire — sets `Dead` + zeroes velocity); `PlayerRespawnSystem` (server-only, after the predicted group — schedule on death, refill+reposition on due). `PlayerControlSystem`/`PlayerAimSystem`/`AbilityFireSystem` gained `.WithDisabled()`; `PlayerAuthoring` bakes `Dead` (disabled) + `RespawnState`. **Stage 3 — Combat juice** (`ProjectM.Client/Presentation`): `CombatFeedbackSystem` (client-only managed `SystemBase`, `PresentationSystemGroup`) — edge-detects replicated `Health` → pooled billboard `TextMesh` damage numbers + runtime `ParticleSystem` hit/death/muzzle bursts + **procedural** `AudioClip` SFX + camera shake; Husk death via despawn-prune; local-fire muzzle via `AbilityCooldown` edge. `PrototypeCameraRig` +`AddShake` (presentation-only decayed shake). **Stage 4 — HUD** (`ProjectM.Client/Presentation`): `HudSystem` (code-built uGUI overlay — `RawImage` health + cooldown bars over `Texture2D.whiteTexture`, legacy `Text` threat count + DOWNED/RESPAWNING overlay) driven from the local player ghost. `ProjectM.Client.asmdef` +`UnityEngine.UI`. **Stage 5 — Look** (assets/editor): URP global `Volume` (Bloom/ACES Tonemapping/ColorAdjustments/Vignette) + dark flat ambient + exp fog + cool key light + URP HDR color grading; emissive re-tint (teal crew, cyan projectiles, orange Husks via new `M_Husk`, dark-metal walls/ground). Husk balance tuned (dmg 8→5, strike 36→48 ticks, speed 3.5→3.0). **Docs:** [[Identity]] (the fiction) + linked from [[Pillars]]/Home; [[DR-009_GameFeel_Identity_FirstBlood]]. ## Validation - **EditMode 74/74 green** (+12 vs M5's 62 — 7 `EnemyAIMath`, 5 `RespawnMath`). Console clean of code/Burst/ghost errors after every stage. - **Runtime (single in-editor client, 6.4.7):** Husks **spawn (6) + replicate server↔client (6==6)**, **chase** from the ring to the player, **deal contact damage** (player → 0 HP). **Death→respawn loop verified** by polling: `Health=0`/`Dead=True`/`RespawnTick` scheduled → recovered at base full-HP/`Dead=False`. **HUD renders** (health bar, "HUSKS N", DOWNED overlay). **Look confirmed** via screenshots: dark metal world + cyan crew + orange Husks + bloom (after fixing the `M_Dummy`-as-wall-material orange-walls bug). - **Adversarial review (12 agents):** architecture verified correct; 3 confirmed low-severity findings (the `uint` cooldown 0-sentinel wraparound), all fixed (`TickUtil.NonZero` + HUD `NetworkTick` compare). **EditMode 74/74 after fixes**, console clean. ## Diagnosis notes (for future me) - **Interpolated ghost ≠ predicted.** Move an ownerless interpolated enemy ghost **server-only in plain `SimulationSystemGroup`** (`[WorldSystemFilter(ServerSimulation)]`), NOT in `PredictedSimulationSystemGroup`. `[UpdateAfter(PredictedSimulationSystemGroup)]` (NOT `[UpdateBefore]` — the predicted group is OrderFirst, so UpdateBefore is silently ignored; corrected in the deepening pass) so a contact `DamageEvent` drains the following tick (~16ms, fine for melee). Stock `LocalTransform` replication carries its position — no hand-written `[GhostField]`. - **Derive enableable state, don't replicate it, when it's a pure function of already-replicated data.** `Dead = Health<=0` derived every predicted tick in both worlds is rollback-correct and dodges the `[GhostEnabledBit]` surface — same idiom as `EffectiveCharacterStats`. To toggle a disabled enableable component you must still visit the entity: `.WithPresent()` (write) vs `.WithDisabled()` (alive-only run). `Simulate` (also enableable) ANDs independently — keep `.WithAll()`. - **A Baker can bake an enableable component DISABLED** (`AddComponent(e); SetComponentEnabled(e, false);`) → instances spawn alive with zero first-tick work (instantiated entities inherit the prefab's enabled state). - **Presentation belongs in a client-only managed `SystemBase` in `PresentationSystemGroup`** (once per frame, no rollback double-fire). Read ECS state via `SystemAPI.Query` INSIDE `OnUpdate` (registers job deps) + `EntityManager.CompleteDependencyBeforeRO()` — NOT from a MonoBehaviour `LateUpdate` (the job-safety exception the camera rig hit). `Entity` is a stable client dict key for a ghost's lifetime; **prune the cache each frame** (a pruned enemy = a kill → death VFX at its last position; never `DestroyEntity` a ghost from the client — `GhostDespawnSystem` owns that). - **Netcode-safe "hit-stop" = a camera punch, never `Time.timeScale`** (which would corrupt the deterministic sim). - **Code-built uGUI with no assets:** `RawImage` over `Texture2D.whiteTexture` (anchor-driven fill, no sprite) + legacy `Text` with `Resources.GetBuiltinResource("LegacyRuntime.ttf")`. Procedural `AudioClip.Create` + runtime `ParticleSystem` (Sprites/Default material, HDR start color to bloom) keep the slice asset-free. - **Watch shared-material bleed when re-tinting:** the walls glowed orange because `M_Dummy` doubled as the wall material — gave Husks their own `M_Husk` instead. Editing a prefab asset's component in code needs `PrefabUtility.LoadPrefabContents` → modify → **`SaveAsPrefabAsset`** (NOT `SavePrefabAsset`, which rejects the contents root) → `UnloadPrefabContents`. - **ACES tonemapping needs the URP asset's color grading mode = HDR** (`m_ColorGradingMode = 1`), else HDR bloom/emission won't tonemap right. - **Editor `is_focused` matters again:** play-enter domain reload + boot took ~10–15s while unfocused; entity inspection via `execute_code` had to be retried until worlds booted. (`Application.runInBackground` keeps Play ticking but Edit mode still throttles unfocused.) ## Open / deferred - Generalize `AbilityFireSystem` auto-target from `TrainingDummyTag` to `EnemyTag` (Husks are hittable via raw-aim swept hit; only the aim-assist snap is missing). - Respawn safety (brief invuln / Husk push-back) so respawn isn't instant re-death. - Real assets (audio, stylized crew/structure models, biomech Husks); SSAO renderer feature. - Enemy variety + a wave/threat director (ranged Husk, brute, broodmaker). - Multi-client validation of remote-player death VFX + per-client HUD. ## Next Either **deepen the loop** (enemy variety + wave director + respawn safety + auto-target on Husks) to make the combat sustain, or resume the milestone track at **M6 — server-authoritative grid build placement** ([[DR-008_M5_HomeBase_BaseLayer_Storage]]). The presentation + server-AI patterns from this slice are the reusable foundation for the former.