Skip to content

Diagnostics for the edge-of-loaded-area crashes, plus spawn hardening - #26

Draft
absol89 wants to merge 5 commits into
masterfrom
claude/project-thread-tqauko
Draft

absol89 wants to merge 5 commits into
masterfrom
claude/project-thread-tqauko

Conversation

@absol89

@absol89 absol89 commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

Requested by Eric · project thread

Status: this does not fix the SkyrimSE+1AF607 crash. It adds logging to find the cause, plus one unrelated hardening change.

Crash (what we know)

EXCEPTION_ACCESS_VIOLATION at SkyrimSE.exe+1AF607 (1.6.1170, ID 13641+0x47) on a "Deer" Character with form ID 0. Crashes like it vary in where they happen, but all involve actors at the edge of the loaded area.

Checked in Ghidra (1.6.1170):

  • What faulted: 13641 is the TESWorldSpace cell lookup (BSTHashMap at worldspace+0x58). It was called with a pointer that isn't a live worldspace.
  • Where the pointer came from: BSTaskPool task 0x54. 37352 queues it via 36979 when an actor leaves its exterior parentCell, copying parentCell->worldspace (+0x128) as a raw pointer. 19799 (MoveRefToNewSpace) later passes it to 13641 without checking it.
  • What the crash dump shows: that value was read from the Deer's parentCell, which no longer looked like a cell (form type 0, ID 0). The dump has no heap memory, so the Deer itself couldn't be inspected.

Changes

  1. Diagnostic, [FormIdZeroActor]: logs a module+offset stack when a Character is constructed with form ID 0, or when one gets its 3D loaded. The client log showed form ID 0 Deer/Elk actors loaded and removed shortly before the crash.
  2. Diagnostic, [ActorPlacement]: logs the actor's and the player's cell, worldspace and position when an actor is added, before and after a remote actor is moved to its owner, and when it is removed. This is to trace a spawn/remove loop on a follower that both saves share: Esbern was removed 6,183 times in one log.
  3. Hardening: Actor::Create now returns null when ModManager::Spawn gives no handle or form ID. This is not the cause of this crash. Callers already handle null.

Held remote actors at the edge of the loaded area (reverted): that keeps NPCs loaded and stuck there instead of letting them walk off on their schedules.

Both diagnostics are temporary and should be removed once the cause is found. Not tested in game.

🤖 Generated with Claude Code

https://claude.ai/code/session_018fvQxNnHTLXfMV8j1PBLiQ

Actor::Create ignored the result of ModManager::Spawn. When the game doesn't
place the reference, the new Character keeps form ID 0 and gets no handle,
so the GamePtr returned by Create holds its only reference. Callers store it
in a raw Actor* and the actor is freed at the end of that statement, then
moved with MoveTo and used as a remote actor. Engine work queued for it can
later run on the freed actor, as in a crash at SkyrimSE 13641+0x47 under
TESObjectREFR::MoveRefToNewSpace (19799) from BSTaskPool, with a form ID 0
"Deer" and no parent cell.

Return null from Create when the spawn yields no handle or form ID. Callers
already handle a null actor; the debug spawner now checks it too, and the
spawn request drops the entity it created for the actor.

Not verified in Ghidra and not built or tested in game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxNnHTLXfMV8j1PBLiQ
@absol89 absol89 self-assigned this Nov 4, 2026
Diagnostic for the SkyrimSE+1AF607 crash: the crashing Deer had form ID 0,
and the client log shows Deer/Elk actors with form ID 0 being loaded just
before it. Log the caller chain (module+offset) when a Character is
constructed without a form ID and when one gets its 3D loaded, so the code
creating these actors can be found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxNnHTLXfMV8j1PBLiQ
The owner streams positions from its own loaded area, which can lie past
the edge of ours. Interpolation moved remote actors there anyway, so the
game moved them into cells we don't have loaded and then into cells as
they unloaded. Ghidra on 1.6.1170 shows the per-frame actor update (37352)
queues task 0x54 with a raw copy of parentCell->worldspace when an actor
leaves its exterior cell, and 19799/13641 later use it unchecked. A crash
dump had that worldspace come from a parent cell that was no longer a cell.

Hold remote actors at their last position inside the loaded grid until
they come back or are removed. Interiors and other worldspaces are
unchanged.

Not tested in game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxNnHTLXfMV8j1PBLiQ
@absol89 absol89 changed the title Don't hand out a remote actor the game failed to place Fix crashes from remote actors moved past the loaded area Nov 4, 2026
Diagnostic for the follower spawn/remove loop (Esbern removed 6,183 times
in one log): log the actor's and the player's cell, worldspace and
position when an actor is added, before and after a remote actor is moved
to its owner, and when it is removed, to show what brings it back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxNnHTLXfMV8j1PBLiQ
Holding a remote actor at the edge of the loaded area keeps it loaded and
stuck there, so it never unloads the way Skyrim expects when an NPC walks
off on its schedule.

This reverts commit a299ac5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxNnHTLXfMV8j1PBLiQ
@absol89 absol89 changed the title Fix crashes from remote actors moved past the loaded area Diagnostics for the edge-of-loaded-area crashes, plus spawn hardening Nov 4, 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.

2 participants