fix(physics): guard the world-frame agreement proven unreachable by measurement

Closes #283 (plan S3) - as UNREACHABLE, not by restructuring ownership.

acdream has two owners that convert a landblock-local network origin into the
streamed world frame: LiveWorldOriginState for presentation/streaming, and
RuntimePhysicsState.TryGetWorldFrameOffset for placement. They rebase on
different edges - Runtime the instant an accepted Position carries
TeleportAdvanced, App only once StreamingOriginRecenterCoordinator observes
old-window retirement completion, many frames later. A one-landblock
disagreement places an entity 192 m from the geometry around it: the same
failure family as the zero-offset bug 670f307c fixed, with a wrong origin
instead of a missing one.

The plan's first step was to prove or disprove reachability BEFORE moving
ownership, because a restructure on a hypothesis is churn. The probe added in
898ff18b answered it: a connected Release session recorded ZERO disagreements
across 11 completed reveals and six destination landblocks (0x0904, 0x1134,
0x3032, 0x8763, 0xA9B4, 0xF682) spanning roughly 45 km. A gap of even one
frame would have printed an offset in the tens of thousands of metres.

Cause of the safety: BeginOriginRecenter detaches EVERY resident landblock
before the new origin is adopted, so the two rebases are serialized and no
conversion can observe the gap. Ownership is therefore left exactly as it is.

What lands instead is the invariant that keeps it true.
LiveWorldOriginState.EnsureAgreesWithRuntimeFrame is checked at the
landblock->world conversion and is terminal on disagreement, converting a
silent 192 m-multiple misplacement into a loud failure with the offset in
metres and the landblock being projected. Six focused tests pin it, including
the cross-world portal case (0x09 -> 0xF6 = 45,504 m). Disagreement can no
longer reach the probe, so ACDREAM_PROBE_WORLD_FRAME now emits a verbose
per-conversion agreement trace - useful when a placement looks displaced for
some reason OTHER than a frame disagreement.

Complete Release solution: 10,844 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-03 14:33:46 +02:00
parent 898ff18b26
commit 89cf1e66d0
5 changed files with 183 additions and 22 deletions

View file

@ -163,6 +163,12 @@ internal sealed class DatLiveEntityProjectionMaterializer
CreateObject.ServerPosition position = canonicalSpawn.Position.Value;
int lbX = (int)((position.LandblockId >> 24) & 0xFFu);
int lbY = (int)((position.LandblockId >> 16) & 0xFFu);
// #283: permanent invariant - the two world-frame owners must agree
// before their conversions can be mixed. Proven unreachable by the
// 2026-08-03 probe run; this keeps it that way.
_origin.EnsureAgreesWithRuntimeFrame(
_runtime.Physics.WorldFrameCenterLandblockId,
position.LandblockId);
if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeWorldFrameEnabled)
ProbeWorldFrameAgreement(position.LandblockId);
var worldOrigin = new Vector3(
@ -1195,12 +1201,12 @@ internal sealed class DatLiveEntityProjectionMaterializer
/// 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
/// <c>LiveWorldOriginState</c> 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.
/// Disagreement is now a terminal invariant
/// (<c>LiveWorldOriginState.EnsureAgreesWithRuntimeFrame</c>, checked
/// immediately before this), so anything reaching here has already
/// agreed. This verbose trace records the centres both owners used for
/// each conversion, which is what you want when investigating a
/// placement that looks displaced but is not a frame disagreement.
/// </summary>
private void ProbeWorldFrameAgreement(uint landblockId)
{
@ -1210,15 +1216,7 @@ internal sealed class DatLiveEntityProjectionMaterializer
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}"));
$"[world-frame] agree centre=({runtimeCenterX},{runtimeCenterY}) projecting=0x{landblockId:X8}"));
}
}