diff --git a/src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs b/src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs index 13760355..e5f1abe3 100644 --- a/src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs +++ b/src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs @@ -163,6 +163,8 @@ internal sealed class DatLiveEntityProjectionMaterializer CreateObject.ServerPosition position = canonicalSpawn.Position.Value; int lbX = (int)((position.LandblockId >> 24) & 0xFFu); int lbY = (int)((position.LandblockId >> 16) & 0xFFu); + if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeWorldFrameEnabled) + ProbeWorldFrameAgreement(position.LandblockId); var worldOrigin = new Vector3( (lbX - _origin.CenterX) * 192f, (lbY - _origin.CenterY) * 192f, @@ -1185,4 +1187,38 @@ internal sealed class DatLiveEntityProjectionMaterializer } return nullSlots >= 8; } + + /// + /// #283 reachability probe. This method is the App-side counterpart of + /// Runtime's TryGetWorldFrameOffset: both convert a + /// landblock-local network origin into the streamed world frame, and both + /// must use the SAME centre landblock or the entity lands a multiple of + /// 192 m from the geometry around it. + /// + /// Runtime rebases on the accepted teleport Position; App's + /// LiveWorldOriginState rebases only after old-window retirement + /// completes. Emits one line per DISAGREEMENT — silence across a portal + /// run is the evidence that the window is unreachable in practice, and + /// that #283 can be closed as a permanent invariant rather than an + /// ownership restructure. Measurement only; it never gates placement. + /// + private void ProbeWorldFrameAgreement(uint landblockId) + { + uint runtimeCenter = _runtime.Physics.WorldFrameCenterLandblockId; + if (runtimeCenter == 0u || !_origin.IsKnown) + return; + + int runtimeCenterX = (int)((runtimeCenter >> 24) & 0xFFu); + int runtimeCenterY = (int)((runtimeCenter >> 16) & 0xFFu); + if (runtimeCenterX == _origin.CenterX + && runtimeCenterY == _origin.CenterY) + { + return; + } + + float deltaX = (runtimeCenterX - _origin.CenterX) * 192f; + float deltaY = (runtimeCenterY - _origin.CenterY) * 192f; + Console.WriteLine(System.FormattableString.Invariant( + $"[world-frame] DISAGREE runtime=({runtimeCenterX},{runtimeCenterY}) app=({_origin.CenterX},{_origin.CenterY}) offsetDelta=({deltaX:F0}m,{deltaY:F0}m) projecting=0x{landblockId:X8}")); + } } diff --git a/src/AcDream.Core/Physics/PhysicsDiagnostics.cs b/src/AcDream.Core/Physics/PhysicsDiagnostics.cs index b38e319e..c42d4ebf 100644 --- a/src/AcDream.Core/Physics/PhysicsDiagnostics.cs +++ b/src/AcDream.Core/Physics/PhysicsDiagnostics.cs @@ -61,6 +61,27 @@ public static class PhysicsDiagnostics public static bool ProbeCellEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_CELL") == "1"; + /// + /// #283 reachability probe (2026-08-03). Runtime rebases its world frame + /// the instant an accepted Position carries TeleportAdvanced, while App's + /// LiveWorldOriginState rebases only once + /// StreamingOriginRecenterCoordinator observes old-window + /// retirement completion — many frames later. Between those edges the two + /// owners can disagree by the source-to-destination landblock delta, and + /// anything converted in that gap lands a multiple of 192 m from the + /// geometry App is building. + /// + /// When true, every projected placement compares Runtime's frame + /// centre against App's origin centre and emits one + /// [world-frame] line per DISAGREEMENT only — silence means the + /// window is never observed. This exists to prove or disprove + /// reachability before any ownership is restructured; it is a + /// measurement, never a gate. Initial state from + /// ACDREAM_PROBE_WORLD_FRAME=1. + /// + public static bool ProbeWorldFrameEnabled { get; set; } = + Environment.GetEnvironmentVariable("ACDREAM_PROBE_WORLD_FRAME") == "1"; + /// /// Stuck-cast/missing-attack investigation (2026-07-30). When true, /// every REMOTE ground-contact edge (HitGround / LeaveGround — each of diff --git a/src/AcDream.Runtime/Physics/RuntimePhysicsState.cs b/src/AcDream.Runtime/Physics/RuntimePhysicsState.cs index 70fd147e..c6d95943 100644 --- a/src/AcDream.Runtime/Physics/RuntimePhysicsState.cs +++ b/src/AcDream.Runtime/Physics/RuntimePhysicsState.cs @@ -546,6 +546,16 @@ public sealed class RuntimePhysicsState : IDisposable } } + /// + /// #283: the landblock Runtime currently treats as world-frame (0,0), or + /// 0 before the local-player Create publishes one. Read-only and + /// diagnostic — App compares it against its own + /// LiveWorldOriginState centre to detect the teleport-window + /// disagreement described on + /// PhysicsDiagnostics.ProbeWorldFrameEnabled. + /// + internal uint WorldFrameCenterLandblockId => _worldFrameCenterLandblockId; + /// /// #284: records that this session's local-player Create was accepted, /// independently of whether it carried a usable landblock. The frame is