acdream/docs/research/2026-07-06-adjust-to-plane-pseudocode.md
Erik f10fe4e96c fix #180 (root): retail adjust_to_plane - PathClipped stops now resolve to the contact point, not a step boundary
The autonomous visual loop on the stateful-sought camera exposed the true
root of the #176 stripes: a ~19Hz SAWTOOTH. The sought re-extends ~3mm/frame
and the sweep silently passes while the 0.3m viewer sphere presses up to
~0.25m past the wall plane, then clips a whole transition step (~0.27m) back.
Headless replay against the real Facility Hub corridor BSP (0x8A020164, the
captured ray) reproduced it exactly: pre-fix, embedded targets passed
unclipped and the first detection stopped at the PREVIOUS STEP BOUNDARY,
tracking the target (eyeBack = s - 0.27).

Root cause: BSPQuery.AdjustToPlane - copied from ACE's BSPTree.cs port -
was structurally inverted and ALWAYS returned false (the touchTime==1 branch
re-placed the sphere at the unchanged check position; touchTime<1 iterated
doing nothing; the <0.02 convergence exit returned false). With the
PerfectClip exact-contact machinery dead, CollideWithPt always fell to the
bare Collided path and the transition reverted the colliding step whole.
ACE never noticed: PERFECT_CLIP (0x40) is a client camera flag the server
never exercises (feedback_bn_decomp_field_names class 3 - the retail binary
outranks ACE in branches ACE never runs). The pre-stateful camera flipped
1-step vs 2-step backoffs on mm drift - the measured pulledIn 0.27 <-> 0.53
of the original #176 strobe was step quantization all along.

Rewritten per the retail binary (pseudocode doc
docs/research/2026-07-06-adjust-to-plane-pseudocode.md):

- BSPTREE::adjust_to_plane (0x00539bf0): clearTime/hitTime bounds (0.0/1.0),
  Phase 1 walks plane-touch times re-testing the whole tree (the tree test
  feeds a DIFFERENT blocking poly back into the next iteration), Phase 2
  binary-searches with the SHARED iteration counter, window < 0.02 =
  CONVERGED, final commit = last known-clear time. Only failure = Phase-1
  exhaustion.
- CPolygon::adjust_sphere_to_poly (0x00538170): early-out = plane-band test
  at the START position (was: precise-poly test at the check position);
  touch side = sign(dpPos)*radius (was: hard-coded -radius; ACE misdecoded
  it as movement.LengthSquared() <= r^2); result unclamped per retail.

Replay pin Issue180CorridorSweepHysteresisReplayTests: short-of-touch
targets pass, past-touch targets always clip, and the clipped stop is the
CONSTANT surface-contact point (eyeBack 1.609 across the band; spread
< 0.03m) instead of tracking the target.

