Adaptive Storage Framework: isolate spawn-time graphic Rand#586
Closed
sviyh wants to merge 1 commit intorwmt:masterfrom
Closed
Adaptive Storage Framework: isolate spawn-time graphic Rand#586sviyh wants to merge 1 commit intorwmt:masterfrom
sviyh wants to merge 1 commit intorwmt:masterfrom
Conversation
…r scope AdaptiveStorage.ThingClass.InitializeStoredThings (bulk re-add on spawn) and Notify_ItemRegisteredAtCell (per-item hook fired from AS's transpiler on ThingGrid.RegisterInCell) both reach Verse.Graphic_Random.get_MatSingle via MinifiedThing.get_Graphic. That consumes Verse.Rand to pick a random material variant. If any of those calls fires inside MP's SeedMapFinalizeLoading seeded scope, it perturbs the stream at a position the client won't reproduce, producing divergent RoomTempTracker.equalizeCells shuffle orderings and an eventual Rand desync via the temperature path. Wrap both entry points with PushState(thingIDNumber)/PopState so their Rand consumption can't leak into the outer scope. Same pattern as MP's own SeedPawnGraphics for PawnRenderer.SetAllGraphicsDirty. LoadPatch is deferred via LongEventHandler.ExecuteWhenFinished: AS's ThingExtensions has a static initializer that reads DefDatabase<ThingDef> and throws if the list is empty. Calling LoadPatch in the mod constructor runs Harmony's patch installation early enough that it transitively triggers the cctor before DefDatabase is populated, failing the cctor and poisoning every subsequent AS extension-method access (thousands of errors per tick). Deferring the install until the current LongEvent finishes guarantees DefDatabase is loaded when Harmony touches AS.
Closed
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
adaptive.storage.framework(by bbradson) wrapping spawn-time graphic-randomization inRand.PushState(thingIDNumber)/PopStateso it can't perturb MP'sSeedMapFinalizeLoadingseeded scope during host'sSaveAndReload.LoadPatchis deferred viaLongEventHandler.ExecuteWhenFinishedto avoid triggering AS'sThingExtensionscctor beforeDefDatabaseis populated.The bug
Adaptive Storage Framework's item-graphic code reaches
Verse.Graphic_Random.get_MatSingleduring spawn, which consumesVerse.Randto pick a random material variant. Two entry points trigger this chain:AdaptiveStorage.ThingClass.InitializeStoredThings— bulk re-add when a storage container itself spawnsAdaptiveStorage.ThingClass.Notify_ItemRegisteredAtCell— per-item hook fired by AS's transpiler onThingGrid.RegisterInCellfor every item spawn landing in an AS storage cellFull stack (captured from probe instrumentation on a reproducing session):
During host's in-process
SaveAndReloadthe spawn-order of these Rand calls relative toRoomTempTracker.RegenerateEqualizeCells.Shufflediffers from the client's fresh-process load (same 8174 things spawned, different ordering). Both pull from the sameSeedMapFinalizeLoading-pushed seed, but at different iteration counts, producing divergentequalizeCellsper room. Within ~120 ticks the temperature drift propagates into aFreezeManager.DoIceMeltingRand.Chanceshort-circuit and the main RNG stream desyncs.The fix
Wrap both entry points with
Rand.PushState(__instance.thingIDNumber)/PopState(MP-only). Same pattern as MP's ownSeedPawnGraphicsforPawnRenderer.SetAllGraphicsDirty. The seed is stable across save/load and identical across clients, so each container picks the same graphic variant every time it spawns — no functional change.Why
LoadPatchis deferredAS's
ThingExtensionshas a static initializer that readsDefDatabase<ThingDef>.AllDefsListForReadingand throws if the list is empty. CallingMpCompatPatchLoader.LoadPatch(this)in the mod constructor runs early enough that Harmony's patch installation on AS methods transitively triggers the cctor beforeDefDatabaseis populated. The cctor throws once, the failure is cached, and every subsequent AS extension-method call rethrowsTypeInitializationException— tens of thousands of errors per load. Deferring viaLongEventHandler.ExecuteWhenFinishedguaranteesDefDatabaseis loaded when Harmony touches AS.Test plan
dotnet build Source/Multiplayer_Compat.csproj -c Release)D:\Steam\steamapps\workshop\content\294100\1629973374\1.6\Assemblies\Multiplayer_Compat.dllThingExtensions/TypeInitializationExceptionflood)equalizeCellsshuffle desync no longer fires.Notes
References/AdaptiveStorageFramework.dll— uses string-named method resolution throughout, so it stays inSource/Mods/rather thanSource_Referenced/.[MpCompatFor("adaptive.storage.framework")]— patch is inert for players without AS installed.Reel's Expanded Storage(reel.expanded.storage) depends on AS Framework and shares the same code path; this patch covers it implicitly.