--- date: 2026-05-31 type: session tags: [session, dots, netcode, m2, combat] permalink: gamevault/07-sessions/2026/2026-05-31-m2-combat --- # Session 2026-05-31 — M2 Combat Foundation ## Goal Build **M2 — Combat**: directional ability fire (predicted projectile), deterministic soft auto-target, server-authoritative health/damage, and a training dummy to shoot — plus migrate input to the new Unity Input System action map. Strict, foundation-grade pass. ## Done ### Architecture locked — see [[DR-003_M2_Combat_Netcode_Architecture]] Predicted projectile ghosts (client predict-spawn + classification by `SpawnId`); server-authoritative auto-target at the fire tick (reconciled via `[GhostField] Projectile.Direction`); server-only damage (`Health.Current` `[GhostField]`); swept hit detection. Fire is a netcode `InputEvent`. ### Built — 26 files, compiles clean, runtime-validated on 6.4.7 - **Input migration:** added `Aim` (Vector2) + `Fire` (Button) to `Project M Input.inputactions`; retargeted the generated wrapper into the `ProjectM.Client` asmdef (`wrapperCodePath` → `Scripts/Client/Input/ProjectMInput.cs`) so client systems can reference it; rewrote `PlayerInputGatherSystem` as a managed `SystemBase` reading the wrapper (`Fire.Set()` on press edge). - **Simulation:** `Health`, `HitRadius`, `DamageEvent` (buffer), `AbilityStats`, `AbilityCooldown` (`[GhostField]`), `Projectile` (`[GhostField]` Direction+SpawnId), `ProjectileSpawner`, `TrainingDummyTag`, `TrainingDummySpawner`; systems `AutoTarget` (static), `AbilityFireSystem` (predicted, `IsFirstTimeFullyPredictingTick`-gated, server-branch auto-target), `ProjectileMoveSystem` (predicted). - **Client:** `ProjectileClassificationSystem` (predicted-spawn classifier; **non-Burst** — see DR-003). - **Server:** `ProjectileDamageSystem` (swept hit), `HealthApplyDamageSystem`, `TrainingDummySpawnSystem` (spawns 3 dummies, self-disables). - **Authoring/assets:** `Projectile.prefab` (OwnerPredicted ghost), `TrainingDummy.prefab` (Interpolated ghost), `PlayerAuthoring` bakes Health/AbilityStats/AbilityCooldown/DamageEvent; both spawners wired into `Gameplay.unity`; `Application.runInBackground` enabled (M1 follow-up). - **Tests:** `AutoTargetTests`, `ProjectileMoveSystemTests`, `HealthApplyDamageSystemTests`, `ProjectileDamageSystemTests` (incl. tunnelling regression) — **EditMode 22/22 green**. ### Runtime validation (Play Mode, in-editor, `execute_code` inspection) - connect → server spawns 3 dummies (HP 60) → **replicated to client** (both worlds show 3 dummies); player ghost spawns with Health 100 / AbilityStats / AbilityCooldown. - Server-injected projectile → `ProjectileMoveSystem` → `ProjectileDamageSystem` (swept hit) → `DamageEvent` → `HealthApplyDamageSystem` → **dummy HP 60→40**, and the drop **replicated server→client** via the `Health` `[GhostField]`. - Bug caught + fixed at runtime: a fast (speed-25) projectile tunnelled the point hit-check; the swept fix made it hit. Slow (speed-3) hit before the fix. Regression test added. ### Method Orchestrated authoring + 3-lens adversarial review of all 25 logic files via a background workflow against a frozen build contract; applied in compile-gated clusters; every volatile Netcode 1.13.2 API verified via `unity_reflect`/context7 + the official ECS samples before coding. ## Decisions - [[DR-003_M2_Combat_Netcode_Architecture]] — predicted projectiles, server auto-target/damage, swept hits, non-Burst classifier. ## Open / deferred - **ENVIRONMENT (action needed): restart the Unity editor.** A Burst *internal compiler error* (from the first compile, since fixed by de-Bursting the classifier) corrupted the Burst incremental cache → every newly-added `[BurstCompile]` entry point (the 5 combat systems + the generated Health/AbilityCooldown/Projectile ghost serializers) logs "not a known Burst entry point" and runs managed-fallback (slow → server tick-batching + ~30–40s play-enter). Code is correct (clean compile + 22/22 tests + runtime damage/replication all work). A **fresh editor launch** (or `Library/BurstCache` wipe while closed) should clear it; re-confirm the console is clean on the next warm play. - **Live interactive fire not yet validated:** the Input System ignores injected device input while the Game view is unfocused, so the input→`AbilityFireSystem`→predicted-spawn→classification path was validated *structurally* (compiles, instantiates, mirrors the verified sample) but not by a real keypress. **Operator test:** focus the editor in Play Mode, press Space / left-click / RT → expect your predicted projectile + dummy HP drop. - Deferred (revisit at trigger): mouse-cursor aim for KBM (needs camera ground-ray rig); player death/respawn (currently HP clamps ≥0, dummies despawn); predicted auto-target if the soft server reconcile ever feels off; projectile/dummy visual polish (currently primitive meshes). ## Addendum (same session) — input-validation tooling + prototype visuals **Headless input interaction (for validation).** The Unity Input System ignores device input while the Game view is unfocused, which blocked headless fire/move validation. Three options were weighed; focus-switching CLI was rejected (fragile, intrusive). Enabled/built: - `InputSettings.editorInputBehaviorInPlayMode = AllDeviceInputAlwaysGoesToGameView` (+ `runInBackground`) so injected/real input reaches the unfocused game. - `DebugInputInjectionSystem` (`#if UNITY_EDITOR`, `ProjectM.Client`): runs after the real gather; static pokes (`Fire()`, `SetMove`, `SetAim`, `Stop()`) drive the local player's `PlayerInput`, exercising the authentic command→prediction pipeline (not a shortcut). Cross-platform (pure C#). **Validated: `SetMove(2,0)` drove the player to x≈101 on server, replicated to client** — proving the full input→command→server→sim→replication path (and the tool) works for continuous input. **Finding — one-shot fire vs tick-batching.** Driving the `Fire` `InputEvent` via the hook did **not** advance `AbilityCooldown.NextFireTick` (AbilityFireSystem never fired), while the server was **tick-batching** (the Burst-cache degradation). Continuous values (Move) survive batching; one-shot events are exactly what Unity's tick-batch warning says gets lost. **Action:** validate fire on a *healthy* (post-editor-restart) editor — re-run the hook (now upgraded to hold Fire across ~10 frames for reliable propagation) or press the key in a focused Game view. If it still fails when healthy, switch AbilityFireSystem's `input.Fire.IsSet` gate to the buffered `.Count` read the HelloNetcode sample uses. **Prototype visuals.** `PrototypeCameraRig` (MonoBehaviour on Main Camera, `ProjectM.Client`): player-following, fully tunable (pitch/yaw/distance/FOV/ortho), default **mid 3/4 ~45° perspective** (V Rising / D4 feel) — operator-chosen. Bright URP-Lit materials: player = cyan, dummies = red, projectiles = yellow, ground = dark grey; added a ground plane to `SampleScene`. Screenshot confirms framing + colors: `Assets/Screenshots/M2_prototype_view.png`. **Post-restart verification (2026-05-31).** Operator restarted Unity (Burst cache cleared — the "not a known Burst entry point" flood is gone) and relaunched the Local Reference editor. **Fire now validated end-to-end**: via `DebugInputInjectionSystem.Fire()`, `AbilityFireSystem` fired (`NextFireTick` advanced) and the dummy went **60→20 HP on both server and client** — input→fire→predicted-projectile→swept-hit→damage→replication all confirmed live. The earlier fire failure **was** the Burst-degraded tick-batching dropping the one-shot `InputEvent`. **Projectile ghost-map errors** the operator saw are **not a code bug** — they appear only under **server tick-batching** (predicted-spawn reconciliation races); a clean single fire produces zero ghost errors. Root cause of the persistent batching: **two Unity editors running at once** (Project M + Local Reference) starving Project M's server — close the reference editor (or keep Project M focused) for clean netcode. **Fixed:** `PrototypeCameraRig` startup job-safety bug (it queried `EntityManager` from LateUpdate during subscene load) — now an ECS `PrototypeCameraTargetSystem` publishes the player position to the camera. **M2 marked ✅ Done.** ## Next **M3 — Data-driven abilities & modifiers** (new, slotted before co-op): ability + character stats in ScriptableObjects → baked blob assets, runtime flat/% modifier stacks → effective stats — design in [[Data_Driven_Abilities]]. Then **M4 — Co-op** over Unity Relay per [[Milestones]].