Table of Contents

Class EcaAuthority

Namespace
FishMMO.Shared.Core
Assembly
FishMMO.Shared.dll

The single answer to "may this ECA step change authoritative state, here, on this peer?".

public static class EcaAuthority
Inheritance
EcaAuthority
Inherited Members

Remarks

Why a helper and not a field test. Every ECA action used to decide this for itself, and the two things they reached for were both wrong. #if UNITY_SERVER is a build-target define, so it is undefined in the editor the scene server is developed in. Gating on TickEventData.IsReplicateTick is worse, because the server's own ability spawn and self-target dispatches carry replicate ticks — so a guard meant to suppress a client suppressed the server as well and the effect never happened anywhere. Both mistakes are invisible in a build and only show up as "the ability does nothing".

What it asks. The identities the event carries, in the order they are most likely to be networked: the explicit initiator, the event's initiator, then the event's target character. The first one with a live FishNet.Object.NetworkObject decides, by way of FishNet.Object.NetworkObject.IsServerInitialized — the same question BaseAction.IsServer asks, so a client-hosted process answers for the peer the character actually belongs to rather than for the process.

What it does when nothing is networked. It allows. A trigger fired by a scene object, an edit-mode test, or a character that has not spawned yet has no peer to be wrong about; refusing there would silently disable every scene-authored trigger in the project. On a real client the characters involved always carry a NetworkObject, and that object reports IsServerInitialized == false, so the case this gate exists for is always decided by evidence rather than by the fallback.

Methods

Allows(PeerEvidence)

The gate's decision, as a pure function of the evidence. Split out so the rule can be tested without a NetworkManager, and so every caller shares one interpretation of "undecidable".

public static bool Allows(EcaAuthority.PeerEvidence evidence)

Parameters

evidence EcaAuthority.PeerEvidence

What the event proved about the executing peer.

Returns

bool

True when authoritative mutation is permitted.

Evidence(ICharacter, EventData)

Resolves what the event can prove about the peer it is running on.

public static EcaAuthority.PeerEvidence Evidence(ICharacter initiator, EventData eventData)

Parameters

initiator ICharacter

The action's explicit initiator, or null.

eventData EventData

The event being executed, or null.

Returns

EcaAuthority.PeerEvidence

The strongest evidence available.

IsServer(EventData)

True when this peer may mutate authoritative state for the supplied event.

public static bool IsServer(EventData eventData)

Parameters

eventData EventData

The event being executed, or null.

Returns

bool

IsServer(ICharacter, EventData)

True when this peer may mutate authoritative state for the supplied event.

public static bool IsServer(ICharacter initiator, EventData eventData)

Parameters

initiator ICharacter

The action's explicit initiator, or null.

eventData EventData

The event being executed, or null.

Returns

bool

MayPredict(EventData)

True when this peer may apply an effect for feedback. See MayPredict(ICharacter, EventData).

public static bool MayPredict(EventData eventData)

Parameters

eventData EventData

The event being executed, or null.

Returns

bool

MayPredict(ICharacter, EventData)

True when this peer may apply an effect for FEEDBACK, ahead of the server agreeing.

public static bool MayPredict(ICharacter initiator, EventData eventData)

Parameters

initiator ICharacter

The action's explicit initiator, or null.

eventData EventData

The event being executed, or null.

Returns

bool

Remarks

Why this is separate from IsServer(ICharacter, EventData). That gate asks "may I change authoritative state", and its answer has to stay no on a client. But using it for everything meant a player's own hit produced nothing locally until the server's report arrived — hit, pause, then the world reacts. In a predicted client that is not acceptable, and it is not what the gate was protecting against: the risk was a client inventing effects for characters it has no business simulating, not a caster showing the result of its own cast.

Only the initiator's owner predicts. The server always may. A client may only when it owns the character that CAUSED the effect — it has that character's input, it predicted the cast, and it is the only peer with anything to predict from. A mere observer answers false: it has no input stream for the caster, holds every peer interpolated, and the server is going to tell it what happened anyway.

What makes this safe. The authoritative consequences of a predicted effect are already gated one level down, inside the controllers: CharacterDamageController.Kill, QueueCombatEvent and RecordCombatContribution each return early unless IsServerStarted. So a predicted hit moves a bar and raises local events; it cannot kill anybody, award loot rights, or emit a combat report. Death in particular stays server-only and unpredicted by deliberate decision — a corpse that stands back up is far worse than one that arrives an RTT late.

An action whose effect is not player-visible — threat, taunt, revive — has nothing to gain here and must keep using IsServer(ICharacter, EventData). Predicting those buys no feel and adds a way to be wrong.