# `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.25–0.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.