Skip to content

Isolate RNG in the gravship launch ritual's reachability check#968

Open
cmlee119 wants to merge 1 commit into
rwmt:masterfrom
cmlee119:fix/gravship-launch-role-check-rand-desync
Open

Isolate RNG in the gravship launch ritual's reachability check#968
cmlee119 wants to merge 1 commit into
rwmt:masterfrom
cmlee119:fix/gravship-launch-role-check-rand-desync

Conversation

@cmlee119

Copy link
Copy Markdown

Fixes #967.

Problem

Building the gravship launch confirmation dialog decides which pawns can board, in Dialog_BeginRitual's constructor:

Dialog_BeginRitual..ctor -> CreateRitualRoleAssignments
  -> RitualRoleAssignments.PawnNotAssignableReason
  -> RitualBehaviorWorker_GravshipLaunch.PawnCanFillRole
  -> CanReachGravship -> GravshipUtility.TryFindSpotOnGravship
  -> Region.RandomCell -> Rand.Range        // consumes RNG

Building the dialog is UI work opened only by the issuing peer (the currentExecutingCmdIssuedBySelf gate in CancelDialogBeginRitual), so this RNG runs on just one peer and advances the shared stream there only. Spamming the launch button repeats it and the divergence accumulates into a desync (Random state from commands doesn't match). Measured: with nothing ticking, the gravship map alone was off by +11,617 rand iterations, all on the peer that pressed launch.

Fix

Wrap RitualBehaviorWorker_GravshipLaunch.CanReachGravship in Rand.PushState / PopState, matching the existing SeedPreceptComp_UnwillingToDo_Chance isolation in the same file for RNG that "can be called in interface". CanReachGravship returns a bool that does not depend on the RNG (it only searches for any allowed cell), and the real boarding calls TryFindSpotOnGravship outside this check, so isolating here leaves both the check result and determinism intact.

Testing

  • Reproduced on vanilla Multiplayer + Odyssey (async time + multifaction, no third-party mods): spamming the launch button desyncs a client with Random state from commands doesn't match.
  • With this fix, ~100 rapid clicks produce no desync.
  • Source/Client builds clean (Release).

Investigated and written with the help of Claude (Anthropic).

Building the gravship launch confirmation dialog decides which pawns can
board in Dialog_BeginRitual's constructor: CreateRitualRoleAssignments ->
RitualRoleAssignments.PawnNotAssignableReason ->
RitualBehaviorWorker_GravshipLaunch.PawnCanFillRole -> CanReachGravship ->
GravshipUtility.TryFindSpotOnGravship -> Region.RandomCell, which consumes
RNG. Building the dialog is UI work opened only by the issuing peer (the
currentExecutingCmdIssuedBySelf gate in CancelDialogBeginRitual), so that
RNG runs on just one peer and advances the shared stream on that peer only.
Spamming the launch button repeats it and the divergence accumulates into a
desync ("Random state from commands doesn't match").

Wrap CanReachGravship in Rand.PushState/PopState, matching the existing
SeedPreceptComp_UnwillingToDo_Chance isolation for RNG that "can be called in
interface". CanReachGravship returns a bool that does not depend on the RNG
(it only searches for any allowed cell), and the real boarding calls
TryFindSpotOnGravship outside this check, so isolating here leaves both the
check result and determinism intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desync when spamming the gravship launch button (RNG in CanReachGravship not isolated)

1 participant