Slice Combat Depth (MC-3 + wiring + review fixes): Spitter aim-line + player-hit punch, rigged enemies, in-band gate (DR-041)

Completes the Combat Depth slice on top of the MC-2 server spine (56cf60cce):

MC-3 impact juice (client, observe-only):
- 7 FeelConfig fields + ResetDefaults; magnitude-scaled player-dealt-hit camera
  PunchFov on the enemy-Health-decrease edge (camera-only hit-stop, never timeScale).
- Spitter Kind==2 aim-LANE telegraph (BuildLaneMesh) — reads baked SpitterState
  client-side, falls back to a fixed length. True freeze + material flash deferred.

Content / wiring:
- SpitterProjectilePrefabAuthoring (the SpitterProjectilePrefab singleton).
- Both directors rebuilt to a 4-entry KIND-INDEXED roster [Grunt,Charger,Spitter,
  Swarmer] + mix/MaxAlive config + the SpitterProjectileConfig singleton in the subscene.
- Real rigged models: EnemySpitter (re-skinned Kaiju, ranged poker) + EnemySwarmerUndead
  (Undead-Werewolf, fast/low-HP); grunt/charger keep Werewolf/ChargerMuscle. EnemySpit =
  ownerless interpolated ghost (no Health, no collider).

Post-impl adversarial review fixes (wf_febdcfdb-665):
- [MED] in-band fire gate: the Spitter committed its telegraph from ANY range (fired while
  advancing from far). Now commits only when sInBand || sCornered (gives CorneredRange a
  real read site) — a Spitter out-of-band holds fire and repositions.
- [LOW] EnemyProjectileDamageSystem early-returns on !ServerTick.IsValid (sibling parity).
- [LOW] EnemyAuthoring bake-time guard: errors if a prefab composes both Charger+Spitter
  (would match zero AI passes -> never move).
- [LOW] tests: Spitter brain fires from Expedition (kills the Base==0 region false-green);
  a direct partition-exclusion test replaces the order-masked claim; added out-of-band +
  cornered negative tests.

