Docs: DR-039 Slice 2 (two classes) record + roadmap update
Slice 2 complete: Warrior/Ranger, DRG-asymmetric, aim-directed cone, menu picker, class carrier via GoInGameRequest. Two VFX-polish items deferred (review-sanctioned). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ Last decluttered 2026-06-08 (removed all shipped `[x]` items; their context is p
|
|||||||
**Committed slices (sequence; ~70 % of the chassis already exists — reuse, don't rebuild):**
|
**Committed slices (sequence; ~70 % of the chassis already exists — reuse, don't rebuild):**
|
||||||
|
|
||||||
- **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 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)** *(review-gated)*: route `MeleeComboSystem` damage through `EffectiveAbilityStats` (the highest-impact edit; `StatTarget.MeleeDamage` byte); class anchor via `AbilityRef.Id` + `CharacterId`; wire the **Cone** archetype (Warrior secondary), keep the Ranger projectile; second ability cooldown; class-select UX; class-conditioned starting `StatModifier`. Absorbs Path B's MC-6 intent (lighter).
|
- **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** *(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 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).
|
- **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).
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ Ran the adversarial pre-code review (1 ground + 3 lenses: netcode/determinism ·
|
|||||||
|
|
||||||
**Phase 1 BUILT + green (this session):** `StatIds` (CharacterId.Warrior=2/Ranger=3, AbilityId.WarriorCone=4, StatTarget.MeleeDamage=9/MeleeRange=10); `CharacterStatsRef.Id` → `[GhostField]` (the one re-bake); `MeleeComboSystem` folds the per-player melee mods (defensive `HasBuffer` guard → identity without seeds, so behavior-preserving). Compiles clean; **345/345 EditMode**. UNCOMMITTED (partial slice).
|
**Phase 1 BUILT + green (this session):** `StatIds` (CharacterId.Warrior=2/Ranger=3, AbilityId.WarriorCone=4, StatTarget.MeleeDamage=9/MeleeRange=10); `CharacterStatsRef.Id` → `[GhostField]` (the one re-bake); `MeleeComboSystem` folds the per-player melee mods (defensive `HasBuffer` guard → identity without seeds, so behavior-preserving). Compiles clean; **345/345 EditMode**. UNCOMMITTED (partial slice).
|
||||||
|
|
||||||
## Next (Slice 2 continuation)
|
## Slice 2 — COMPLETE + validated (2026-06-18, [[DR-039_Slice2_Two_Classes_Warrior_Ranger]])
|
||||||
|
|
||||||
Build the rest of Slice 2: (1) `Tuning.ClassSourceId`; (2) the Warrior cone — `PendingConeFire` + `AbilityFireSystem` Cone-archetype branch (aim-directed, cooldown both worlds, signal server-only) + `ConeFireDamageSystem`; (3) the class carrier — `GoInGameRequest.ClassId` + `ClassSelection` singleton + `GoInGameClientSystem` send + `GoInGameServerSystem` class writes; (4) AbilityDatabase authoring rows (Warrior/Ranger `CharacterStatsBlob`; WarriorCone `AbilityDefBlob`, archetype Cone, wide angle in `AutoTargetConeRadians`); (5) the menu picker UI (`MainMenuController` → `WorldLauncher` → `ClassSelection`); (6) a client cone VFX + the slash-arc reading the folded melee range. Then re-bake + Play-validate (server==client, the class asymmetry, the cone, clean handshake) + class-fold/cone EditMode tests, then commit Slice 2 as one unit (DR-039). Slice 1's operator visual fun-gate also still open.
|
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).
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
id: DR-039
|
||||||
|
title: Slice 2 — Two Classes (Warrior melee / Ranger ranged)
|
||||||
|
status: accepted
|
||||||
|
date: 2026-06-18
|
||||||
|
tags:
|
||||||
|
- decision
|
||||||
|
- design
|
||||||
|
- combat
|
||||||
|
- classes
|
||||||
|
- netcode
|
||||||
|
- slice
|
||||||
|
permalink: gamevault/07-sessions/decisions/dr-039-slice2-two-classes-warrior-ranger
|
||||||
|
---
|
||||||
|
|
||||||
|
# DR-039 — Slice 2: Two Classes (Warrior / Ranger)
|
||||||
|
|
||||||
|
> The second build slice of the co-op-roguelite redirect ([[DR-037_Procedural_Expedition_Spine_Two_Classes_Persistent_Meta]]). Two playable classes whose identity = the primary-attack archetype + asymmetric stats, with run-acquired meta-buffs riding the existing `StatModifier` pipeline. Preceded by the mandatory adversarial design review ([[validate-netcode-design-before-coding]]); forks operator-locked. Built + validated; the WarriorCone client VFX is the one deferred polish item.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
[[DR-037_Procedural_Expedition_Spine_Two_Classes_Persistent_Meta]] committed "two classes (Warrior melee / Ranger ranged)". The pre-code review (1 ground + 3 lenses — netcode/determinism · class-feel · reuse/scope, all **GO-WITH-CHANGES**) caught two strawman errors and the palette-swap risk; the operator locked all four recommended forks.
|
||||||
|
|
||||||
|
## Operator forks (locked 2026-06-18)
|
||||||
|
|
||||||
|
- **Asymmetric melee — DRG model:** Warrior = longer-reach + harder-hitting melee, slower + tankier; Ranger = shorter/weaker melee, faster + longer range. (Rejected: symmetric melee → reads as a skin swap.)
|
||||||
|
- **Aim-directed Warrior cone:** the Fire-slot cone is pointed by aim (a deliberate crowd-control burst), short range, wide arc, short cooldown.
|
||||||
|
- **Menu picker:** per-player class chosen in the menu, carried on the spawn RPC.
|
||||||
|
- **Two seeds + co-op synergy:** each class seeds 2 combat stats; the Ranger gets a wider auto-target assist so the Warrior's knockback feeds it.
|
||||||
|
|
||||||
|
## Decision (with the review's fixes folded in)
|
||||||
|
|
||||||
|
**1. Class carrier = `GoInGameRequest.ClassId` (NOT `ConnectionConfig`).** The review's BLOCKER: `ConnectionConfig` is a per-WORLD singleton cleared pre-spawn — it cannot hold N players' classes in co-op. Added a `byte ClassId` to the existing `GoInGameRequest` RPC. The client reads a client-world **`ClassSelection`** singleton (seeded by `WorldLauncher` from the menu's `SelectedClass`) into the RPC; **`GoInGameServerSystem`** (the system that already spawns the player + stamps GhostOwner/RegionTag) writes the class at spawn via **`ClassTraits`**: `ecb.SetComponent` the `AbilityRef.Id` (Warrior = WarriorCone / Ranger = Primary) + `ecb.AppendToBuffer` the trait seeds. 0/unknown → Warrior.
|
||||||
|
|
||||||
|
**2. Character deltas ride replicated StatModifier SEEDS on the Default character (no per-class blob row).** `CharacterStatsRef.Id` stays `Default(1)`; the DRG-asymmetry (MoveSpeed/MaxHealth) + the kit (Range/AutoTargetRange/MeleeDamage/MeleeRange) are 4 `StatModifier` seeds per class on the reserved `Tuning.ClassSourceId` (never stripped). Because `StatModifier` is `[GhostField] OwnerSendType.All`, the owning client folds the correct `EffectiveCharacterStats`/melee — so the class is correct under prediction **without** new character blob rows. *(`CharacterStatsRef.Id` was still promoted to `[GhostField]` — committed in Phase 1 — and is currently unused-but-harmless; it future-proofs a mid-run class swap / blob-based char stats.)*
|
||||||
|
|
||||||
|
**3. Melee asymmetry folds in `MeleeComboSystem`, not `EffectiveAbilityStats`.** New `StatTarget.MeleeDamage`/`MeleeRange` (bytes 9/10) are folded onto the live-tunable `TuningConfig.MeleeDamage`/`MeleeRange` base **inside `MeleeComboSystem`** (server-side cleave, deterministic-safe) off the player's replicated mods buffer (via a `BufferLookup<StatModifier>` + `.WithEntityAccess()` — the query was already at the 7-arg `SystemAPI.Query` max; `HasBuffer`-guarded → identity without seeds). Chosen over routing through `EffectiveAbilityStats.Damage` to avoid conflating melee with the Fire ability's Damage axis — melee gets its own augment axis (the review's "single highest-impact edit").
|
||||||
|
|
||||||
|
**4. The Warrior cone = the Cone archetype, resolved INLINE in `AbilityFireSystem`'s server branch** (same-tick, mirroring the `MeleeComboSystem` cleave — it runs before `HealthApplyDamageSystem` in the predicted group), NOT a separate `ConeFireDamageSystem`. Cooldown advances on **both** worlds (the client predicts the gate); the cone **damage is server-only** (`if (isServer)`, `SourceTick`-stamped, `MeleeConeMath.InCone` collect-all over living enemies gathered once at the top). No projectile ghost, no `PendingConeFire` component. `WarriorCone` ability authored (archetype Cone, dmg 22, range 2.2, ~130° arc via `AutoTargetConeDegrees=65`, cd 22) and added to the gameplay subscene's AbilityDatabase (re-baked).
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- **Validation (green):** clean compile; **348/348 EditMode** (incl. 3 new `ClassTraits` tests: ability mapping + the asymmetric seed folds). **Play-validated the Warrior end-to-end, server==client:** `AbilityRef=WarriorCone`, 4 seeds, `eff MaxHP 130 / MoveSpeed 5.1 (base 6×0.85) / cone dmg 22 / coneRad 1.13` identical on both worlds; **`conns=1` — the `CharacterStatsRef` re-bake did NOT break the handshake**; zero runtime errors. The Ranger path is the same carrier code with `classId=3` (covered by the EditMode seed test + the symmetric write); editor-default boot is always Warrior (the menu drives the choice).
|
||||||
|
- **Reusable patterns:** per-class deltas via replicated `StatModifier` seeds on a shared character (no per-class blob churn, prediction-correct via `OwnerSendType.All`); an archetype handled inline in the predicted fire system with a server-only damage branch (the cleave idiom) when no ghost is spawned.
|
||||||
|
- **Deferred polish (review-sanctioned):** the WarriorCone has **no client-side fire VFX** (server-only damage; the player sees damage numbers + enemy reactions but no cone effect on their own fire) — schedule a `CombatFeedbackSystem` cone-flash; and the melee **slash-arc visual still reads the base `TuningConfig.MeleeRange`**, so the Warrior's longer reach isn't shown in the arc (fold the local player's MeleeRange seed there). Neither is a functional gap.
|
||||||
|
- **Open (operator):** the Slice 2 fun-gate (do the two classes feel distinct in a real fight; does the cone read as a panic burst) + Slice 1's still-open visual fun-gate.
|
||||||
|
- **Files:** `StatIds`/`Tuning`/`CharacterStatsRef`/`MeleeComboSystem` (Phase 1, `d9d67c4e7`); `ClassSelection`/`ClassTraits`/`GoInGameRequest`/`GoInGameClientSystem`/`GoInGameServerSystem`/`AbilityFireSystem` (`a7fdd6f71`); `Ability_WarriorCone.asset` + subscene + `ClassTraitsTests` (`0a3a39e3d`); `WorldLauncher`/`MainMenuController` (`431a7e2ed`).
|
||||||
Reference in New Issue
Block a user