fix(A.5 T10): lock 2 missed _dats.Get<Setup> sites

Spec compliance review on T10-T12 bundle (commits 0cf86bb/00bb030/0405947)
caught 2 unprotected dat reads that the original T10 audit missed:

- GameWindow.UpdatePlayerAnimation (line ~7546): reads Setup when the
  player entity is missing from _animatedEntities (post-respawn pattern).
- GameWindow.EnterPlayerModeNow (line ~8567): reads Setup when entering
  player mode to derive StepUpHeight / StepDownHeight from the dat.

Both run on the render thread post-_streamer.Start(), so they can race
with the worker thread's BuildLandblockForStreamingLocked. DatBinReader's
shared buffer position would corrupt — same class of "ball with spikes"
bug the original Phase A.1 hotfix addressed.

Wrap both reads in lock (_datLock).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-10 07:41:36 +02:00
parent 0405947bac
commit 76e1a64d78

View file

@ -7543,7 +7543,11 @@ public sealed class GameWindow : IDisposable
// we always want it animated in player mode.
if (!_animatedEntities.TryGetValue(pe.Id, out var ae))
{
var setup = _dats.Get<DatReaderWriter.DBObjs.Setup>(pe.SourceGfxObjOrSetupId);
// A.5 T10: lock around _dats.Get — worker thread may be
// building a landblock mesh concurrently. DatBinReader's
// shared buffer position would corrupt without serialization.
DatReaderWriter.DBObjs.Setup? setup;
lock (_datLock) { setup = _dats.Get<DatReaderWriter.DBObjs.Setup>(pe.SourceGfxObjOrSetupId); }
if (setup is null) return;
_physicsDataCache.CacheSetup(pe.SourceGfxObjOrSetupId, setup);
@ -8564,7 +8568,10 @@ public sealed class GameWindow : IDisposable
// 0.4 m fallbacks.
if (_dats is not null && (playerEntity.SourceGfxObjOrSetupId & 0xFF000000u) == 0x02000000u)
{
var playerSetup = _dats.Get<DatReaderWriter.DBObjs.Setup>(playerEntity.SourceGfxObjOrSetupId);
// A.5 T10: lock around _dats.Get — worker thread may be
// building a landblock mesh concurrently.
DatReaderWriter.DBObjs.Setup? playerSetup;
lock (_datLock) { playerSetup = _dats.Get<DatReaderWriter.DBObjs.Setup>(playerEntity.SourceGfxObjOrSetupId); }
if (playerSetup is not null)
_physicsDataCache.CacheSetup(playerEntity.SourceGfxObjOrSetupId, playerSetup);
_playerController.StepUpHeight = (playerSetup is not null && playerSetup.StepUpHeight > 0f)