388/388 EditMode green + two Play smokes (clean boot, fire, swept-hit, region, server==
client; rigged Kaiju spitter bakes + fires with zero console errors). Accepted as-is
(documented in DR-041): global spit soft-cap, co-op punch attribution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 21:08:59 -07:00
parent 56cf60cce3
commit e32dadbc66
20 changed files with 5907 additions and 16 deletions
@@ -76,17 +76,24 @@ Adopt `WaveSlots`/`KindForSlot` + a 4-entry prefab buffer; **add `WaveDirector.M
Pure-math: `WaveSlots`≥1, per-epoch ramps, `KindForSlot` determinism + composition counts, swarmer bucketing, `PackSizeForSlot`≥1, `IsChargerSlot` wrapper keeps 4 legacy assertions; **parity** test (`WaveSlots` reproduces legacy curve); `BandVelocity` (retreat/advance/in-band/Y-flat); `ClusterOffset` determinism. System: `EnemyProjectileMove` integrate+LastStep+range-expiry; `EnemyProjectileDamage` append+at-most-once + **tunnelling regression** (LastStep>radius still hits) + **region-filter** (Expedition spit doesn't damage Base target); Spitter brain (advance/retreat/in-band-commit-then-spawn-with-Region, soft-cap no-burn); cornered fallback; dash-through-spit negation; cluster spawn (PackSize spawned, 1 slot consumed, pack-over-MaxAlive defers); discriminator routing (no double-visit); 4-entry buffer guard. **Play-validation:** no sort-cycle at world-creation; no Burst ICE; Spitter end-to-end (holds range, aim-line telegraph, dodgeable+dash-negatable spit); region correctness (no cross-region see/damage); swarmer reads as a swarm + respects MaxAlive; mix ramp visibly shifts; MC-3 magnitude-scaled punch + predicted tick-rate UNAFFECTED (no timeScale); perf (live spits ≤24, stable frame time under base siege + expedition swarm).
## Consequences
- **Deferred to a later slice:** DOTS `[MaterialProperty]` enemy hit-flash (ShaderGraph `_Flash*` + render-entity mapping); true freeze-frame hit-stop (gated off); Spitter in-band strafe (1b); player-shoots-spit (2b); Swarmer pack-size epoch ramp (field exposed, unwired).
- **Deferred to a later slice:** DOTS `[MaterialProperty]` enemy hit-flash (ShaderGraph `_Flash*` + render-entity mapping); true freeze-frame hit-stop (gated off); Spitter in-band strafe (1b); player-shoots-spit (2b); Swarmer pack-size epoch ramp (field exposed, unwired); **per-region** spit soft-cap (v1 is GLOBAL — 24 is generous); a co-op **local-attribution gate** for the player-dealt-hit punch (v1 keys on any enemy-Health edge, so a teammate's hit nudges your camera a touch); **bespoke Spitter/Swarmer art** (v1 reuses the rigged Kaiju + Undead-Werewolf models for distinct silhouettes).
- **Open (operator):** the combat fun-gate is a hands-on co-op playtest after build ("play with a friend and not want to stop"); the Slice 3 fun-gate still pending too.
- **Status:** reviewed + locked; build IN FLIGHT (see below). Full review (verdicts/blockers/forks) in run transcript `wf_eb115556-8cc`.
- **Status:** BUILT + post-impl-reviewed + Play-validated (2026-06-24). Pre-coding review `wf_eb115556-8cc`; post-impl adversarial review `wf_febdcfdb-665` (3 lenses → per-finding refutation; **1 MED + 3 LOW fixed in code, 2 LOW documented**). **388/388 EditMode** + a clean netcode Play smoke (boot, fire, swept-hit, region, server==client). Fun-gate co-op playtest still pending.
## Build progress (in flight — 2026-06-22)
## Build record (complete — 2026-06-24)
**Done + compiling clean (368/368 EditMode still green, backward-compatible at epoch 1):**
- Leaf components: `SpitterState`(+baked `WindupTicks`), `SwarmerTag`, `EnemyProjectile`, `SpitterProjectilePrefab`, `MixBands` (`Simulation/Combat/`).
- Math: `EnemyAIMath.{BandVelocity, ClusterOffset}`; `ZoneEnemyMath` Kind consts + `WaveSlots/KindForSlot/PackSizeForSlot` (legacy `WaveSize`/`IsChargerSlot` kept intact for parity).
- Systems: `EnemyProjectileMoveSystem` + `EnemyProjectileDamageSystem` (plain server group, LastStep swept, region filter); `EnemyAISystem` Spitter pass + partition guards (`WithNone<LungeState,SpitterState>` Grunt / `WithNone<SpitterState>` Charger) + `m_EnemyProjectiles` cache.
- Discriminator: `EnemyTelegraph.IsCharger→byte Kind`; `EnemyAuthoring` bakes Kind from sibling authoring.
- Authoring: `SpitterAuthoring`, `SwarmerAuthoring`, `EnemyProjectileAuthoring`; both director components (`ZoneEnemyDirector`, `WaveDirector`) + their authoring gained the mix/cluster fields + (Wave) mandatory `MaxAlive`. Base siege adopts `WaveSlots`/`KindForSlot`/cluster (fork 4a); defaults keep the size curve (≈+1 charger +1 spitter/wave) so the END-game stays bounded.
Built through the full `/dots-dev` ladder: **388/388 EditMode green** + a Play smoke (clean two-world boot, no sort-cycle/ICE; Spitter fires → spit sweeps → player damaged → drained, region-correct, **server==client**) + the post-impl adversarial review (`wf_febdcfdb-665`). Two commits: the **MC-2 server spine** (`56cf60cce`) and the **MC-3 + tests + wiring + review-fixes** (one commit, on operator go).
**Remaining:** MC-3 client juice (FeelConfig fields + `CombatFeedbackSystem` player-hit camera punch + Spitter `Kind==2` aim-line); the additive EditMode tests (per the test plan above); prefab + subscene wiring (Spitter/Swarmer/EnemyProjectile ghosts via the new-ghost recipe, 4-entry director rosters, `SpitterProjectilePrefab` singleton, MixBands/MaxAlive on both directors); then the verify ladder + Play smoke + post-impl review + doc bookend + commit. **Resume from here if compacted.**
**Shipped (MC-2 spine):** `SpitterState`, `SwarmerTag`, `EnemyProjectile`, `SpitterProjectilePrefab`, `MixBands`; `EnemyAIMath.{BandVelocity,ClusterOffset}`; `ZoneEnemyMath` Kind consts + `WaveSlots/KindForSlot/PackSizeForSlot` (legacy `WaveSize`/`IsChargerSlot` kept + parity-tested); `EnemyProjectileMove/DamageSystem` (plain server group, LastStep swept, region filter, at-most-once destroy); `EnemyAISystem` Spitter pass + partition guards; `EnemyTelegraph.IsCharger→byte Kind` + `EnemyAuthoring` Kind baking.
**Shipped (MC-3 juice):** 7 `FeelConfig` fields (+ `ResetDefaults`); magnitude-scaled **player-dealt-hit `PunchFov`** on the enemy-`Health`-decrease edge; the **Spitter `Kind==2` aim-LANE** telegraph (`BuildLaneMesh`; reads baked `SpitterState` client-side, falls back to a fixed length). True freeze-frame + `[MaterialProperty]` enemy hit-flash DEFERRED (gated off / own ShaderGraph slice).
**Shipped (content):** `SpitterProjectilePrefabAuthoring` (the singleton); both directors carry a 4-entry **kind-indexed** roster `[Grunt,Charger,Spitter,Swarmer]` + mix/MaxAlive config (fork-4a base siege included) + the `SpitterProjectileConfig` singleton. **Real rigged models:** Spitter = `EnemySpitter` (rigged Kaiju, dialed to a ranged poker — HP 28/spd 2.8/cd 66), Swarmer = `EnemySwarmerUndead` (rigged Undead-Werewolf — HP 8/spd 6.5/cd 24, scale 0.6); grunt/charger keep Werewolf/ChargerMuscle. Spit = `EnemySpit` (ownerless interpolated ghost, no `Health`, no collider). All bake clean (zero console errors, Rukhanka rig intact). *(The simple-mesh `Enemy*` prototypes were left untouched; `EnemySwarmer.prefab` reverted.)*
**Post-impl review fixes (`wf_febdcfdb-665`, all re-validated 388/388 + a re-Play):**
- **[MED] in-band fire gate** — the Spitter committed its telegraph from ANY range (would fire while advancing from far, defeating the hold-range question). Fixed: commit only when `sInBand || sCornered` (which gives the once-dead `CorneredRange` a real read site — a Spitter advancing-from-far OR retreating-from-too-close now holds fire and repositions). Guarded by new `Spitter_OutOfBand_DoesNotCommitWindup` + `Spitter_Cornered_CommitsWindupPointBlank`.
- **[LOW] `EnemyProjectileDamageSystem`** now early-returns on `!ServerTick.IsValid` (sibling-system parity).
- **[LOW] bake-time Charger+Spitter guard** — `EnemyAuthoring.Bake` `Debug.LogError`s a prefab composing BOTH (it would match zero AI passes → never move).
- **[LOW] test quality** — the Spitter brain test now fires from **Expedition** (a dropped `Region` copy = 0 = Base would fail it, killing the `Base==0` false-green); the overstated "proves the partition" claim is replaced by a **direct** partition-exclusion test (`Spitter_IsExcludedFromGruntAndChargerPasses`).
**Accepted (documented, not defects):** the `MaxLiveProjectiles` soft-cap is GLOBAL across regions (24 generous; per-region deferred); the player-dealt-hit punch keys on the enemy-`Health` edge per the locked design (a teammate's hit nudges your camera a touch in co-op — a future local-attribution gate). See the Deferred list above.