diag(physics): remote landing-edge probe; record the two live jump defects

The user live-tested route 4a and reported two defects on player remotes: a
remote holds the falling animation after landing before finally landing, and a
remote jumping onto a house plants on the roof where retail slides off, then
blips to the slid-down position.

Neither is a route 4a regression. Do NOT revert 44830a0e — reverting would
restore the per-packet render slam 4a removed without touching either defect.

Bug B's root cause is identified and already covered by open issue #32, whose
text names both symptoms in one sentence. Both landing sites assert
TransientState |= Contact | OnWalkable unconditionally, where retail derives it
from the contact plane — CPhysicsObj::SetPositionInternal @0x00515330
(`if (contact_plane.N.z < floor_z) set_on_walkable(0) else set_on_walkable(1)`).
A steep roof is contact but NOT on_walkable; asserting both suppresses the slide
response, so the body sits until the server's positions walk 4 m away and
AP-87's threshold snaps it. That is the blip. Verified byte-identical pre-4a via
`git show 19d95094:`.

Bug B's *visible shape* IS 4a's: pre-4a every packet slammed the render entity
to the wire pose, so a stuck body flickered toward the true sliding position
5-10x per second — jitter rather than a clean hold.

Bug A stops at the goal's stop-condition rather than getting a speculative fix.
Three hypotheses with non-overlapping fixes; picking wrong means changing a
retail-ported gate on a guess. Retail's mechanism is already fully decoded, so
what is missing is OUR runtime state — no cdb trace against retail is needed.

Adds ACDREAM_PROBE_REMOTE_LANDING (PhysicsDiagnostics, read once at startup per
the diagnostic-owner rule, one bool check when off). It logs both landing sites
immediately before HitGround, and — the most diagnostic signal — emits a
separate line when a site is reached but the gravity gate is about to no-op,
which is hypothesis 1 (a wholesale Body.State write wiping the transient Gravity
bit mid-air, exactly AP-81's stated risk). Temporary instrumentation, marked for
stripping once the evidence is in.

Evidence recorded rather than new bugs filed: #32 gains the observation, the
root cause and the #173/AD-10 dependency caveat; AP-87 gains a live instance of
its stated risk; AD-10's stale file:line is corrected to RemoteMotionCombiner
with a note that its terrain-only normal cannot see a house roof at all.

Also files #308 — a SECOND flaky test, distinct from #302, which was twice
misattributed to it before being written down. #302 is a GC-allocation assertion
in App.Tests; #308 is a wall-clock deadline loop in Core.Net.Tests that fails
only under full-suite CPU contention (0 failures in 4 isolated runs). Conflating
them hides one, and an agent told to "ignore the known flake" would wave through
a real transport regression.

Gates: complete Release solution 10,938 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-04 01:10:03 +02:00
parent dda1e2a03a
commit eeec4fb42a
6 changed files with 366 additions and 2 deletions

View file

@ -1860,6 +1860,34 @@ internal sealed class LiveEntityNetworkUpdateController
{
_motionRuntime.EnsureRemoteMotionBindings(rmState, aeForLand, update.Guid);
}
// Bug A investigation (2026-08-04, docs/ISSUES.md #32):
// capture the exact state HitGround is about to act on —
// see PhysicsDiagnostics.LogRemoteLanding for the field
// list and PhysicsDiagnostics.ProbeRemoteLandingEnabled
// for the discriminator table. TEMPORARY — strip once
// the live-test run has landed.
if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeRemoteLandingEnabled)
{
bool gravitySetForProbe = rmState.Body.HasGravity;
AcDream.Core.Physics.PhysicsDiagnostics.LogRemoteLanding(
site: "controller",
guid: update.Guid,
airborneBefore: true,
gravitySet: gravitySetForProbe,
contact: rmState.Body.InContact,
onWalkable: rmState.Body.OnWalkable,
hasDefaultSink: rmState.Motion.DefaultSink is not null,
resolveIsOnGround: null,
sequencerStyle: aeForLand?.Sequencer?.CurrentStyle ?? 0,
sequencerMotion: aeForLand?.Sequencer?.CurrentMotion ?? 0);
if (!gravitySetForProbe)
{
AcDream.Core.Physics.PhysicsDiagnostics.LogRemoteLandingGateNoOp(
"controller", update.Guid);
}
}
ulong landingStateAuthorityVersion =
positionRecord.StateAuthorityVersion;
rmState.Movement.HitGround();

View file

@ -155,6 +155,92 @@ public static class PhysicsDiagnostics
public static bool ProbeStickyEnabled { get; set; }
= Environment.GetEnvironmentVariable("ACDREAM_PROBE_STICKY") == "1";
/// <summary>
/// Bug A investigation (2026-08-04, live route 4a test — see
/// <c>docs/ISSUES.md</c> #32 and
/// <c>docs/research/2026-08-04-remote-landing-investigation.md</c>): a
/// PLAYER remote sometimes stays in the falling animation after landing,
/// then snaps to the grounded pose after a delay. Three hypotheses were
/// identified, with non-overlapping fixes, so this probe captures the
/// state needed to discriminate them at BOTH remote landing-detection
/// sites: the UpdatePosition landing block in
/// <c>LiveEntityNetworkUpdateController</c> (site=<c>controller</c>) and
/// the per-tick VectorUpdate landing branch in
/// <c>RuntimeRemotePhysicsUpdater</c> (site=<c>per-tick</c>).
///
/// <para>
/// When true, emits one <c>[remote-landing]</c> line per landing edge via
/// <see cref="LogRemoteLanding"/>, capturing: the airborne flag on entry,
/// whether the Gravity state bit is still set (the
/// <c>MotionInterpreter.HitGround</c> gate at
/// <c>MotionInterpreter.cs</c>:~2435 no-ops silently when it is NOT —
/// hypothesis 1), the Contact/OnWalkable transient bits, whether a
/// <c>DefaultSink</c> is bound (hypothesis 2 — nothing to dispatch
/// through), the per-tick site's <c>resolveResult.IsOnGround</c> (n/a at
/// the controller site, which has no resolver call), and the
/// sequencer's current style/motion id (hypothesis 3 — the sequencer
/// disagrees with what the re-apply should produce). A companion
/// <c>[remote-landing-gate]</c> line fires via
/// <see cref="LogRemoteLandingGateNoOp"/> whenever a landing site is
/// reached but Gravity is already clear, so the HitGround call about to
/// happen is a silent no-op — the single most valuable signal for
/// hypothesis 1.
/// </para>
///
/// <para>
/// Initial state from <c>ACDREAM_PROBE_REMOTE_LANDING=1</c>. TEMPORARY —
/// strip once the discriminating live-test capture has landed.
/// </para>
/// </summary>
public static bool ProbeRemoteLandingEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_REMOTE_LANDING") == "1";
/// <summary>
/// Emit one <c>[remote-landing]</c> line for a remote landing-detection
/// edge. Caller MUST guard with
/// <c>if (!ProbeRemoteLandingEnabled) return;</c> before calling.
/// <paramref name="resolveIsOnGround"/> is <see langword="null"/> at the
/// controller site (no per-frame resolver call at that edge).
/// </summary>
public static void LogRemoteLanding(
string site,
uint guid,
bool airborneBefore,
bool gravitySet,
bool contact,
bool onWalkable,
bool hasDefaultSink,
bool? resolveIsOnGround,
uint sequencerStyle,
uint sequencerMotion)
{
var ci = System.Globalization.CultureInfo.InvariantCulture;
string onGroundText = resolveIsOnGround.HasValue
? resolveIsOnGround.Value.ToString()
: "n/a";
Console.WriteLine(string.Format(ci,
"[remote-landing] site={0} guid=0x{1:X8} t={2} airborneBefore={3} " +
"gravitySet={4} contact={5} onWalkable={6} hasDefaultSink={7} " +
"resolveIsOnGround={8} seqStyle=0x{9:X8} seqMotion=0x{10:X8}",
site, guid, Environment.TickCount64, airborneBefore,
gravitySet, contact, onWalkable, hasDefaultSink, onGroundText,
sequencerStyle, sequencerMotion));
}
/// <summary>
/// Emit one <c>[remote-landing-gate]</c> line when a landing edge is
/// reached but the Gravity state bit is already clear, so the imminent
/// <c>MotionInterpreter.HitGround</c> call will silently no-op (the
/// gate at <c>MotionInterpreter.cs</c>:~2435) — hypothesis 1 for Bug A.
/// Caller MUST guard with
/// <c>if (!ProbeRemoteLandingEnabled) return;</c> before calling.
/// </summary>
public static void LogRemoteLandingGateNoOp(string site, uint guid)
{
Console.WriteLine(System.FormattableString.Invariant(
$"[remote-landing-gate] site={site} guid=0x{guid:X8} t={Environment.TickCount64} NOOP gravityAlreadyClear=true"));
}
public static void LogCellSetBuild(
uint seedCellId,
System.Numerics.Vector3 sphereCenter,
@ -651,6 +737,7 @@ public static class PhysicsDiagnostics
ProbeSweptEnabled = false;
ProbeStepWalkEnabled = false;
ProbeTeleportEnabled = false;
ProbeRemoteLandingEnabled = false;
// Side-channel fields
LastBspHitPoly = null;

View file

@ -528,6 +528,34 @@ internal sealed class RuntimeRemotePhysicsUpdater
// ~1 Hz re-emit.
ulong landingStateAuthorityVersion =
record.StateAuthorityVersion;
// Bug A investigation (2026-08-04, docs/ISSUES.md #32):
// capture the exact state HitGround is about to act on —
// see PhysicsDiagnostics.LogRemoteLanding for the field
// list and PhysicsDiagnostics.ProbeRemoteLandingEnabled
// for the discriminator table. TEMPORARY — strip once
// the live-test run has landed.
if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeRemoteLandingEnabled)
{
bool gravitySetForProbe = rm.Body.HasGravity;
AcDream.Core.Physics.PhysicsDiagnostics.LogRemoteLanding(
site: "per-tick",
guid: serverGuid,
airborneBefore: true,
gravitySet: gravitySetForProbe,
contact: rm.Body.InContact,
onWalkable: rm.Body.OnWalkable,
hasDefaultSink: rm.Motion.DefaultSink is not null,
resolveIsOnGround: resolveResult.IsOnGround,
sequencerStyle: sequencer?.CurrentStyle ?? 0,
sequencerMotion: sequencer?.CurrentMotion ?? 0);
if (!gravitySetForProbe)
{
AcDream.Core.Physics.PhysicsDiagnostics.LogRemoteLandingGateNoOp(
"per-tick", serverGuid);
}
}
rm.Movement.HitGround();
if (!IsCurrentOwner(
record,