6.5 KiB
Context
The client currently has the full MVP wire contract for CombatEvent, but the Unity receive path stops at PlayerState. NetworkManager registers login, join, logout, heartbeat, and PlayerState handlers, while Player and PlayerUI only expose authoritative snapshot data that came from PlayerState. As a result, local muzzle flash can exist as cosmetic presentation, but server-truth combat outcomes such as damage, death, or ShootRejected are not surfaced on the client at all.
This step sits after authoritative player-state ownership and remote interpolation are already in place. The change must reuse that ownership model instead of inventing a second source of truth for HP or death, and it must avoid pulling future server-combat implementation details into the client-side receive/apply design. Shared networking contracts under Assets/Scripts/Network/ already define the message and lane policy, so this change should stay on the Unity client side.
Goals / Non-Goals
Goals:
- Register and route authoritative
CombatEventmessages through the existing client receive path. - Define one explicit client-side application point for authoritative combat outcomes so hit, damage, death, and
ShootRejectedare not handled ad hoc across unrelated presentation scripts. - Let the client update owned authoritative player presentation state from server-confirmed combat results while keeping local firing FX cosmetic.
- Expose lightweight combat-result visibility in existing UI or diagnostics so MVP playtests can observe accepted damage, death, and rejected shots.
Non-Goals:
- Implement server-authoritative combat resolution or decide how the server emits each event.
- Redesign shared protobuf schemas, message lanes, or stale-drop rules for reliable gameplay events.
- Add speculative client-side rollback, lag compensation, or remote combat prediction.
- Replace
PlayerStateas the longer-lived authoritative snapshot source for movement and periodic HP correction.
Decisions
Decision: Route client combat events through NetworkManager -> MasterManager -> Player
NetworkManager will register a CombatEvent handler alongside PlayerState, then forward parsed events into player-owned presentation logic through MasterManager. MasterManager remains the player lookup boundary, and Player becomes the point that decides how a given authoritative combat result affects its owned state and presentation.
Why this approach:
- It matches the receive path already used for authoritative
PlayerState. - It keeps endpoint lookup and Unity object ownership out of
NetworkManager. - It avoids spreading combat-result handling across UI and movement components directly.
Alternative considered:
- Let
NetworkManagerupdate UI or scene objects directly. Rejected because it would bypass the current player ownership model and make later combat presentation harder to reason about.
Decision: Keep PlayerState as the base authoritative snapshot, but allow authoritative combat-result deltas to update client-owned presentation state between snapshots
The client already owns one authoritative snapshot per player. CombatEvent handling will extend that player-owned state with authoritative combat-result application, such as subtracting confirmed damage from the current authoritative HP view, marking death state, or recording last combat-result metadata for presentation. The next accepted PlayerState remains allowed to refresh the full snapshot and correct any drift.
Why this approach:
CombatEventis itself authoritative server output, so applying it on the client does not create speculative gameplay truth.- It gives the client immediate combat-result feedback without waiting for the next sync-lane
PlayerState. - It preserves the existing rule that periodic
PlayerStatesnapshots can refresh the full authoritative state.
Alternative considered:
- Wait for
PlayerStateonly and useCombatEventfor cosmetic feedback. Rejected because TODO step 5 explicitly requires client combat-result handling that can update HP or death state from server truth.
Decision: Separate local fire FX from authoritative combat-result presentation
The controlled player may continue to play local fire FX immediately, but CombatEvent handling will own authoritative damage, death, hit feedback, and rejection feedback. ShootRejected will be surfaced as a client-visible authoritative result without introducing rollback of predicted movement or speculative state rewrites.
Why this approach:
- It preserves the MVP rule that cosmetic preplay is allowed, but gameplay truth is not.
- It keeps this step focused on receive/apply behavior rather than prediction rollback mechanics.
Alternative considered:
- Roll back or cancel local fire presentation on
ShootRejected. Rejected because the MVP only requires authoritative visibility of rejection, not a full local combat preplay rollback system.
Decision: Use lightweight player UI or diagnostics for development-time combat visibility
Existing MVP diagnostics such as PlayerUI should expose enough combat-result state to make damage, death, and ShootRejected visible during playtests. This should stay lightweight: a last combat-event line, death indicator, or similar output is enough.
Why this approach:
- It keeps observability close to the player object already showing authoritative HP.
- It avoids introducing a larger diagnostics framework for one MVP step.
Alternative considered:
- Defer visibility until end-to-end server combat exists. Rejected because the TODO step explicitly calls for combat-result visibility during development.
Risks / Trade-offs
- [Applying
CombatEvent.damageon top of the latest snapshot can be refreshed again by a laterPlayerState] → Mitigation: treatCombatEventas an immediate authoritative delta for presentation and let the next acceptedPlayerStateremain the full-state correction path. - [A combat event may reference an attacker or target player that is not spawned locally] → Mitigation: route events through
MasterManagerand ignore or log missing-player references instead of throwing. - [UI can accidentally become the owner of combat truth] → Mitigation: keep combat-result application in
Playeror a player-owned helper, and let UI only read derived authoritative state/diagnostic values. - [
ShootRejectedhandling could grow into a rollback system] → Mitigation: scope this step to surfacing rejection and keeping local fire FX cosmetic rather than retroactively undoing unrelated local state.