--- date: 2026-06-09 type: session tags: - session - combat - mc-0 - mc-1 - netcode - dash - charger permalink: gamevault/07-sessions/2026/2026-06-09-mc1-implementation --- # MC-0 + MC-1 implementation — dash, Charger, telemetry (code-complete; fun-gate pending) > Implements [[2026-06-09_MC1_Build_Spec]] (the mandatory pre-code adversarial review's output). Direction: [[Path_to_Fun]] · locks: [[DR-029_Path_A_Fork_Locks]]. **Status: all code + tests + structural Play-validation done; the MC-1 FUN-GATE (feel pass + bench + friend read) is the open operator item — MC-1 is NOT "done" until it passes.** ## What was built (the full uncommitted slice) ### MC-0 — instrument the box - `DevTelemetry` (Simulation/Debug) — server-only singleton with the four fun-gate counters + LiveEnemyCount/LastSampleTick proof-of-life. `DebugTelemetryReport : IRpcCommand` — **unconditional wire type** (RpcCollection hash parity); only the systems are `#if UNITY_EDITOR`. - `DevTelemetrySystem` (Server) — ensures the singleton, samples each tick, ships the report to every connection every 15 ticks. `DevTelemetryReceiveSystem` (Client) — drains the RPC into the `DevTelemetryReadout` static; `DebugOverlay` renders the live counters. - **All four counters now wired**: `DashIFrameNegatedHits` (HealthApplyDamageSystem, per negated event) · `DashesWasted` (DashSystem window-close edge, via a new `DashState.NegatedCount` server-written field; close-edge is **server-gated on the singleton** so the client's DashState is never zeroed mid-rollback) · `ChargerWhiffWindowsOpened` (EnemyAISystem, both whiff sites) · `ChargerWhiffPunishesLanded` (HealthApplyDamageSystem: player-sourced hit inside a new `LungeState.StaggerUntilTick` window, **scored once per window** by zeroing on first punish so punishes:windows ≤ 1). ### MC-1 — dash - `DamageEvent.SourceTick` (non-replicated) stamped via `TickUtil.NonZero` at all THREE append sites (EnemyAISystem melee, ProjectileDamageSystem, TurretFireSystem); `SourceTick==0` = unstamped = never i-framed (fail-safe). - `DashState` (predicted, non-replicated, re-simulated from input) + `DashCooldown{[GhostField] NextTick}` — baked on the player via PlayerAuthoring. `PlayerInput.Dash` InputEvent (`Fire` twin, command-hash churn only — no ghost re-bake). - `DashSystem` (predicted, `[UpdateAfter(PlayerControlSystem)]`, Bursted): idempotent start (press + cooldown-ready + not-in-window), HALF-OPEN i-frame window `[StartTick, IFrameUntilTick)`, recovery tail (movement locked, no i-frames), **sharpness-override blink** (`GroundedMovementSharpness` 15→200 for the window — NO CharacterProcessor edit, fully headless, exactly as the spec's confirmed correction predicted). - Negation in `HealthApplyDamageSystem`: per-element (not whole-buffer), half-open, `NetworkTick` comparisons only (wrap-safe). `PlayerDeathStateSystem` clears the window + restores sharpness on death. - **Dash juice** in `CombatFeedbackSystem`: dash whoosh SFX + afterimage burst + camera shake + FOV punch on the `DashCooldown.NextTick` edge (muzzle-flash pattern); i-frame shimmer trail each frame the local window is active; **local hit-feedback suppressed during the local i-frame window** (masks the documented prediction-reconciliation Health flicker). All knobs in `FeelConfig` (Feature 5 block, live-pokeable). ### MC-1 — Charger - `LungeState` (server-only; **component-presence is the discriminator** — no enum in the Bursted system) + `ChargerAuthoring` (composes WITH EnemyAuthoring on the prefab; bakes only LungeState). - `EnemyAISystem` Charger pass (Grunt pass excludes via `.WithNone()`): precedence **knockback (cancels lunge) > lunge-active (SweptMove travel, contact damage, wall-stop + overshoot whiff → stagger) > seek > commit** — commit locks Dir at windup-elapse and **fires even if the target left range** (the punishable tell). Charger windup 30 ticks / lunge 16 u/s / 18 ticks / stagger 36 ticks (per-pass consts). - **Prefab chain**: `EnemyCharger.prefab` (capsule template: Enemy.prefab duplicate + ChargerAuthoring + tuned stats HP 45 / spd 2.6 / dmg 14 / cd 48 / scale 1.0) → `EnemyChargerMuscle.prefab` via a new **`EnemyRigTools` "Build Charger (MC-1)"** menu item (builds ONLY the charger so the committed Werewolf/Kaiju outputs aren't re-serialized). Model: **SM_Chr_Muscle_Male_01** (PolygonSciFiCity — the [[Synty_Asset_Inventory]] "verified Generic rig, next-faction" path), atlas `PolygonScifi_01_A` + red tint (danger read), RootY −1.0. Added as the 4th `WaveDirector.EnemyPrefabs` round-robin entry in the Gameplay subscene (additive ghost — no re-hash of existing ghosts). ## Validation (gates 1 + 2 of three) - **EditMode: 259/259 green** (spec tests incl. half-open boundary, wraparound, per-element, SourceTick-0 fail-safe, idempotent start, cooldown gate, death-mid-dash cleanup, override ordering, Charger commit-out-of-range / whiff-stagger / knockback-cancel, 7 telemetry-counter tests + the post-review rollback-window regression). - **Play (real netcode session, server+client): zero console errors** — no `ComponentSystemSorter` cycle, no stale-Burst exception. DashState/DashCooldown baked in BOTH worlds; DashSystem sorts directly after PlayerControlSystem (the documented 1-tick fixed-step offset pattern). **Live E2E negation**: armed a window on the server player, appended in-window + out-of-window strikes → only the outside one applied (100→93), counter +1, close-edge cleanup ran. **Live Charger**: spawned from the baked pool → seek → 30-tick telegraph → lunge commit (speed 16) → contact damage → repeat; replicates to the client; killed the (stationary) player → death/respawn flow exercised. **Telemetry pipe**: server counters → RPC → client `DevTelemetryReadout` → overlay, values matching. Charger material values verified (AnimatedLitShader + SciFi atlas + red tint). - Six "wasted" dashes were counted live with `serverCd == clientCd` — real input-driven dash starts replicated consistently (and wasted-counting works). ## Post-build adversarial review (4 lenses → 12 findings → 2 confirmed, both FIXED) A 29-agent review workflow (netcode/prediction · DOTS/Burst · spec-adherence · edge-cases, each finding adversarially refuted) ran over the full diff. Ten findings were refuted as true-but-mitigated (by design, world placement, an existing test, or the documented deviations). Two were confirmed and fixed in-session: 1. **MAJOR — the dash override lacked the StartTick lower bound.** `DashState` is non-replicated → NOT restored on prediction rollback; after a press at tick D the client re-simulates pre-dash ticks S..D−1 with the post-press window visible, and an upper-bound-only `iFrameActive` stomped dash velocity + sharpness onto ticks that never had them → **dash-start overshoot + snap-back under real latency** (editor RTT≈0 masked it; the negation had the lower bound, only the movement override didn't — the bug originated in the spec's §4 pseudocode). **Fix:** `inDashWindow = StartTick != 0 && !StartTick.IsNewerThan(serverTick)` now gates both override branches; pre-dash re-sim ticks fall to the restore branch. Pinned by `Rollback_ReSimulated_PreDash_Tick_Gets_No_Override`. 2. **MINOR — DashSystem vs HealthApplyDamageSystem were unordered** in the server's predicted group, so a same-tick teammate-projectile vs dash-start negation (`src == StartTick`; ProjectileDamageSystem appends and the drain runs the SAME tick) was an unconstrained sorter tiebreak. **Fix:** `[UpdateAfter(typeof(DashSystem))]` on HealthApplyDamageSystem (chains verified disjoint — no cycle; Play-validated: dash 13 → drain 14, clean world creation). Notable refuted-but-recorded items: the punish check compares the stagger window against the *drain* tick (correct today — the only `SourceNetworkId>=0` site drains same-tick; revisit if MC-4's cleave appends in the plain group) · the dash displacement consumes velocity one tick after the i-frame window (the documented OrderFirst 1-tick offset; the shimmer matches the true negation window) · the build-server never clears DashState at window-close (intentional — all readers are tick-guarded; pinned by `Without_Telemetry_Singleton_The_Close_Edge_Leaves_DashState_Intact`) · Charger lunge contact only tests the current nearest player (the spec'd + pre-existing Grunt targeting model; a co-op design note for later). (Process note: the first review run returned `{confirmed: []}` because all four agents died on a subagent session limit — an empty result that masquerades as a clean pass. Check the failures list; re-run after the reset via `resumeFromRunId`.) ## Deviations from the build spec (all deliberate) 1. **Input = direct device reads** (`leftShift` / `buttonEast` `wasPressedThisFrame` in PlayerInputGatherSystem), NOT a `.inputactions` Dash action — kills the only focused-editor step (wrapper regen). Migrate to the action map later if rebinding UI ever needs it. 2. **Per-pass consts for the Charger windup** (30 ticks in the Charger foreach) instead of an `EnemyStats.WindupTicks` field — same effect (Grunt feel untouched), one field less; promote to per-prefab/live-singleton when tuning demands it. 3. **`LungeState.StaggerUntilTick`** added for punish scoring (the spec inferred the window from `EnemyAttackCooldown`, which would conflate post-hit cooldown with stagger and let one window score many punishes). ## Open items (operator) - **The MC-1 fun-gate** (gate 3): focused-editor feel pass — dash SNAP test (sharpness 200 must read as a blink, else the documented CharacterProcessor fallback + Burst restart), Charger telegraph readability, RootY feet check on the Muscle rig, then the bench (timed vs spam ≥70% fewer hits) + a friend read at Demo A. Live whiff/punish numbers only emerge with a dodging player. - **TuningConfig live-singleton** (the spec's MC-0 blocker) still does not exist — dash/Charger knobs are baked consts (+ FeelConfig statics for presentation). Decide whether to land it before the tuning pass. - `Assets/_Recovery/0.unity` (2.2 MB, 2026-06-08 23:36) — an untracked Unity scene-recovery artifact; review + delete (with its .meta) if it holds nothing. - Wave-spawned Chargers appear only when a siege runs (player-driven pacing) — the round-robin makes 1 in 4 spawns a Charger. ## Links [[2026-06-09_MC1_Build_Spec]] · [[Path_to_Fun]] · [[DR-028_Combat_Primary_Verb_Depth_First]] · [[DR-029_Path_A_Fork_Locks]] · [[DR-023_Enemy_Animation_MonsterMash]] (rig pipeline) · [[Synty_Asset_Inventory]]