fix(phys): A6.P6 — cylinder step-over for Contact movers (CCylSphere::step_sphere_up)
Retail's CCylSphere::intersects_sphere at acclient_2013_pseudo_c.txt
:324626-324641 routes the Contact-state branch through step_sphere_up
(line 324516), not slide. The step-up check at line 324519-324524:
cyl_clearance = sphere.radius + cyl.height - offset.z
if (step_up_height < cyl_clearance) → slide (cyl too tall)
else → DoStepUp, on failure → step_up_slide
For the cottage door's foot cyl (h=0.20m, r=0.10m) at standing height,
cyl_clearance = 0.30m and player step_up_height = 0.60m, so the sphere
steps over the cyl easily — no radial push-out.
Pre-fix bug (live trace door-phantom.utf8.log 2026-05-25 PM):
when the player slid along the closed cottage door's slab face, the
foot cyl fired Slid with radial outward push at the door's middle X
(cn=(0.64,0.77,0) etc.) — a "phantom collision" that broke the slide.
Cause: A6.P5's cellSet expansion made the door reliably visible from
all approach angles, exposing this pre-existing behavior. Pre-A6.P5
the cyl wasn't visible from many approach angles so the phantom rarely
fired; the underlying mismatch with retail was always there.
Fix: in CylinderCollision, when oi.Contact && !sp.StepUp && !sp.StepDown
and engine is non-null, compute cyl_clearance, and if step_up_height
allows it, call DoStepUp with the cyl's radial collision normal. On
success the sphere is repositioned past the cyl (returns OK). On
failure (no walkable surface beyond — e.g., a wall behind the cyl),
fall back to StepUpSlide which uses SlideSphereInternal's crease
projection — smoother tangent slide than the radial push.
Conformance:
- All A6P5 unit tests + Path 5 tests + Apparatus_50cmOffCenter_* +
Apparatus_DeadCenter_* + Directional_OutsideIn/InsideOut + issue #98
LiveCompare_FirstCap_FixClosesCottageFloorCap pass in isolation.
- Full Core suite failure count unchanged (17 baseline → 17 with-fix);
diff is documented static-leak flakiness, no real regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3b1ae83931
commit
3d4e63f9c8
1 changed files with 66 additions and 6 deletions
|
|
@ -2383,7 +2383,7 @@ public sealed class Transition
|
|||
// ACE: Sphere.IntersectsSphere handles CylSphere objects via
|
||||
// the same 6-path dispatcher. For now we keep the swept-sphere
|
||||
// cylinder test which matches the retail CylSphere behavior.
|
||||
result = CylinderCollision(obj, sp);
|
||||
result = CylinderCollision(obj, sp, engine);
|
||||
|
||||
// A6.P4 door investigation (2026-05-24): log every Cylinder
|
||||
// shadow tested. Tells us whether the broadphase returned
|
||||
|
|
@ -2521,13 +2521,33 @@ public sealed class Transition
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cylinder collision test for CylSphere objects (tree trunks, rock pillars, NPCs).
|
||||
/// Applies a horizontal wall-slide response when the sphere overlaps the
|
||||
/// cylinder, matching the BSP path 5/6 response for consistent behavior.
|
||||
/// Cylinder collision test for CylSphere objects (tree trunks, rock pillars, NPCs,
|
||||
/// door foot-colliders). For Contact-grounded movers, attempts to step over short
|
||||
/// cylinders (retail-faithful CCylSphere::step_sphere_up). For airborne movers,
|
||||
/// movers already stepping, or cylinders too tall to step over, applies a
|
||||
/// horizontal wall-slide response.
|
||||
///
|
||||
/// <para>
|
||||
/// A6.P6 (2026-05-25): the step-over path matches retail's
|
||||
/// <c>CCylSphere::step_sphere_up</c> at
|
||||
/// <c>acclient_2013_pseudo_c.txt:324516-324538</c>. The door's foot
|
||||
/// cylinder (h=0.20m, r=0.10m) is too tall for the static slide to
|
||||
/// produce smooth sliding along the slab — the radial push-out
|
||||
/// fires as a "phantom collision" at the door's center when the
|
||||
/// sphere is touching the slab face and the cyl is just within reach.
|
||||
/// Retail steps the sphere over the cyl (succeeds when
|
||||
/// <c>step_up_height >= sphere.radius + cyl.height - offset.z</c>),
|
||||
/// which lets the sphere walk past the cyl without the radial push.
|
||||
/// On step-up failure (cyl too tall, no walkable surface beyond),
|
||||
/// falls back to <c>step_up_slide</c> — the same crease-projection
|
||||
/// slide the BSP path uses, which produces smoother behavior than
|
||||
/// the radial push.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private TransitionState CylinderCollision(ShadowEntry obj, SpherePath sp)
|
||||
private TransitionState CylinderCollision(ShadowEntry obj, SpherePath sp, PhysicsEngine engine)
|
||||
{
|
||||
var ci = CollisionInfo;
|
||||
var oi = ObjectInfo;
|
||||
Vector3 sphereCurrPos = sp.GlobalCurrCenter[0].Origin;
|
||||
Vector3 sphereCheckPos = sp.GlobalSphere[0].Origin;
|
||||
float sphRadius = sp.GlobalSphere[0].Radius;
|
||||
|
|
@ -2550,7 +2570,7 @@ public sealed class Transition
|
|||
if (distSqCheck >= combinedRSq)
|
||||
return TransitionState.OK; // not overlapping at check position
|
||||
|
||||
// ─── Overlap detected: apply wall-slide ─────────────────────
|
||||
// ─── Overlap detected ─────────────────────────────────────
|
||||
// Horizontal outward normal from the cylinder axis to the sphere
|
||||
// check position. For the degenerate case where the sphere center
|
||||
// is exactly on the axis, use the movement direction as a fallback
|
||||
|
|
@ -2571,6 +2591,46 @@ public sealed class Transition
|
|||
collisionNormal = new Vector3(dxCheck / distCheck, dyCheck / distCheck, 0f);
|
||||
}
|
||||
|
||||
// A6.P6 (2026-05-25): retail-faithful CCylSphere::step_sphere_up for
|
||||
// Contact-grounded movers. acclient_2013_pseudo_c.txt:324516-324538.
|
||||
//
|
||||
// Retail check: step_up_height must clear (sphere.radius + cyl.height
|
||||
// - offset.z) where offset.z is sphere center Z minus cyl low_pt Z.
|
||||
// Geometrically: the height we need to lift the sphere to clear the
|
||||
// cyl's top, less the sphere center's current height above the cyl
|
||||
// base, equals cyl top minus sphere bottom (positive when sphere
|
||||
// currently below cyl top).
|
||||
//
|
||||
// For the door's foot cyl (h=0.20m, sphere radius 0.48m, step_up 0.60m)
|
||||
// at standing height (offset.z ~0.38m): cyl_clearance =
|
||||
// 0.48 + 0.20 - 0.38 = 0.30m, step_up_height = 0.60m → step over OK.
|
||||
if (oi.Contact && !sp.StepUp && !sp.StepDown && engine is not null)
|
||||
{
|
||||
float offsetZ = sphereCheckPos.Z - obj.Position.Z;
|
||||
float cylClearance = sphRadius + cylTop - offsetZ;
|
||||
|
||||
if (oi.StepUpHeight >= cylClearance)
|
||||
{
|
||||
// Try step-up over the cyl (DoStepUp probes upward by
|
||||
// step_up_height, then step-down for walkable surface).
|
||||
// On success: sphere is repositioned past/over the cyl,
|
||||
// ContactPlane updated, returns OK.
|
||||
if (DoStepUp(collisionNormal, engine))
|
||||
return TransitionState.OK;
|
||||
|
||||
// Step-up failed — sphere couldn't find a walkable surface
|
||||
// beyond the cyl (e.g., a wall behind it). Fall back to
|
||||
// step_up_slide which uses the SlideSphereInternal crease
|
||||
// projection — smoother than the radial push-out below
|
||||
// because it follows the contact-plane / cyl-normal crease
|
||||
// direction.
|
||||
return sp.StepUpSlide(this);
|
||||
}
|
||||
// else: cyl too tall to step over — fall through to radial slide
|
||||
}
|
||||
|
||||
// ─── Fallback: airborne / non-Contact / cyl-too-tall — wall-slide ───
|
||||
|
||||
// Wall-slide position (in world space):
|
||||
// curr = sphereCurrPos (pre-step)
|
||||
// movement = sphMovement
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue