10 KiB
date, type, tags, permalink
| date | type | tags | permalink | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 2026-06-09 | session |
|
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 theDevTelemetryReadoutstatic;DebugOverlayrenders the live counters.- All four counters now wired:
DashIFrameNegatedHits(HealthApplyDamageSystem, per negated event) ·DashesWasted(DashSystem window-close edge, via a newDashState.NegatedCountserver-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 newLungeState.StaggerUntilTickwindow, scored once per window by zeroing on first punish so punishes:windows ≤ 1).
MC-1 — dash
DamageEvent.SourceTick(non-replicated) stamped viaTickUtil.NonZeroat 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.DashInputEvent (Firetwin, 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 (GroundedMovementSharpness15→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,NetworkTickcomparisons only (wrap-safe).PlayerDeathStateSystemclears the window + restores sharpness on death. - Dash juice in
CombatFeedbackSystem: dash whoosh SFX + afterimage burst + camera shake + FOV punch on theDashCooldown.NextTickedge (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 inFeelConfig(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).EnemyAISystemCharger pass (Grunt pass excludes via.WithNone<LungeState>()): 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.prefabvia a newEnemyRigTools"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), atlasPolygonScifi_01_A+ red tint (danger read), RootY −1.0. Added as the 4thWaveDirector.EnemyPrefabsround-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
ComponentSystemSortercycle, 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 → clientDevTelemetryReadout→ 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:
- MAJOR — the dash override lacked the StartTick lower bound.
DashStateis 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-onlyiFrameActivestomped 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 byRollback_ReSimulated_PreDash_Tick_Gets_No_Override. - 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)
- Input = direct device reads (
leftShift/buttonEastwasPressedThisFramein PlayerInputGatherSystem), NOT a.inputactionsDash action — kills the only focused-editor step (wrapper regen). Migrate to the action map later if rebinding UI ever needs it. - Per-pass consts for the Charger windup (30 ticks in the Charger foreach) instead of an
EnemyStats.WindupTicksfield — same effect (Grunt feel untouched), one field less; promote to per-prefab/live-singleton when tuning demands it. LungeState.StaggerUntilTickadded for punish scoring (the spec inferred the window fromEnemyAttackCooldown, 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