Docs: DR-038 Slice 1 record + session log

DR-038 records Slice 1 (combat readability + HUD declutter) with the two reusable
netcode patterns: bake-client-safe for client-needed/server-owned/never-changes
data (EnemyTelegraph), and a [GhostEnabledBit] derived once/tick (IsLunging). Open
item: the operator visual fun-gate. Session log captures the design redirect +
Slice 1 build/validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 12:48:16 -07:00
parent f3eccec524
commit f98125f0b2
2 changed files with 79 additions and 0 deletions
@@ -0,0 +1,38 @@
---
date: 2026-06-17
type: session
tags: [session, design, direction, roguelite, expedition, classes, research]
permalink: gamevault/07-sessions/2026/2026-06-17-design-redirect-coop-roguelite
---
# Session 2026-06-17 — Design redirect: co-op roguelite (Expedition spine + classes + persistent meta)
## Goal
Evaluate the operator's playtest [[Scratch Notes 6152026]] (2026-06-15, playing the shipped single-arena slice), front-load full research, and present research-backed paths forward for a game-design edit. `/dots-dev` design pass under ultracode.
## Done
- **8-agent research + feasibility workflow** (2 code-survey + 6 design/feasibility): mapped the actual codebase (combat/ability/enemy + world/region/save/HUD) and researched Shape of Dreams, hub/meta reconciliation (DRG/Hades/Gunfire/Cult of the Lamb/Riftbreaker/Remnant 2), class-kit + augment design, combat readability (health bars + telegraphs), procedural-zone content strategy, and **DOTS/Netcode feasibility** (context7-verified). Full output in the run transcript; durable synthesis folded into [[DR-037_Procedural_Expedition_Spine_Two_Classes_Persistent_Meta]].
- **Key findings:** (1) feasibility GREEN — *server-spawns-ghosts ⇒ no seed replication, no cross-client determinism problem*; `ExpeditionFieldSystem` is already a working epoch-seeded proc-gen seeder (~70 % of the chassis). (2) Reconciliation = **Mission-as-Sortie** (base = permanent hub, expedition = re-seeded run venue, run-buffs expire) — satisfies the locked "never a run-reset" pillar. (3) Stale root cause = no procedural-venue / persistent-growth / thematic variety (project had none); encounter variety > structural > cosmetic. (4) Classes: melee-primary is most skill-expressive; `AbilityRef.Id` = class anchor; `StatModifier`/`TimedModifier` IS the meta-buff mechanism. (5) Readability: on-damage-sticky bars (not always-on) + per-enemy telegraph ramp; `Health.Current` already replicated. (6) Proc-gen = authored-arena **pool sampling**, NOT WFC.
- **Presented paths + ran the fork-locking ritual.** Operator locked: **re-scope the deadline** (build the bigger game; *"don't estimate time, I handle deadlines"*); **two classes** (Warrior melee + Ranger ranged); **expedition = required spine + persistent meta**; **all four do-now readability/HUD wins** (incl. the Charger lunge re-bake).
- **Wrote the game-design edit:** new [[DR-037_Procedural_Expedition_Spine_Two_Classes_Persistent_Meta]] (accepted); updated [[Pillars]] (locked the expedition-spine + 2-class + persistent-meta decisions), [[Path_to_Fun]] (direction-evolved banner), [[Backlog]] (committed Slices 14; SL-1…7 marked superseded), [[Identity]] (light fiction note), and demoted [[End_Of_Month_Game_Jam_Slice]] to SUPERSEDED (archived, kept as a fallback/showcase).
## Decisions
- [[DR-037_Procedural_Expedition_Spine_Two_Classes_Persistent_Meta]] — the redirect (accepted). Supersedes [[DR-035_End_Of_Month_Slice_Adoption]]'s "ship the minimum" Decision-Gate answer; reverses [[DR-031_Base_Mining_Loop_Cohesion]]'s expedition pause; fulfils pillar-#4.
## Open / deferred
- **Slice 1 (Combat Readability & HUD Declutter)** is the next build — mostly client-side (`CombatFeedbackSystem`/`HudSystem`/`BuildPaletteState`), one small enemy-ghost re-bake (Charger lunge tell). No netcode design review needed except the re-bake.
- **Slices 24** (classes / expedition spine / persistent meta) each **run the adversarial netcode/determinism design review BEFORE coding** ([[validate-netcode-design-before-coding]]).
- **Netcode correctness items to confirm in those reviews:** `EnemyAISystem` region-filter; `ThreatDirector` mid-siege-return gate; `RegionRelevancySystem` per-tick cost (cap ghosts, cosmetic props non-ghost); mass-spawn sync-point amortisation; new `StatTarget` bytes byte-compared (Burst ICE); avoid new `PlayerInput` events (re-bake); persist-seed-regenerate-layout determinism.
- **Per-slice forks** to lock at each review: expedition depth (one zone vs sequential+boss); shared vs per-party expedition instances; zone-clear reward shape; class-select moment; meta unlock surface.
## Slice 1 — Combat Readability & HUD Declutter (BUILT + engineering-validated, same session)
Built all four do-now wins ([[DR-038_Slice1_Combat_Readability_HUD_Declutter]]) via a grounding+design workflow (4 read-only agents → exact edit sites + the two resolved design points) then serial Unity-MCP edits across 12 files + a test file. **Validation:** clean compile (0 err/warn incl. the Burst-affecting `EnemyAISystem` query + the `[GhostEnabledBit]` source-gen); **345/345 EditMode** (342 baseline + 3 new `IsLunging` derive tests); **Play-validated** — the ghost-hash change did NOT break the handshake (server+client `conns=1`), `EnemyTelegraph` baked on all 4 enemy prefabs, `IsLunging` baked-DISABLED on the Charger + replicated to the client, zero runtime errors. Resolved design points: `EnemyTelegraph` = a **baked client-safe** component (TuningConfig is server-only) so the client sizes the danger ramp; `IsLunging` = a single-bit `[GhostEnabledBit]` **derived once/tick** from `LungeState.UntilTick` (the Dead idiom), direction derived from the replicated rotation. **Open:** the operator visual fun-gate (bar look, cone-persists-through-lunge, toggle feel) + the commit.
## Next
Operator **visual fun-gate** on Slice 1 (eyes-on: health bars, Charger lunge cone, telegraph ramp, build-mode toggle), then **commit**. Then open **Slice 2 — Two Classes (Warrior/Ranger)** with its adversarial netcode/determinism design review FIRST ([[validate-netcode-design-before-coding]]) before any `create_script`.
@@ -0,0 +1,41 @@
---
id: DR-038
title: Slice 1 — Combat Readability & HUD Declutter (health bars, telegraph, build-mode toggle, Charger lunge tell)
status: accepted
date: 2026-06-17
tags:
- decision
- design
- combat
- readability
- hud
- netcode
- slice
permalink: gamevault/07-sessions/decisions/dr-038-slice1-combat-readability-hud-declutter
---
# DR-038 — Slice 1: Combat Readability & HUD Declutter
> The first build slice of the co-op-roguelite redirect ([[DR-037_Procedural_Expedition_Spine_Two_Classes_Persistent_Meta]]) — the four "do-now" wins from the operator playtest ([[Scratch Notes 6152026]]): enemy health bars, a fixed/longer attack telegraph + scale-pulse, a build-mode HUD toggle, and a Charger committed-lunge tell. Mostly client-side; one small netcode re-bake. Code-complete + engineering-validated; the visual fun-gate is the open operator item.
## Context
The playtest flagged two readability gaps ("enemies need visible health bars + a more visible indicator of when they are going to hit") and one HUD-clutter gap ("hide everything buildable by default — enter build mode"). Research (8-agent pass, [[DR-037_Procedural_Expedition_Spine_Two_Classes_Persistent_Meta]]) set the conventions: **on-damage-sticky** bars (NOT always-on, which clutters hordes), a telegraph that ramps **0→1 ending AT impact**, multi-sensory cues, and a minimalist run-phase HUD.
## Decisions
**Feature A — Build-mode toggle (client-only, no netcode):** `BuildPaletteState` gains a `PaletteOpen` bool (orthogonal to `Active` = a slot is selected); `Select` auto-opens, `Clear`/close cancels. `HudSystem` gates `_paletteRow` on `PaletteOpen`; a subtle bottom-center **"Tab/Y — BUILD" discovery chip** shows while the palette is closed at base. The toggle is a raw device read (Keyboard `Tab` / Gamepad `buttonNorth`) in `BuildSendSystem`**NOT** a new `.inputactions` action (the generated `ProjectMInput.cs` is overwritten on regen; CLAUDE.md two-file-fragility rule). Combat input was already suppressed while a slot is selected, so closing the palette ungates combat for free.
**Feature B — Enemy health bars (client-only, NO new replication):** pooled **world-space `Canvas` + `UnityEngine.UI.Image`** per live Husk (sortingOrder 5, below the UITK HUD's 50 — avoids Unity-6 world-UITK sorting bugs), managed in `CombatFeedbackSystem` exactly like the existing `_dangerZones` pool. **On the `Health.Current` decrease edge** → show + reset a 3 s timer → fade over 0.5 s; **always-on under 25 % HP**; pool-capped (nearest 24, beyond `FeelConfig.HealthBarMaxDistSq` hidden) to bound horde clutter; pruned via the main loop's `_seen` set. `Health.Current` is already a `[GhostField]`; `Health.Max` is baked-identical client-side — **no re-bake, no new replication**. Per-instance `Image.color` carries alpha (no shared-material bleed).
**Feature C — Telegraph fix + scale-pulse (the windup-source decision):** the danger-cone ramp denominator was a hard-coded `22f` that plateaus early for longer windups (the Charger). The client needs the per-enemy windup **duration**, but `TuningConfig` is **server-only** (created only by the `#if UNITY_EDITOR` `DevTuningReceiveSystem` → absent on release clients) and `LungeState` is server-only. **Decision: a NEW baked client-safe component `EnemyTelegraph { byte WindupTicks; byte IsCharger }`** — baked values are identical on both worlds, so the client reads it directly to size the ramp + pick the Charger look, with **no `[GhostField]`** (it never changes → snapshot-independent). **`EnemyAuthoring.EnemyBaker` is the SOLE writer** even on the Charger (the prefab composes both authorings on one entity) — it reads sibling `ChargerAuthoring` to set `IsCharger`/the Charger windup, avoiding a double-`AddComponent`. `byte` not enum (the Burst cross-assembly-enum ICE rule). Also raised `Tuning.AttackWindupTicks` 18→22 (~367 ms, ≥ reaction + interp buffer) and folded a short anticipation **scale-pulse** into the client-owned cone (never the ghost transform).
**Feature D — Charger committed-lunge tell (the one re-bake):** today the cue VANISHES at lunge commit (`AttackWindup` zeroes). **Decision: a new `[GhostEnabledBit] IsLunging : IComponentData, IEnableableComponent`** — the **minimal** replicated surface (a single bit; the client derives the lunge HEADING for free from the already-replicated `LocalTransform.Rotation`, which `EnemyAISystem` writes each lunge tick). **Server derives the bit ONCE per tick** in a single dedicated post-pass loop in `EnemyAISystem` (`isLunging = LungeState.UntilTick != 0`) — chosen over toggling at the 5 lunge-mutation sites (bug-prone across the `continue` paths); this mirrors `PlayerDeathStateSystem` deriving `Dead` from `Health`. **Baked DISABLED** (Chargers spawn not-lunging) + visited via `.WithPresent<IsLunging>()` (the Dead idiom). `CombatFeedbackSystem` widens the danger-cone gate from "windup active" to "windup OR lunging" (lunge = intensity 1, forward-stretched), giving a seamless windup→committed-travel cue. Adding the `[GhostEnabledBit]` changes the **Charger ghost hash → re-bake** (validated below).
## Consequences
- **Validation (engineering, green):** clean compile (0 errors/warnings incl. the Burst-affecting `EnemyAISystem` query edit + the `[GhostEnabledBit]` source-gen); **345/345 EditMode** (342 baseline + 3 new `IsLunging` derive tests: commit-enables, whiff-disables, knockback-disables-via-the-mid-body-continue-path); **Play-validated** — the ghost-hash change did **NOT** break the netcode handshake (server+client `conns=1`), the bake added `EnemyTelegraph` to all 4 enemy prefabs + `IsLunging` baked-DISABLED on the Charger and it **replicated to the client world** (present + disabled), and **zero runtime errors** with the new systems running.
- **Open (operator fun-gate — visual/feel, needs eyes):** do the bars read on-damage and not clutter a swarm? does the Charger cone now persist through the committed lunge (no vanish at commit) and point along the travel? does the windup ramp fill correctly for both Grunt and Charger? does the build-mode toggle + discovery chip feel discoverable + declutter the HUD? Tuning knobs live: `Tuning.AttackWindupTicks`, `FeelConfig.HealthBarMaxDistSq`, the baked `EnemyTelegraph.WindupTicks` (Grunt 22 / Charger 30).
- **Reusable patterns (carry into Slices 24):** (1) for data the **client needs but the server owns** and that **never changes**, bake a client-safe component instead of replicating (`EnemyTelegraph`); (2) a **`[GhostEnabledBit]` derived once/tick from a server-only field** extends the `Dead`/`Downed` idiom to enemies (`IsLunging`) — single-bit replication, direction derived from `LocalTransform`.
- **Files:** `Simulation/Combat/AttackWindup.cs` (+`EnemyTelegraph`), `Simulation/Combat/LungeState.cs` (+`IsLunging`), `Simulation/Tuning.cs`, `Authoring/Combat/EnemyAuthoring.cs` + `ChargerAuthoring.cs` (bake), `Server/Combat/EnemyAISystem.cs` (derive loop), `Client/Presentation/CombatFeedbackSystem.cs` (bars + merged telegraph/lunge + pulse), `Client/Presentation/HudSystem.cs` + `Client/Building/BuildPaletteState.cs` + `BuildSendSystem.cs` (toggle), `Client/Presentation/FeelConfig.cs`, `Tests/EditMode/ChargerTests.cs`.
- **CLAUDE.md:** not yet updated — the two reusable patterns above are good gotcha candidates but earn an inline line once Slices 24 make the enemy-telegraph/enableable-derive conventions load-bearing (net-zero rule). Flagged, not silently deferred.