Skip to content

Fix desync when cancelling a gravship launch - #966

Merged
notfood merged 3 commits into
rwmt:masterfrom
cmlee119:fix/gravship-launch-cancel-currentmap-desync
Aug 3, 2026
Merged

Fix desync when cancelling a gravship launch#966
notfood merged 3 commits into
rwmt:masterfrom
cmlee119:fix/gravship-launch-cancel-currentmap-desync

Conversation

@cmlee119

@cmlee119 cmlee119 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #965.

Problem

Cancelling the gravship prelaunch confirmation desynced peers whose camera was on a different map. PatchGravshipPreLaunchCancel.Postfix closes the session with:

GravshipTravelUtils.CloseSessionAt(Find.CurrentMap.Tile);

Cancelling runs as a synced command on every peer, but Find.CurrentMap was not the same value on every peer.

Why Find.CurrentMap differed

Map commands are already deterministic about this — AsyncTimeComp.ExecuteCmd sets Current.Game.currentMapIndex = map.Index before running them. The cancel command simply was not a map command:

  • vanilla's cancel callback is delegate { }, a capture-less lambda, so SyncDelegate.Lambda(..., 4) had no map-bound object to serialize;
  • SyncMethod.DoSync therefore fell back to mapId = ScheduledCommand.Global;
  • world commands run through AsyncWorldTimeComp.ExecuteCmd, which never touches currentMapIndex.

So Find.CurrentMap stayed whatever map each peer's camera happened to be on, CloseSessionAt was 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.

  • Launching and cancelling from the host and from clients, with cameras on the gravship map and on other maps — no desync in any of the combinations tried.
  • Three gravships with three concurrent GravshipTravelSessions open — cancelling one never closed the others.
  • Source/Client builds 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.Apply is 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).

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>
@notfood

notfood commented Jul 27, 2026

Copy link
Copy Markdown
Member

If there are two sessions active (multifaction), it'll close both. The real issue is that cancel action is missing .SetContext(SyncContext.MapSelected) to make Find.CurrentMap give the correct map.

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>
@cmlee119

cmlee119 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@notfood Changed it as you suggested — the cancel lambda is now registered with SetContext(SyncContext.MapSelected), and the CloseAllSessions() helper is gone. The PR description has been updated accordingly.

Retested with 3 players (1 host + 2 clients, async time + multifaction, Odyssey, no third-party mods):

  • Three gravships with three concurrent GravshipTravelSessions open — cancelling one never closed the others.
  • Launching and cancelling from the host and from the clients, with cameras on the gravship map and on other maps — no desync in any of the combinations I tried.

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.

@notfood notfood added fix Fixes for a bug or desync. 1.6 Fixes or bugs relating to 1.6 (Not Odyssey). odyssey Fix or bugs relating to Odyssey (Not 1.6) labels Aug 3, 2026
@notfood notfood moved this to In review in 1.6 and Odyssey Aug 3, 2026
Removed comments explaining the cancel callback behavior. It's self explanatory.
@notfood
notfood merged commit b4548a9 into rwmt:master Aug 3, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from In review to Done in 1.6 and Odyssey Aug 3, 2026
@notfood

notfood commented Aug 3, 2026

Copy link
Copy Markdown
Member

Damn, commited to master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.6 Fixes or bugs relating to 1.6 (Not Odyssey). fix Fixes for a bug or desync. odyssey Fix or bugs relating to Odyssey (Not 1.6)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Desync when cancelling a gravship launch (CloseSessionAt uses Find.CurrentMap)

2 participants