8.5 KiB
date, type, tags, permalink
| date | type | tags | permalink | |||||
|---|---|---|---|---|---|---|---|---|
| 2026-05-31 | session |
|
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) toProject M Input.inputactions; retargeted the generated wrapper into theProjectM.Clientasmdef (wrapperCodePath→Scripts/Client/Input/ProjectMInput.cs) so client systems can reference it; rewrotePlayerInputGatherSystemas a managedSystemBasereading the wrapper (Fire.Set()on press edge). - Simulation:
Health,HitRadius,DamageEvent(buffer),AbilityStats,AbilityCooldown([GhostField]),Projectile([GhostField]Direction+SpawnId),ProjectileSpawner,TrainingDummyTag,TrainingDummySpawner; systemsAutoTarget(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),PlayerAuthoringbakes Health/AbilityStats/AbilityCooldown/DamageEvent; both spawners wired intoGameplay.unity;Application.runInBackgroundenabled (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 theHealth[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 (orLibrary/BurstCachewipe 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'sPlayerInput, 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.