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