Suites green (Core 2600+2skip / App 729+2skip / UI 425 / Net 385).
Pending: visual-loop re-verify + the user gate (#180 + #176 re-gate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 17:20:46 +02:00

6.3 KiB
Raw Blame History

BSPTREE::adjust_to_plane + CPolygon::adjust_sphere_to_poly — pseudocode (#180 sawtooth fix)

Source: docs/research/named-retail/acclient_2013_pseudo_c.txt. Read 2026-07-06 while root-causing the #180 residual (the camera-sweep sawtooth: PathClipped stops landing a whole transition step short of the wall contact).

Function VA pseudo-C line
BSPTREE::adjust_to_plane 0x00539bf0 323440
BSPTREE::collide_with_pt (caller) 0x0053a090 323615
CPolygon::adjust_sphere_to_poly 0x00538170 321999

⚠️ ACE'S PORT OF BOTH FUNCTIONS IS WRONG — do not re-import it

ACE BSPTree.cs:43-85 adjust_to_plane + Polygon.cs:99-114 adjust_sphere_to_poly decode the retail x87 control flow incorrectly, and the result is a function that always returns false (trace it: touchTime==1 re-places the sphere at t=1 — the unchanged check position — and re-tests; any touchTime<1 iterates 15 times doing nothing; the <0.02 convergence exit returns false — inverted). ACE never noticed because PERFECT_CLIP (0x40) is a viewer/camera flag the SERVER never meaningfully exercises. acdream's pre-fix BSPQuery.AdjustToPlane/AdjustSphereToPoly copied ACE's shape and inherited the dead machinery — the memory lesson feedback_bn_decomp_field_names class 3 (ACE mush-decode wrong in branches ACE never runs; the retail binary outranks it).

Consequence (the #180 sawtooth / the ORIGINAL #176 strobe): with AdjustToPlane always failing, collide_with_pt's PerfectClip branch always fell to the bare Collided return → the transition reverted the colliding step to the PREVIOUS step boundary. A PathClipped camera sweep therefore stopped a whole sub-step (≤ 1 sphere radius, ~0.250.3 m) short of the actual contact — quantized to step boundaries. The pre-fix full-boom camera flipped between 1-step and 2-step backoffs on mm input drift (the measured pulledIn 0.27 ↔ 0.53 = one step vs two steps of the ~1.6 m/6-step sweep); the stateful-sought camera turned it into a re-extend/clip-0.27 m sawtooth at ~19 Hz. Same root: the stop never resolved to the contact point.

CPolygon::adjust_sphere_to_poly — 0x00538170

Returns the parametric time along movement at which the SPHERE SURFACE touches the polygon's plane, approaching from the side the start position is on. (The retail tail is x87 compare-bit mush; the reconstruction below is the only shape consistent with the visible operand loads + the caller's use.)

adjust_sphere_to_poly(this, CSphere* sphere, Vector3 curPos, Vector3 movement) -> float:
    dpPos = dot(plane.N, curPos) + plane.d          // plane distance of the START (0x0053818d)
    if |dpPos| < sphere.radius: return 1.0          // start already within one radius of the
                                                    // plane — no forward touch time (0x005381a4)
    dpMove = dot(plane.N, movement)                 // (0x005381ca)
    if |dpMove| <= 2e-4: return 0.0                 // moving parallel to the plane (0x005381d2)
    r = (dpPos < 0) ? -radius : +radius             // touch on the start's side (0x005381ea)
    return (r - dpPos) / dpMove                     // UNCLAMPED — retail has no [0,1] clamp

Divergences fixed in acdream's port: the early-out was a precise-poly overlap test at the CHECK position (wrong test, wrong endpoint); the touch side was hard-coded -radius; the result was clamped to [0,1]. ACE's version keys the ± sign on movement.LengthSquared() <= r² — a misdecode of the dpPos < 0 compare.

BSPTREE::adjust_to_plane — 0x00539bf0

Finds the closest non-penetrating position along [curPos → checkPos] and commits it into the sphere. Two known-time bounds: clearTime (var_50/var_4c, init 0.0 — the start is clear) and hitTime (var_48/var_44, init 1.0 — the check position hit). ONE iteration counter shared by both phases.

adjust_to_plane(this, CSphere* sphere /*in: at checkPos; out: adjusted*/,
                Vector3 curPos, CPolygon*& hitPoly, Vector3* contactPt) -> success:
    movement  = sphere.center - curPos               // (0x00539c16..0x00539c3c)
    clearTime = 0.0 ; hitTime = 1.0 ; i = 0

    // Phase 1 (0x00539c4d): walk plane-touch times.
    loop:
        t = hitPoly->adjust_sphere_to_poly(sphere, curPos, movement)
        if t == 1.0: break                           // start within radius → pure [0,1] search
        sphere.center = curPos + movement * t        // (0x00539ca8-0x00539cb9)
        if !root->sphere_intersects_poly(sphere, movement, &hitPoly, contactPt):
            clearTime = t                            // touch position clear (0x00539d02)
            break
        // still blocked — by a possibly DIFFERENT poly (the tree test wrote hitPoly)
        i += 1 ; hitTime = t                         // (0x00539cdd-0x00539ce5)
        if i >= 15: return FAIL                      // the only failure exit (0x00539ce9)

    // Phase 2 (0x00539ddb): binary-search [clearTime, hitTime]; i continues.
    do:
        avg = (clearTime + hitTime) * 0.5
        sphere.center = curPos + movement * avg
        if !sphere_intersects_poly(...): clearTime = avg
        else:                            hitTime  = avg
        if hitTime - clearTime < 0.02: break         // CONVERGED (0x00539dca) — NOT a failure
        i += 1
    while i < 15

    sphere.center = curPos + movement * clearTime    // commit the last CLEAR time (0x00539de1)
    return SUCCESS                                   // Phase 2 always succeeds (even unconverged)

Caller (collide_with_pt 0x0053a090, PERFECT_CLIP branch): on success — set_collision_normal(globalized hitPoly.plane), add_offset_to_check_pos( globalize(adjusted original) · scale), return Adjusted(3); on the rare Phase-1 failure — return Collided(2) with NO normal write. acdream's CollideWithPt already had this caller shape correct; only the two functions above were broken.

Verification

Headless replay Issue180CorridorSweepHysteresisReplayTests (real Facility Hub cell 0x8A020164 BSP, the exact captured ray): pre-fix, targets embedded up to 0.25 m into the wall behind the camera passed unclipped and the first detection (s=1.625) stopped a full step back (eyeBack 1.354, tracking the target); post-fix the stop must sit at the surface-contact point (eyeBack ≈ constant ≈ 1.61) for every target past first touch.