Skip to content

Multiplayer: all interaction submissions rejected — ClientMessage has no Interaction variant (live since v0.42.0) #6941

Description

@matthewevans

Summary

Every interaction submission over the WebSocket transport fails. The client sends a
ClientMessage variant the server does not define, so the server rejects the frame at
deserialization — before any game logic runs.

User-visible error (from the reporter's screenshot):

Invalid message: unknown variant `Interaction`, expected one of `ClientHello`, `CreateGame`,
`JoinGame`, `Action`, `PreviewManaPayment`, `Reconnect`, `AbandonGame`, ... `CancelTakeback`
at line 1 column 21

The enumerated list in that message is exactly the current ClientMessage variant set.

Root cause

Side Evidence
Client sends it client/src/adapter/ws-adapter.ts:764this.send({ type: "Interaction", data: { submission } }) in submitInteraction()
Draft path too client/src/adapter/server-draft-adapter.ts:218 — identical send
Server lacks the variant crates/server-core/src/protocol.rs:135pub enum ClientMessage has 26 variants, none named Interaction
Server lacks the handler grep submit_interaction crates/server-core/ → zero hits. session.rs:11 imports only bind_interaction_authority; the crate only ever derives viewer_interaction outbound

Provenance

Introduced by a8766823cd — "fix(client): authorize attachment interaction fan (#6778)",
merged 2026-07-29. git show --stat a8766823cd -- crates/server-core is empty: that PR
wired the engine, WASM, and client halves but never the server half.

Blast radius

  • Single-player / P2P: unaffectedwasm-adapter.ts:815 and p2p-adapter.ts:1697-1698
    dispatch to wasm.submitInteraction and never cross the wire.
  • WebSocket multiplayer: every interaction submission fails. The attachment fan on any
    equipped or enchanted permanent is the most visible surface, which is why it was reported
    as an equipment bug. The failure is at the transport layer, so it is not equipment-specific.
  • Server drafts: same defect via server-draft-adapter.ts:218.

Affected releases

git merge-base --is-ancestor a8766823cd <tag>:

Release Date Affected
v0.41.0 2026-07-30 clean
v0.42.0 2026-07-31 first affected
v0.43.0 2026-08-02 affected (current)

git show origin/main:crates/server-core/src/protocol.rs | grep -c "^ Interaction {"0.
Still present on main.

Fix

The client envelope is already correct — ClientMessage is #[serde(tag = "type", content = "data")]
(protocol.rs:134), matching the Action / PreviewManaPayment convention. No client change needed.

  1. Add Interaction { submission: engine::types::interaction::InteractionSubmission } to ClientMessage.
  2. Handle it in the session loop alongside Action, calling
    engine::game::interaction::submit_interaction(state, actor, submission). Derive the actor from the
    authenticated session, per the convention documented on PreviewManaPayment (protocol.rs:154-155):
    "The authenticated session, rather than the client, determines the actor."
    Mirror the WASM path at engine-wasm/src/lib.rs:1142-1152; see
    crates/engine/tests/integration/interaction_contract.rs for the stale/unauthorized coverage.
  3. Reply on the channel ws-adapter.ts:761-768 awaits, and broadcast state as Action does.

Follow-up (separate issue)

Nothing binds the TS message-type string literals to the Rust ClientMessage variants at build
time, and scripts/check-protocol-version.mjs did not catch this. A generated-constants or
parity-test guard would prevent the whole class.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions