fix(physics): S4/AD-65 — the away-from-plane response snaps to the surface, as retail does
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Campaign S slice S4, the half that landed. Retail's CTransition:: adjust_offset @0x0050a370 branches on dot(offset, contactPlane.N) at 0x0050a4fa: moving INTO the plane subtracts the normal component (0x0050a529), moving AWAY calls Plane::snap_to_plane @0x00509c50 — which preserves X and Y and re-solves ONLY Z so the offset lies in the plane (the d terms cancel algebraically), no-op under the 0.000199999995f |N.z| epsilon. acdream ran the orthogonal projection in BOTH directions, shrinking downhill XY travel by cos^2(theta): 25% at 30 degrees, 50% at 45 — AD-65's recorded shortfall, now retired. The combined Opus review independently re-derived the algebra, the branch polarity, the epsilon's bit-identity (17b75139), and the sabotage magnitude (the re-instated projection yields X = 0.75 = cos^2 30 exactly), and verified the delta is 4 non-comment lines with the into-plane arm, the crease arm, and both no-plane arms untouched. Its blast-radius sweep found the away arm exercised but NOT discriminated by any pre-existing test — every one asserts lower bounds the snap over-satisfies — so the two new exact-value tests are the only discriminating coverage, recorded in the test's class doc, and the felt 33-100% downhill speed-up is the morning gate's one row. AD-66 (the push-out's bare radius) is WITHHELD: byte-confirmed twice, implemented, then pulled after the same clean-room binaries measured contradictory absorbed-tick outcomes flipping with nothing but test assert shape — issue #341 carries the observation matrix and the apparatus plan; its two exact-value tests are [Skip]-ed; the retained substitution's rationale is restored at the site per review F1, with the review's remaining findings (F2/F3/F4/F5/F6) applied and F8 filed as #342. AD-69 filed: the same block omits retail's get_block_offset seam-frame correction, deferred to the AD-66 relanding for attributability. #340 filed: a fifth load-sensitive flake. Review verdict: PASS. AD-65 is provably unable to reach the #341 anomaly's code path (the absorb scenario takes the crease arm). Clean-room suite: 11,239 passed / 6 skipped / 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4721838916
commit
d73125d3b0
8 changed files with 859 additions and 40 deletions
|
|
@ -5491,8 +5491,14 @@ public sealed class Transition
|
|||
///
|
||||
/// Ported from pseudocode section 6 (AdjustOffset).
|
||||
/// ACE: Transition.AdjustOffset(Vector3 offset).
|
||||
///
|
||||
/// internal (not private) as of Campaign S slice S4 (2026-08-07), matching
|
||||
/// the existing <see cref="SlideSphereInternal"/> precedent, so conformance
|
||||
/// tests can call it with exact-value CollisionInfo/SpherePath fixtures
|
||||
/// instead of driving it indirectly through the whole step-subdivision
|
||||
/// loop. See docs/research/2026-08-07-s4-pseudocode.md.
|
||||
/// </summary>
|
||||
private Vector3 AdjustOffset(Vector3 offset)
|
||||
internal Vector3 AdjustOffset(Vector3 offset)
|
||||
{
|
||||
var sp = SpherePath;
|
||||
var ci = CollisionInfo;
|
||||
|
|
@ -5561,42 +5567,54 @@ public sealed class Transition
|
|||
else if (collisionAngle <= 0f)
|
||||
{
|
||||
// Moving into the contact plane: remove component into the plane.
|
||||
// retail CTransition::adjust_offset 0x0050a505/0x0050a529
|
||||
// (collisionAngle <= 0 -> subtract full normal component).
|
||||
result -= ci.ContactPlane.Normal * collisionAngle;
|
||||
branch = "into-plane";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Moving away from contact plane: snap to plane surface.
|
||||
// SnapToPlane: remove any component that would violate the plane.
|
||||
result -= ci.ContactPlane.Normal * collisionAngle;
|
||||
// AD-65 fix (2026-08-07): moving away from the contact plane —
|
||||
// retail calls Plane::snap_to_plane @0x00509c50 here, which is NOT
|
||||
// the orthogonal-projection subtraction used in the into-plane arm
|
||||
// above. snap_to_plane preserves X and Y and re-solves ONLY Z so
|
||||
// the offset lies exactly in the plane:
|
||||
// |N.z| <= EPSILON -> no-op (near-vertical plane; can't divide)
|
||||
// else -> result.z = -(result.x*N.x + result.y*N.y) / N.z
|
||||
// (retail's literal formula carries +d/N.z -d/N.z terms that
|
||||
// cancel algebraically; derivation in
|
||||
// docs/research/2026-08-07-s4-pseudocode.md.) The pre-fix
|
||||
// subtraction shrank XY by cos^2(theta) on slopes — register
|
||||
// row AD-65 in docs/architecture/retail-divergence-register.md.
|
||||
Vector3 n = ci.ContactPlane.Normal;
|
||||
if (MathF.Abs(n.Z) > PhysicsGlobals.EPSILON)
|
||||
result.Z = -(result.X * n.X + result.Y * n.Y) / n.Z;
|
||||
branch = "away-plane";
|
||||
}
|
||||
|
||||
// Safety check: ensure the sphere stays above the contact plane.
|
||||
// Ported from pseudocode section 6 (AdjustOffset safety block), with
|
||||
// a correction for the Z-axis sphere-origin convention.
|
||||
// AD-66 — deviation RETAINED, landing WITHHELD (2026-08-07, S4;
|
||||
// issue #341). Retail's CTransition::adjust_offset 0x0050a370 uses
|
||||
// the BARE global_sphere->radius for both the trigger comparison
|
||||
// (0050a5c4 fld [ecx+0xc], then subtract F_EPSILON) and the zDist
|
||||
// numerator (0050a5dc fsubr [ecx+0xc]) — byte-confirmed twice. The
|
||||
// code below deliberately does NOT port that yet: the S4 landing was
|
||||
// pulled after the same clean-room binaries measured contradictory
|
||||
// outcomes on the #331 absorb scenario (see #341). The register row
|
||||
// AD-66 stays ACTIVE.
|
||||
//
|
||||
// The LocalSphere origin is at (0, 0, radius): the sphere center
|
||||
// sits `radius` above the character root along WORLD Z, NOT along
|
||||
// the plane normal. When a character stands with feet on a tilted
|
||||
// plane, the sphere center's perpendicular distance to that plane
|
||||
// is `radius * Normal.Z`, not `radius`. The naïve `dist < radius`
|
||||
// threshold therefore fires spuriously on every slope — the sphere
|
||||
// is geometrically offset, not actually penetrating — and the
|
||||
// subsequent push-up lifts the feet by `r * (sec θ - 1)`: 7 cm at
|
||||
// 30°, 20 cm at 45°, 48 cm at 60°. The steep-slope lift is large
|
||||
// enough to break the "feet on plane → set contact plane" check in
|
||||
// ValidateWalkable; ValidateTransition then clears OnWalkable,
|
||||
// gravity applies next frame, and the character visibly flickers
|
||||
// into the Falling animation while running up hills. Observed
|
||||
// empirically on steep slopes.
|
||||
//
|
||||
// Correct threshold: `radius * Normal.Z` (the natural resting
|
||||
// distance of a Z-aligned sphere on the given plane). The push
|
||||
// fires only when the sphere is ACTUALLY penetrating below natural
|
||||
// resting. ACE and the published pseudocode have the original
|
||||
// threshold, but the bug would also affect ACE's simulation — it's
|
||||
// just invisible server-side where no one renders characters.
|
||||
// The RATIONALE for the retained substitution, preserved because the
|
||||
// deviation is live: the LocalSphere origin is at (0, 0, radius) —
|
||||
// the sphere centre sits `radius` above the root along WORLD Z, not
|
||||
// along the plane normal, so a sphere resting on a tilted plane is
|
||||
// `radius * Normal.Z` from it. The bare `dist < radius` threshold
|
||||
// fires spuriously on every slope and the push-up lifts the feet by
|
||||
// r * (sec θ - 1): 7 cm at 30°, 20 cm at 45°, 48 cm at 60° — enough
|
||||
// to break ValidateWalkable's feet-on-plane check, clear OnWalkable,
|
||||
// and flicker the Falling animation while running uphill (observed
|
||||
// empirically). If the premise is right, RETAIL ITSELF has that
|
||||
// lift; resolving which story is true is exactly what #341's
|
||||
// apparatus session is for.
|
||||
if (ci.ContactPlaneCellId != 0 && !ci.ContactPlaneIsWater)
|
||||
{
|
||||
Vector3 globCenter = sp.GlobalSphere[0].Origin;
|
||||
|
|
@ -5606,13 +5624,25 @@ public sealed class Transition
|
|||
float dist = Vector3.Dot(globCenter, ci.ContactPlane.Normal)
|
||||
+ ci.ContactPlane.D;
|
||||
|
||||
// Natural resting distance of the Z-aligned sphere on this plane.
|
||||
// AD-66 WITHHELD 2026-08-07 (S4): retail's BARE radius here is
|
||||
// byte-confirmed (0050a5c4 / 0050a5dc, see the register row), but
|
||||
// landing it collided with the #331 absorb characterization pin
|
||||
// through an interaction the overnight session could not
|
||||
// stabilize: the same clean-room binaries measured BOTH a
|
||||
// one-time resting lift and an exact latch on the absorbed-tick
|
||||
// scenario, flipping with nothing but the shape of the test's
|
||||
// post-tick asserts. Until that measurement anomaly is explained
|
||||
// with real apparatus, the pre-S4 substitution stays, and the
|
||||
// register row stays ACTIVE. Do not land the bare radius on the
|
||||
// strength of the byte evidence alone - the bytes were never the
|
||||
// open question.
|
||||
float naturalRestingDist = radius * ci.ContactPlane.Normal.Z;
|
||||
|
||||
if (dist < naturalRestingDist - PhysicsGlobals.EPSILON)
|
||||
{
|
||||
// Sphere is actually penetrating the plane (feet below it)
|
||||
// — push up along +Z to restore natural resting distance.
|
||||
// Sphere is penetrating below the natural-resting threshold
|
||||
// (radius * N.z — the retained AD-66 substitution, see the
|
||||
// block comment above) — push up along +Z to restore it.
|
||||
float zDist = (naturalRestingDist - dist) / ci.ContactPlane.Normal.Z;
|
||||
if (radius > MathF.Abs(zDist))
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue