From b745850ab8d438459a8c897db47ab8a0e0b196b0 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 21 Jun 2026 20:12:40 +0200 Subject: [PATCH] feat(slice2): wire outdoorReady into TeleportArrivalReadiness (GameWindow) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 33 ++++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 4320fbb8..35514cdd 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -5471,6 +5471,7 @@ public sealed class GameWindow : IDisposable // impossible claim / timeout. PortalSpace keeps input frozen meanwhile. EnsureTeleportArrivalController(); _pendingTeleportRot = rot; + _lastArrivalVerdict = AcDream.App.World.ArrivalReadiness.NotReady; // reset probe state for each new hold _teleportArrival!.BeginArrival(newWorldPos, p.LandblockId); } } @@ -5481,6 +5482,8 @@ public sealed class GameWindow : IDisposable // runtime deps are wired by then). private AcDream.App.World.TeleportArrivalController? _teleportArrival; private System.Numerics.Quaternion _pendingTeleportRot = System.Numerics.Quaternion.Identity; + // §3.4 arrival-gate probe: tracks previous verdict so we log only the flip. + private AcDream.App.World.ArrivalReadiness _lastArrivalVerdict = AcDream.App.World.ArrivalReadiness.NotReady; // #145: the LANDBLOCK-relative (cell-local) position used to SEED the player // body's cell-relative CellPosition. This is the ONE place the streaming center @@ -5509,24 +5512,36 @@ public sealed class GameWindow : IDisposable // Reuses the #107 login readiness triplet (GameWindow.cs:1010-1024), evaluated // against the teleport's (destPos, destCell): an impossible indoor claim short- // circuits to immediate placement; otherwise hold until terrain is sampled and, - // for an indoor cell, the cell struct has hydrated. + // for an indoor cell, the cell struct has hydrated. For an outdoor cell, hold + // until the physics landblock is registered (§3.4 #145 residual fix). private AcDream.App.World.ArrivalReadiness TeleportArrivalReadiness( System.Numerics.Vector3 destPos, uint destCell) { bool claimUnhydratable = IsSpawnClaimUnhydratable(destCell); - // #135: an INDOOR destination (sealed dungeon / building interior) gates on the - // EnvCell FLOOR hydrating. Retail places on the cell floor. An OUTDOOR destination - // places immediately on the server-authoritative position — see TeleportArrivalRules - // (#145: holding is futile because streaming doesn't progress during a PortalSpace - // hold; the stale source landblock is dropped at recenter so the resolve trusts the - // server cell until the destination streams in). bool indoor = (destCell & 0xFFFFu) >= 0x0100u; bool indoorCellReady = indoor && !claimUnhydratable && _physicsEngine.IsSpawnCellReady(destCell); + // §3.4: for outdoor arrivals, gate on the physics landblock being resident. + // The landblock id is the high 16 bits of the cell id (i.e. destCell & 0xFFFF0000). + // Streaming progresses during the hold because the streaming controller runs before + // TeleportArrivalController.Tick. The 10 s _maxHoldFrames safety-net remains active. + bool outdoorReady = !indoor + && _physicsEngine.IsLandblockLoaded(destCell & 0xFFFF0000u); - return AcDream.App.World.TeleportArrivalRules.Decide( - claimUnhydratable, indoor, indoorCellReady); + var verdict = AcDream.App.World.TeleportArrivalRules.Decide( + claimUnhydratable, indoor, indoorCellReady, outdoorReady); + + if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeArrivalGateEnabled + && verdict != _lastArrivalVerdict + && verdict != AcDream.App.World.ArrivalReadiness.NotReady) + { + Console.WriteLine(System.FormattableString.Invariant( + $"[arrival-gate] destCell=0x{destCell:X8} landblock=0x{(destCell & 0xFFFF0000u):X8} verdict={verdict} indoor={indoor} landblockCount={_physicsEngine.LandblockCount}")); + } + _lastArrivalVerdict = verdict; + + return verdict; } // The deferred snap (the original OnLivePositionUpdated steps 2-5), now run only