Files
Project-M/Docs/Vault/07_Sessions/2026/2026-05-31_M2_Combat.md
T
2026-05-31 21:35:12 -07:00

8.5 KiB
Raw Blame History

date, type, tags, permalink
date type tags permalink
2026-05-31 session
session
dots
netcode
m2
combat
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 (wrapperCodePathScripts/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 → ProjectileMoveSystemProjectileDamageSystem (swept hit) → DamageEventHealthApplyDamageSystemdummy 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

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 + ~3040s 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.