From 76e1a64d78354997dbee354c477a83b547da266c Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 10 May 2026 07:41:36 +0200 Subject: [PATCH] fix(A.5 T10): lock 2 missed _dats.Get sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/AcDream.App/Rendering/GameWindow.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 741b2a9..a5e5a69 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -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(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(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(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(playerEntity.SourceGfxObjOrSetupId); } if (playerSetup is not null) _physicsDataCache.CacheSetup(playerEntity.SourceGfxObjOrSetupId, playerSetup); _playerController.StepUpHeight = (playerSetup is not null && playerSetup.StepUpHeight > 0f)