Docs: DR-040 Slice 3 (Expedition Combat Spine) reviewed + scope-locked

Heaviest pre-code review done (1 ground + 3 lenses). v1 loop scope-locked: walk gate ->
epoch-seeded enemy wave in the expedition -> clear -> return + Ore -> escalated base siege.
4 netcode blockers fixed-in-spec (EnemyAISystem per-region targets; WaveSystem cleared-check
RegionTag{Base}; relevancy MaxAlive cap; reward per-epoch sentinel). Arena pool / zone-theme
byte / TimedModifier buff / SaveData v6 / mini-boss deferred to v2. Build is the next unit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 00:56:03 -07:00
parent d6d75b4706
commit 6b368d1a29
3 changed files with 61 additions and 2 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ Last decluttered 2026-06-08 (removed all shipped `[x]` items; their context is p
- **Slice 1 — Combat Readability & HUD Declutter** ✅ **BUILT + engineering-validated 2026-06-17 ([[DR-038_Slice1_Combat_Readability_HUD_Declutter]])** — on-damage-sticky **enemy health bars** (+fade, <25 %HP always-on) in `CombatFeedbackSystem` (`Health.Current` already replicated); **telegraph fix** (per-enemy baked `EnemyTelegraph` windup ramp replacing the hard-coded `22f` + windup 18→22 + a scale-pulse); **build-mode toggle** (client `BuildPaletteState.PaletteOpen` gates the palette + a "Tab/Y — BUILD" chip); **Charger committed-lunge tell** (`[GhostEnabledBit] IsLunging` derived once/tick — one Charger re-bake). 345/345 EditMode; Play-validated (handshake intact, bake correct, no runtime errors). **Open: operator visual fun-gate.**
- **Slice 2 — Two Classes (Warrior / Ranger)** ✅ **BUILT + validated 2026-06-18 ([[DR-039_Slice2_Two_Classes_Warrior_Ranger]])** — DRG-asymmetric (Warrior melee bruiser tankier/slower; Ranger ranged faster/squishier + co-op auto-assist hook); aim-directed Warrior cone (Cone archetype inline in `AbilityFireSystem`, server-only damage, same-tick); class carried on `GoInGameRequest.ClassId` (the per-world-`ConnectionConfig` blocker fixed) → `ClassTraits` seeds at spawn; character deltas via replicated `StatModifier` seeds on Default (no per-class blob); melee asymmetry folds in `MeleeComboSystem` (`StatTarget.MeleeDamage`/`MeleeRange`); menu class picker. 348/348 EditMode; Warrior Play-validated server==client (re-bake handshake intact). **Deferred polish:** cone client VFX + slash-arc reach. **Open: fun-gate (operator).**
- **Slice 3 — Expedition Combat Spine** *(review-gated)*: reactivate `ExpeditionFieldSystem`**authored-arena pool** sampled by `ExpeditionEpoch` (pool-sampling, NOT WFC/terrain-gen); `ZoneEnemySpawnSystem` (`RegionTag{Expedition}`); the **`EnemyAISystem` region-filter fix**; the **`ThreatDirector` mid-siege-return gate**; a **zone-clear reward** on `ExpeditionGateSystem`; a **replicated zone-theme byte** on the global ghost; the required sortie→clear→return→escalated-siege loop. Manage `RegionRelevancySystem` per-tick cost (cap ghosts; cosmetic props non-ghost). Absorbs Path B MC-2 (enemy mix) intent.
- **Slice 3 — Expedition Combat Spine** 🔬 **REVIEWED + scope-locked 2026-06-18 ([[DR-040_Slice3_Expedition_Combat_Spine]]) — build is next.** Minimal v1 loop: walk gate → fight an epoch-seeded enemy wave in the expedition → clear → return + Ore → escalated base siege. New `ZoneEnemyTag`/`ZoneEnemyDirector`/`ZoneEnemyState` + `ZoneEnemySpawnSystem` (reuse Grunt/Charger prefabs, `RegionTag{Expedition}`, ring math, one-per-interval, Calm-only). **4 netcode blockers fixed:** `EnemyAISystem` per-region target lists (+ no base-structure/Core fallback for expedition husks); `WaveSystem` cleared-check → `RegionTag{Base}` only (+ ThreatDirector cull); RegionRelevancy `MaxAlive` cap; reward double-fire gated by `CycleRuntime.LastRewardedEpoch` + a real clear. **Deferred to v2:** arena-layout pool/authoring, zone-theme byte + HUD, `TimedModifier` buff, SaveData v6, mini-boss. **Open fork:** optional (default) vs required sortie.
- **Slice 4 — Persistent Meta-Progression** *(review-gated)*: **SaveData v6** (meta-currency, unlocked classes, persistent epoch); a between-runs growth surface at the hub; persist-the-seed-regenerate-the-layout (pure-function generator).
**Per-slice forks to lock at each review:** expedition depth (one zone vs sequential+boss); shared expedition region vs per-party instances (shared recommended); zone-clear reward shape; class-select moment (menu vs at-base); meta unlock surface.
@@ -47,6 +47,10 @@ Ran the adversarial pre-code review (1 ground + 3 lenses: netcode/determinism ·
Built the whole slice across 4 committed chunks (`d9d67c4e7``431a7e2ed`): the data layer + melee-augment routing; the class carrier (`GoInGameRequest.ClassId` + `ClassSelection` + `ClassTraits`, the review's fix for the per-world-`ConnectionConfig` blocker); the aim-directed Warrior cone (inline in `AbilityFireSystem`'s server branch, same-tick); the WarriorCone ability authored + subscene re-baked; the menu class picker. Character deltas ride replicated `StatModifier` seeds on the Default character (prediction-correct, no per-class blob churn); melee asymmetry folds in `MeleeComboSystem`. **348/348 EditMode** (+3 `ClassTraits` tests); **Warrior Play-validated end-to-end, server==client** (`AbilityRef=WarriorCone`, 4 seeds, eff MaxHP 130 / MoveSpeed 5.1 / cone dmg 22; `conns=1` — re-bake handshake intact; zero runtime errors). **Deferred (review-sanctioned polish):** the WarriorCone client VFX + the slash-arc reading the folded melee reach. **Open: the Slice 2 fun-gate** (do the classes feel distinct — operator's eyes).
## Slice 3 — Expedition Combat Spine: REVIEWED + scope-locked (2026-06-18, [[DR-040_Slice3_Expedition_Combat_Spine]])
Ran the heaviest pre-code review (1 ground + 3 lenses — netcode/determinism · procgen-feel · reuse/scope). Verdict: PROCEED WITH BLOCKERS RESOLVED; the chassis is heavily reused. Scope-locked to a tractable **v1 loop** (walk gate → epoch-seeded enemy wave in the expedition → clear → return + Ore → escalated base siege), all forks at the recommended defaults. **4 netcode blockers identified + fixes specified:** `EnemyAISystem` per-region target lists (the known bug is broader — base structures + Core fallback leak too); `WaveSystem` cleared-check must filter `RegionTag{Base}` (else the base siege never clears while expedition enemies live — a NEW blocker the review caught); RegionRelevancy `MaxAlive` cap; reward double-fire gated by a per-epoch sentinel + a real clear. Deferred to v2: arena-layout pool, zone-theme byte, `TimedModifier` buff, SaveData v6, mini-boss. The plan is build-ready (precise component/system/test/Play spec in DR-040). **Status: reviewed + locked, NOT built.**
## Next
Continue the roadmap: **Slice 3 — Expedition Combat Spine** (the procedural run venue). Open it with its adversarial netcode/determinism design review FIRST ([[validate-netcode-design-before-coding]]) — it's the heaviest netcode slice (reactivate `ExpeditionFieldSystem` → authored-arena pool + `ZoneEnemySpawnSystem(RegionTag{Expedition})` + the `EnemyAISystem` region-filter fix + the `ThreatDirector` mid-siege-return gate + a zone-clear reward + a replicated zone-theme byte + the required sortie→clear→return→escalated-siege loop; manage `RegionRelevancySystem` per-tick cost). Then Slice 4 (persistent meta). Operator fun-gates for Slices 1 + 2 remain open (their eyes).
**Build Slice 3 v1** from [[DR-040_Slice3_Expedition_Combat_Spine]] (start: new components + the 3 surgical server-count fixes → the `EnemyAISystem` region-filter (riskiest — test it) → `ZoneEnemySpawnSystem` + authoring + the Ore reward → tests + Play-validate → commit). Then Slice 4 (persistent meta). Operator fun-gates for Slices 1 + 2 (and, once built, 3) remain open (their eyes); the optional-vs-required-sortie fork defaults to optional.
@@ -0,0 +1,55 @@
---
id: DR-040
title: Slice 3 — Expedition Combat Spine (v1 plan, reviewed + locked)
status: accepted
date: 2026-06-18
tags:
- decision
- design
- combat
- expedition
- procgen
- netcode
- slice
permalink: gamevault/07-sessions/decisions/dr-040-slice3-expedition-combat-spine
---
# DR-040 — Slice 3: Expedition Combat Spine (v1 plan)
> The third build slice of the co-op-roguelite redirect ([[DR-037_Procedural_Expedition_Spine_Two_Classes_Persistent_Meta]]) — reactivate the dormant Expedition region as a procedural COMBAT venue (today it only has resource harvesting). The heaviest netcode slice. Preceded by the mandatory adversarial design review (1 ground + 3 lenses — netcode/determinism · procgen-feel · reuse/scope). **Reviewed + scope-locked; build is the next unit.** All forks resolved to the recommended defaults (autonomous, per the operator's "build, don't stop" directive); one is flagged for the operator.
## Verdicts
Netcode: **PROCEED WITH BLOCKERS RESOLVED** (the chassis is sound + heavily reused; four netcode traps are load-bearing). Feel: sound, with adjustments (encounter variety > layout variety; co-op pacing; reward legibility). Scope: sound; **defer three over-specified areas** → v1 is a single focused loop: *walk the gate → fight an epoch-seeded enemy wave in the expedition → clear → return richer → escalated base siege.*
## Locked forks (recommended defaults)
- **Sortie = OPTIONAL** (the post-expedition retaliation is the only siege source; `ThreatDirector.ScheduleEnabled` stays baked-but-inert). Sidesteps split-party co-op frustration. **⚠️ operator-flag:** the feel lens called required-vs-optional a real design fork; default OPTIONAL — revisit if you want forced sorties.
- **One enemy wave per sortie** (no arena-layout pool in v1 — defer the 12-18 authored arena configs to v2). Variety comes from epoch-seeded enemy COMPOSITION (Grunt-heavy → Charger-heavy as epoch climbs), the highest-leverage lever per the research (encounter > layout).
- **Zone enemies reuse the Grunt/Charger prefabs** + `RegionTag{Expedition}` + a `ZoneEnemyTag` marker (they keep `EnemyTag` so Slice-1 readability/health-bars/telegraphs/damage all work for free).
- **Spawn during Calm only**; **flat Ore reward** (no kill-counter); **fold the reward into `ExpeditionGateSystem`** (atomicity).
- **Defer to v2:** the replicated zone-theme/depth byte + HUD (v1 uses the existing arid atmosphere); the `TimedModifier` +damage buff (Ore deposit is the complete v1 reward); SaveData v6 (sortie is session-scoped — epoch 0-defaults safely); the mini-boss; sequential/depth zones; per-party instances.
## Decision — v1 build (with the four netcode blockers fixed)
**BLOCKER 1 — `EnemyAISystem` region filter (the known bug, broader than first stated).** The player snapshot, the structure snapshot, AND the Core fallback + early-return are ALL base-region; an expedition enemy would path to (and the Core-fallback would pin every expedition enemy to) the invisible base. **Fix:** build target lists PER REGION (or one list + a parallel `NativeList<byte>` regions + an equality guard); add `RefRO<RegionTag>` to BOTH seek queries (Grunt `WithNone<LungeState>` + Charger `WithAll<LungeState>`); an expedition husk gets an **empty** structure list and **no** Core fallback (idle if its arena has no expedition player). Burst-safe (byte compare). Cover with an EditMode test (expedition husk + base player @base + expedition player @+1000 → asserts it seeks the +1000 player, never the base player/Core/structure).
**BLOCKER 2 — `RegionRelevancySystem` O(ghosts×connections)/tick does not scale; mitigate.** It `set.Clear()` + rebuilds the whole `GhostRelevancySet` every tick (60Hz) on the `GhostSendSystem` prerequisite path. v1's enemy counts (~15-35) are within budget (scope lens), so v1 may ship without caching — BUT enforce a **`MaxAlive` cap** in `ZoneEnemyDirector` and keep cosmetic props NON-ghost. (Incremental caching — rebuild only on a region-flip / ghost-create-destroy dirty flag, gated by the gate/transit + spawner — is the v2 fix when ghost counts grow.)
**BLOCKER 3 — `WaveSystem` "cleared" check is GLOBAL `EnemyTag`** → the base siege can NEVER complete while expedition enemies live. **Fix:** filter the `m_AliveHusks`/cleared-check to `RegionTag{Base}` only. Likewise gate `ThreatDirectorSystem`'s `SiegeTimeout` Husk-cull to `RegionTag{Base}` (else it culls expedition enemies). (These are the surgical reason zone enemies keep `EnemyTag` — Option A — rather than a fully-separate tag.)
**BLOCKER 4 — co-op double-fire of the reward.** Two players returning the same tick each increment `PendingReturns` (the siege de-dups, but the reward wouldn't). **Fix:** gate the zone-clear reward on a per-epoch sentinel — add `CycleRuntime.LastRewardedEpoch` (server-only, free) and award once per `ExpeditionEpoch`, only after a **real clear** (zone-enemy count reached 0 this epoch — prevents farming via rapid gate re-entry, MINOR 2). Apply in `ExpeditionGateSystem`'s Base-bound block (or an ordered `ZoneClearRewardSystem` Gate→Reward→ThreatDirector).
**New types (v1):** `ZoneEnemyTag` (marker); `ZoneEnemyDirector{int MaxAlive; float RingRadius; int RingSlots; int SpawnIntervalTicks; int GruntsPerWave; int ChargersPerWave; int RewardOre}` (baked singleton — the `ResourceFieldSpawner` authoring pattern, on the gameplay subscene; a `ZoneEnemyPrefab` buffer aliasing the existing Grunt/Charger prefabs); `ZoneEnemyState{uint SpawnCounter; int RemainingToSpawn; uint NextSpawnTick; uint SeededEpoch}` (server-only — its OWN counter, NEVER `WaveState.SpawnCounter`, MAJOR 1). `CycleRuntime.LastRewardedEpoch` (+ a "cleared this epoch" track).
**`ZoneEnemySpawnSystem`** (server `SimulationSystemGroup`, `UpdateAfter(ExpeditionFieldSystem)`, `UpdateBefore(CyclePhaseSystem)`): runs when the expedition is occupied + `Phase==Calm`; epoch-seeded composition (distinct seed offset from the field's, e.g. `epoch*3+2`); one-per-`SpawnIntervalTicks` cadence (amortise — MAJOR 2); ring-slot via `EnemyAIMath.RingPosition` at `RegionMath.RegionOrigin(Expedition)`; **`ecb.Instantiate` the prefab + `baked.WithPosition(pos)` (NOT `FromPosition` — preserves the `[GhostField]` Scale) + `AddComponent(RegionTag{Expedition})` + `AddComponent<ZoneEnemyTag>`** before Playback (runtime-ghost, prespawn-dodge — MAJOR 5); capped at `MaxAlive`; composition = pure `f(epoch, slotIndex)` with stable order (save-reproducible). **Teardown:** extend `ExpeditionFieldSystem`'s occupied→empty teardown to also destroy `ZoneEnemyTag` entities (single lifetime point); also add the missing `RegionTag{Expedition}` guard to its `BlightClutter` teardown (MINOR 1, defensive).
**Reward:** flat `RewardOre` to the shared `ResourceLedger` (untagged `CycleDirector`, replicated) via `StorageMath`/`ecb.AppendToBuffer`, gated by Blocker 4. The escalated base siege is the EXISTING `ThreatState.PendingReturns` retaliation (already wired + phase-gated — no change).
## Consequences
- **v1 loop is complete + testable + shippable:** sortie → real expedition combat → clear → return + Ore → escalated base siege → `GoalProgress` +1. Reuses region split, relevancy, gate, epoch-seeded chassis, ring math, threat retaliation, the two-class combat, AND Slice-1 readability (zone enemies are full `EnemyTag` enemies).
- **EditMode tests (planned):** region-filter target correctness; deterministic epoch scatter (same epoch → identical spawns); WaveSystem cleared-check ignores expedition enemies; zone-clear reward deposits once per epoch. **Play-validation:** zone enemies spawn `RegionTag{Expedition}` (visible to expedition player, NOT a base-only connection); kill them, return, retaliation siege arms; expedition player gets no base-Husk aggro; server==client; relevancy cost sane.
- **Deferred to v2 (logged):** arena-layout pool + authoring; zone-theme/depth byte + pre-gate HUD ("DEPTH 5 — BOSS"); the felt-beat replicated reward + `TimedModifier` buff; SaveData v6 (persist epoch); mini-boss; layout×encounter decoupling; sequential zones; RegionRelevancy incremental caching.
- **Open (operator):** the OPTIONAL-vs-REQUIRED sortie fork (default OPTIONAL); the Slice 3 fun-gate; the still-open Slice 1 + 2 fun-gates.
- **Status:** reviewed + locked, **NOT built** — the build is the immediate next unit (start with the new components + the three surgical server-count fixes, then the `EnemyAISystem` region-filter (the riskiest, test it), then `ZoneEnemySpawnSystem` + authoring + the reward, then tests + Play-validate, then commit). Full review (verdicts/blockers/forks/sources) in the run transcript `wf_b8033e26-1f5`.