Fix desync when cancelling a gravship launch - #966
Conversation
The prelaunch-cancel handler closed the GravshipTravelSession with
CloseSessionAt(Find.CurrentMap.Tile). Cancelling runs as a synced command
on every peer, but Find.CurrentMap is the map each peer's camera is on -
local UI state, not synchronized simulation state. A peer whose camera is
on a different map closes the wrong tile and never closes the gravship
session, so that map stays paused only for them. The per-map tick counts
then diverge and the game desyncs ("Map instances don't match" /
"Wrong random state on map N").
Close the session by looking it up instead of by the local camera. Of the
four CloseSessionAt call sites, this was the only one using Find.CurrentMap;
the others already pass deterministic tiles (curTile/landingTile/takeoffTile).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
If there are two sessions active (multifaction), it'll close both. The real issue is that cancel action is missing |
The cancel callback is a capture-less lambda, so its SyncDelegate has no
map-bound object to serialize and DoSync falls back to
mapId = ScheduledCommand.Global. World commands are executed by
AsyncWorldTimeComp.ExecuteCmd which, unlike AsyncTimeComp.ExecuteCmd, never
sets Current.Game.currentMapIndex. Find.CurrentMap therefore stayed whatever
map each peer's camera happened to be on, and the handler's
CloseSessionAt(Find.CurrentMap.Tile) closed the GravshipTravelSession on some
peers but not others. The peers that missed it kept that map paused, the
per-map tick counts diverged, and the game desynced ("Map instances don't
match" / "Wrong random state on map N").
SetContext(SyncContext.MapSelected) makes the command carry the selection,
which binds it to that map, so every peer executes the cancel in the same map
context and closes the same session.
This replaces the CloseAllSessions() approach from the previous commit, which
closed every open GravshipTravelSession and would have closed another
faction's session in multifaction games. Thanks @notfood for catching it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@notfood Changed it as you suggested — the cancel lambda is now registered with Retested with 3 players (1 host + 2 clients, async time + multifaction, Odyssey, no third-party mods):
One thing that is still open: the launch confirmation dialog itself opens for every faction, not just the one that owns the gravship. That is a separate problem, so I will file it as its own issue and send a PR for it rather than widening this one. |
Removed comments explaining the cancel callback behavior. It's self explanatory.
|
Damn, commited to master. |
Fixes #965.
Problem
Cancelling the gravship prelaunch confirmation desynced peers whose camera was on a different map.
PatchGravshipPreLaunchCancel.Postfixcloses the session with:Cancelling runs as a synced command on every peer, but
Find.CurrentMapwas not the same value on every peer.Why
Find.CurrentMapdifferedMap commands are already deterministic about this —
AsyncTimeComp.ExecuteCmdsetsCurrent.Game.currentMapIndex = map.Indexbefore running them. The cancel command simply was not a map command:delegate { }, a capture-less lambda, soSyncDelegate.Lambda(..., 4)had no map-bound object to serialize;SyncMethod.DoSynctherefore fell back tomapId = ScheduledCommand.Global;AsyncWorldTimeComp.ExecuteCmd, which never touchescurrentMapIndex.So
Find.CurrentMapstayed whatever map each peer's camera happened to be on,CloseSessionAtwas called with a different tile per peer, and peers that missed it kept the gravship map paused. The per-map tick counts then diverged and the game desynced (Map instances don't match/Wrong random state on map N).Fix
Register the cancel lambda with
SetContext(SyncContext.MapSelected)so the command carries the selection and is bound to that map. Every peer then executes the cancel in the same map context and closes the same session.One line; no effect outside multiplayer.
Testing
3 players (1 host + 2 clients), async time + multifaction, Odyssey, no third-party mods.
GravshipTravelSessions open — cancelling one never closed the others.Source/Clientbuilds clean (Release).Known limitation (separate issue)
The launch confirmation dialog itself still opens for every faction, not just the one that owns the gravship —
RitualOutcomeEffectWorker_GravshipLaunch.Applyis simulation code, so the dialog is created on every peer. That is a separate problem from this desync; I will file it as its own issue and send a PR for it.Investigated and written with the help of Claude (Anthropic).