Skip to content

AsyncTime: Skip timestamp fixing for destroyed pawns - #979

Open
M-r-A wants to merge 1 commit into
rwmt:devfrom
RimWorld-mods-patches:pr/timestamp-fixer-destroyed-pawn
Open

AsyncTime: Skip timestamp fixing for destroyed pawns#979
M-r-A wants to merge 1 commit into
rwmt:devfrom
RimWorld-mods-patches:pr/timestamp-fixer-destroyed-pawn

Conversation

@M-r-A

@M-r-A M-r-A commented Aug 9, 2026

Copy link
Copy Markdown

Every animal death writes a warning to the log. A predator hunt produces one per kill.

Deep-saving destroyed thing Squirrel36139 with saveDestroyedThings==false. label=saveable

Where it comes from

The stack under that warning is a complete causal chain, and the middle of it is ours:

Verse.Scribe_Deep:Look<Verse.IExposable> (Verse.IExposable&,bool,string,object[])
Verse.ScribeSaver:DebugOutputFor (Verse.IExposable)
Multiplayer.Client.Patches.TimestampFixer:FixPawn (Verse.Pawn,Verse.Map,Verse.Map)   TimestampFixer.cs:45
Multiplayer.Client.Patches.WorldPawnsAddPawn_FixTimestamp:Prefix (Verse.Pawn)        TimestampFixer.cs:118
RimWorld.Planet.WorldPawns:PassToWorld (Verse.Pawn,RimWorld.Planet.PawnDiscardDecideMode)
Verse.Pawn.Destroy_Patch1 (Verse.Pawn,Verse.DestroyMode)
Verse.Thing.Kill_Patch1 (Verse.Thing,System.Nullable`1<Verse.DamageInfo>,Verse.Hediff)
...
RimWorld.JobDriver_PredatorHunt/<>c__DisplayClass13_0:<MakeNewToils>b__3 ()

Reading upward from the bottom: a predator lands a melee hit, the prey dies,
Pawn.Destroy hands it to WorldPawns.PassToWorld, and AddPawn hits our prefix.

FixPawn shifts a pawn's timestamps when it crosses between time contexts, since
under asyncTime each map keeps its own tick counter. It visits the pawn's fields
by saving itScribe.saver.DebugOutputFor — so that a prefix on
RegisterDeepSaved can rewrite each timestamp as the scribe walks past it.

That is what trips the warning. Vanilla objects to a deep save reaching a destroyed
thing, and a pawn that arrived here via Destroy is destroyed by definition. So the
warning fires on every death that passes to world pawns.

Fix

Return early when the pawn is destroyed.

The four fields involved — canSleepTick, canLovinTick,
ticksWhenAllowedToEscapeAgain, lastPrisonBreakTicks — all describe behaviour only
a live pawn has. There is nothing to correct.

Why this is safe

  • Not lossy. If the pawn is later resurrected and spawned, the SpawnSetup path
    applies the correction then, driven by worldPawnRemoveTick.
  • No leftover state. The caller clears lastMap after calling FixPawn, so the
    early return does not strand a stale marker.
  • Cannot desync. p.Destroyed is simulation state set inside Destroy(), which
    runs in the synced tick, so every client takes the same branch. A guard on
    client-local state here would have been worse than the warning.
  • Live pawns are untouched. A pawn that leaves a map alive — a caravan departing,
    an animal wandering off — still has Destroyed == false and still gets its
    timestamps corrected.

Testing

Build clean in Debug and Release, 165/165 tests.

Manual verification, two checks:

  • Predator hunt — the Deep-saving destroyed thing warning no longer appears,
    where it previously appeared once per kill.
  • No other behaviour change around deaths.

Note

The same fix appears as one line item in the "QoL / smaller fixes" list of #961.
Extracted here as a standalone change since that PR touches 98 files and is still
in review; the code is identical, so whichever lands first makes the other a no-op.

FixPawn shifts a pawn's timestamps when it crosses between time contexts, and
visits the pawn's fields by saving it, so that a prefix on RegisterDeepSaved can
rewrite each one as the scribe walks past. Vanilla warns when a deep save reaches
a destroyed thing, so each visit for a dead pawn logged

    Deep-saving destroyed thing X with saveDestroyedThings==false

Anything killed on a map reaches WorldPawns.AddPawn through Destroy, so an
ordinary predator hunt produced one warning per kill.

Return early instead. The fields involved -- canSleepTick, canLovinTick,
ticksWhenAllowedToEscapeAgain, lastPrisonBreakTicks -- all describe behaviour only
a live pawn has, so there is nothing to correct. A pawn that is later resurrected
and spawned gets the correction from the SpawnSetup path.
@M-r-A M-r-A changed the title Skip timestamp fixing for destroyed pawns AsyncTime: Skip timestamp fixing for destroyed pawns Aug 10, 2026
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.

1 participant