fix(physics): route remote Positions on contact, not walkability (AP-140)
The two gates that decide whether an accepted remote Position is interpolated
or hard-snapped read `Airborne`, which is `!Body.OnWalkable` — WALKABILITY.
Retail reads CONTACT: InterpolationManager::adjust_offset @0x00555D30 gates its
entire body on `transient_state & 1` @0x00555D52, so a retail body in contact
with a non-walkable face still interpolates.
The two predicates disagree in exactly one state — in contact, not on walkable
ground — which 204d0ae0 turned from unreachable into ordinary. Before it, the
per-tick forge made every non-airborne remote walkable by construction, so the
disagreement could not occur.
Both gates now read `!Body.InContact`: ApplyRemoteContactRouting's flight
carve-out and OnPosition's player-remote arm.
`Airborne` is deliberately NOT re-derived from CONTACT. That would perturb all
five of its writers and contradict a pinned assertion in
RemoteTeleportPlacementTests.Apply_PendingGroundToSteepContact_ (InContact:
true, OnWalkable: false -> Assert.True(remote.Airborne)); a previous
implementer attempted it and correctly backed out rather than editing the
assertion. This narrower shape touches no existing test.
AP-140's register row is retired in this commit, as the row itself specified.
Honest scope: this is a faithfulness fix, not a visible one. ACE derives its
IsGrounded flag with the same floor_z test, so during a slide it almost
certainly reports not-grounded, the classifier returns NoPositionOperation, and
neither arm is taken. Expect no observable change against ACE.
Suite 11,027 passed / 4 skipped / 0 failed (baseline 11,023).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f4f2579575
commit
2eb39a0250
6 changed files with 273 additions and 33 deletions
|
|
@ -119,6 +119,48 @@ public sealed class LiveEntityNetworkBranchRoutingTests
|
|||
source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AP-140 (retired 2026-08-04): the SECOND accepted-Position routing
|
||||
/// gate — <c>OnPosition</c>'s player-remote landing block — must
|
||||
/// select the hard snap on retail's CONTACT predicate
|
||||
/// (<c>InterpolationManager::adjust_offset</c> @0x00555D30 gates its
|
||||
/// whole body on <c>transient_state & 1</c> @0x00555D52, and bit 0
|
||||
/// is <c>CONTACT_TS</c>), not on the client <c>Airborne</c> flag,
|
||||
/// which is <c>!Body.OnWalkable</c> — WALKABILITY, a strictly wider
|
||||
/// set that also captures a remote sliding on a steep face.
|
||||
///
|
||||
/// <para>
|
||||
/// A source pin rather than a behavioural fixture for the reason this
|
||||
/// class already documents: the controller's dependency set is
|
||||
/// composition-only. The twin gate inside
|
||||
/// <c>ApplyRemoteContactRouting</c> — a static method, so reachable —
|
||||
/// IS covered behaviourally, in
|
||||
/// <c>LiveEntityNetworkRemoteSteadyStateIntegrationTests</c>. Restore
|
||||
/// <c>if (rmState.Airborne)</c> here and this test fails.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PlayerRemoteLandingSnapSelectsOnContactNotOnWalkability()
|
||||
{
|
||||
string source = ReadSource("LiveEntityNetworkUpdateController.cs");
|
||||
|
||||
Assert.Contains(
|
||||
"if (!rmState.Body.InContact)",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
// `rmState.Airborne` survives as a WRITE target and in prose (the
|
||||
// block's own comment explains why it is deliberately not cleared
|
||||
// there); what must never come back is reading it as the gate.
|
||||
Assert.DoesNotContain(
|
||||
"if (rmState.Airborne)",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"if (remote.Airborne)",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static string ReadSource(string fileName)
|
||||
{
|
||||
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue