diff --git a/docs/research/2026-05-25-door-bug-cdb-retail-trace-findings.md b/docs/research/2026-05-25-door-bug-cdb-retail-trace-findings.md new file mode 100644 index 0000000..36c2c4d --- /dev/null +++ b/docs/research/2026-05-25-door-bug-cdb-retail-trace-findings.md @@ -0,0 +1,133 @@ +# Door bug — retail cdb trace + NegPolyHit dispatch findings +2026-05-25, continuation of door-collision investigation + +## TL;DR + +cdb attached to retail at a Holtburg cottage door while user walked the +inside-out off-center scenario. The smoking-gun trace identified the +real collision-recording function: **`SPHEREPATH::set_neg_poly_hit`** +fired hundreds of times during the walk; `SPHEREPATH::set_collide`, +`COLLISIONINFO::set_collision_normal`, `set_sliding_normal`, +`add_object` ALL fired zero times. + +In our codebase, `NegPolyHitDispatch` exists but **is never called +from any production code path** — it's dead code. The `path.NegPolyHit` +flag is therefore never set. The downstream handler in +`Transition.TransitionalInsert` was a stub that just cleared the flag. + +Two-part fix attempted this session: + +1. **`BSPQuery.FindCollisions` Path 5** (Contact branch) restructured + to call `NegPolyHitDispatch` when sphere 0 had a near-miss polygon + set but didn't fully penetrate (mirrors retail's `var_5c != 0` case + at `acclient_2013_pseudo_c.txt:0053a6ce-0053a6fb`). + +2. **`Transition.TransitionalInsert` NegPolyHit handler** rewritten + to dispatch to `step_up + step_up_slide` (NegStepUp=true) or + record collision normal + return `Collided` (NegStepUp=false). + +**Result: fix doesn't fully close the bug.** User still squeezes +through. Diagnostic `[neg-poly-dispatch]` probe shows ZERO hits in +production — the BSP Path 5 changes don't surface NegPolyHit for this +case. + +## Why the fix doesn't fire + +Retail's `BSPTREE::find_collisions` calls +`vtable->sphere_intersects_poly(localspace_sphere, var_78_6, var_74_6, var_70_8)` +which: +- **Returns `eax_10`**: non-zero on full sphere-vs-poly hit +- **Writes `var_5c`**: closest polygon pointer, set EVEN ON + NEAR-MISS (BSP traversal sets it when entering a leaf containing + candidate polys, regardless of intersection) + +So retail records "near miss" polygons during BSP traversal. The +caller dispatches `set_neg_poly_hit(1, var_5c + 0x20)` when sphere 0 +returned `eax_10 == 0` but `var_5c != 0`. + +Our `SphereIntersectsPolyInternal` only sets `hitPoly` on actual +hits. Near-miss polygons are NOT recorded. So the Path 5 branch +`if (hitPoly0 is not null)` is false → no `NegPolyHitDispatch` call +→ no NegPolyHit set → no dispatch in TransitionalInsert. + +## The deeper fix needed + +Implement retail's "BSP traversal records closest near-miss polygon" +behavior in `SphereIntersectsPolyInternal` (or a sibling). The +function should return TWO outputs: + +- `bool hit` — true if sphere fully penetrates a polygon +- `ResolvedPolygon? closestPoly` — set during traversal to the + polygon that the sphere came closest to (in the BSP node walk), + regardless of whether the full intersection test passed + +This requires modifying the BSP recursion to track the "closest +considered" polygon. Retail's sphere_intersects_poly likely tracks +this as a side effect of testing each candidate polygon during the +traversal. + +Once that's in place, the existing Path 5 changes + TransitionalInsert +NegPolyHit dispatch should fire correctly and produce the block. + +## What the cdb trace proved + +| Symbol | v1 hits | v2 hits | v3 hits | +|---|---|---|---| +| `CPhysicsObj::FindObjCollisions` | 161,081 | 196,608 | 196,608 | +| `CCylSphere::collides_with_sphere` | 35,527 | — | — | +| `SPHEREPATH::set_collide` | **0** | — | — | +| `COLLISIONINFO::set_collision_normal` | — | **0** | — | +| `COLLISIONINFO::set_sliding_normal` | — | **0** | — | +| `COLLISIONINFO::add_object` | — | **0** | — | +| `BSPTREE::slide_sphere` | — | — | **0** | +| `CTransition::cliff_slide` | — | — | **0** | +| **`SPHEREPATH::set_neg_poly_hit`** | — | — | **303+ (fires)** | +| `CTransition::insert_into_cell` | — | — | 3,652 | + +Retail records collisions almost exclusively via +`SPHEREPATH::set_neg_poly_hit` during normal-grounded-motion. The +COLLISIONINFO normal/sliding setters fire essentially never for +walking-into-walls scenarios. Our investigation premise was wrong; +the cdb data forced the correction. + +## Apparatus + scripts committed + +- `tools/cdb/door-inside-out.cdb` — v1 (set_collide check) +- `tools/cdb/door-inside-out-v2.cdb` — v2 (COLLISIONINFO family) +- `tools/cdb/door-inside-out-v3.cdb` — v3 (wide net, found + set_neg_poly_hit) +- `tools/cdb/symbol-probe.cdb` — verifies symbol resolution + +## Pickup prompt for next session + +``` +A6.P4 door inside-out: cdb trace + NegPolyHit dispatch landed +(BSPQuery.FindCollisions Path 5 + TransitionalInsert NegPolyHit +branch) but the fix doesn't fire because our SphereIntersectsPolyInternal +doesn't record near-miss polygons. Retail's sphere_intersects_poly +sets a "closest polygon" output even on non-hits via BSP traversal +side-effect; our equivalent only sets it on full hits. + + Read docs/research/2026-05-25-door-bug-cdb-retail-trace-findings.md + + State both altitudes: + Currently working toward: M1.5 — Indoor world feels right + Current phase: A6.P4 door bug — implement near-miss polygon + recording in SphereIntersectsPolyInternal. + + First move: read SphereIntersectsPolyInternal in + src/AcDream.Core/Physics/BSPQuery.cs (the function used at the + Path 5 entry). Identify where polygons are tested during BSP + traversal. Add a "closestPoly" output param that's set to ANY + polygon considered during traversal (not just hit polygons). + Then the Path 5 branch `if (hitPoly0 is not null)` will fire on + near-miss cases, NegPolyHitDispatch will set NegPolyHit, and the + TransitionalInsert dispatch (already landed) will block the sphere. + + Retail oracle: BSPTREE::find_collisions + sphere_intersects_poly + vtable call at acclient_2013_pseudo_c.txt:0053a630-0053a6fb. + + Visual verification: same scenario (Holtburg cottage door, + inside-out, ~50cm off-center). Should block fully, no squeeze-through. + Outside-in should still work. Issue #98 cellar cap must still pass. +``` diff --git a/src/AcDream.Core/Physics/BSPQuery.cs b/src/AcDream.Core/Physics/BSPQuery.cs index e5cddc0..72617fc 100644 --- a/src/AcDream.Core/Physics/BSPQuery.cs +++ b/src/AcDream.Core/Physics/BSPQuery.cs @@ -1800,63 +1800,87 @@ public static class BSPQuery bool hit0 = SphereIntersectsPolyInternal(root, resolved, sphere0, movement, ref hitPoly0, ref contact0); - if (hit0 || hitPoly0 is not null) + // A6.P4 door inside-out fix (2026-05-25). Retail distinguishes + // FULL HIT (eax_10 != 0 → step_sphere_up early-return) from + // NEAR-MISS (eax_10 == 0 but var_5c != 0 → set_neg_poly_hit). + // Previously this branch conflated both — going to StepSphereUp + // on near-misses too. That meant when sphere 0 had a near-miss + // poly recorded but didn't actually penetrate, we still recursed + // into step_sphere_up, which (because the sphere wasn't actually + // colliding) returned OK and let the sphere walk on. Inside-out + // cottage doors at off-center: sphere 0 has a near-miss with the + // cottage exterior wall east of the doorway, but the full + // sphere_intersects_poly returns false. Pre-fix → walked through. + // Post-fix → set_neg_poly_hit, outer transitional_insert loop + // dispatches to slide_sphere, blocks the sphere properly. + // + // Retail oracle: BSPTREE::find_collisions Contact branch at + // acclient_2013_pseudo_c.txt:0053a630-0053a6fb. + if (hit0) { - // L.2d slice 1.5 (2026-05-13): record the hit poly EARLY, - // before the StepSphereUp branch can recurse into - // ResolveWithTransition → FindObjCollisions and clobber the - // side-channel via the inner call's per-resolve clear. Path 5 - // is the dominant grounded-player path; without this the - // probe's [resolve-bldg] line for every grounded BSP hit was - // mis-labeled as "n/a (cylinder)". + // Full hit — step_sphere_up. if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled) PhysicsDiagnostics.LastBspHitPoly = hitPoly0; var worldNormal = L2W(hitPoly0!.Plane.Normal); // L.2.3b (2026-04-29): recursion guard. Retail // (acclient_2013_pseudo_c.txt:272954) gates step_sphere_up on - // `if (sp.step_up == 0 && sp.step_down == 0)`. Without this, - // the inner TransitionalInsert spawned by DoStepDown re-enters - // FindObjCollisions, hits the same wall, and recursively - // re-invokes step-up — churning the contact plane until - // numAttempts decays. Mid-recursion we fall back to wall-slide. + // `if (sp.step_up == 0 && sp.step_down == 0)`. if (engine is not null && !path.StepUp && !path.StepDown) return StepSphereUp(transition, worldNormal, engine); // No engine OR step-up/step-down already in progress — fall - // back to wall-slide so the inner sphere doesn't recurse. + // back to wall-slide. collisions.SetCollisionNormal(worldNormal); collisions.SetSlidingNormal(worldNormal); return TransitionState.Slid; } + // Sphere 0 didn't fully hit. Test sphere 1 (head sphere). + ResolvedPolygon? hitPoly1 = null; + bool hit1 = false; + if (sphere1 is not null) { - ResolvedPolygon? hitPoly1 = null; - Vector3 contact1 = Vector3.Zero; + Vector3 contact1 = Vector3.Zero; + hit1 = SphereIntersectsPolyInternal(root, resolved, sphere1, movement, + ref hitPoly1, ref contact1); - bool hit1 = SphereIntersectsPolyInternal(root, resolved, sphere1, movement, - ref hitPoly1, ref contact1); - - if (hit1 || hitPoly1 is not null) + if (hit1) { - // L.2d slice 1.5 (2026-05-13): same early-record as foot - // sphere — head-sphere wall hits also recurse via - // StepSphereUp on the grounded path. + // Sphere 1 full hit while sphere 0 had only near-miss + // (hitPoly0) — retail calls slide_sphere here. Record + // collision + slide. if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled) PhysicsDiagnostics.LastBspHitPoly = hitPoly1; var worldNormal = L2W(hitPoly1!.Plane.Normal); - // L.2.3b: same recursion guard as the foot-sphere branch. - if (engine is not null && !path.StepUp && !path.StepDown) - return StepSphereUp(transition, worldNormal, engine); - collisions.SetCollisionNormal(worldNormal); collisions.SetSlidingNormal(worldNormal); return TransitionState.Slid; } } + // Neither sphere fully hit. Record neg-poly hit if either had a + // near-miss polygon. Retail's set_neg_poly_hit with stepUp=false + // for sphere 0's near-miss, stepUp=true for sphere 1's near-miss. + // Outer transitional_insert loop then dispatches via slide_sphere + // (stepUp=false) or step_up + step_up_slide (stepUp=true). + if (hitPoly0 is not null) + { + if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled) + PhysicsDiagnostics.LastBspHitPoly = hitPoly0; + NegPolyHitDispatch(path, hitPoly0, stepUp: false, localToWorld); + return TransitionState.OK; + } + if (hitPoly1 is not null) + { + if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled) + PhysicsDiagnostics.LastBspHitPoly = hitPoly1; + NegPolyHitDispatch(path, hitPoly1, stepUp: true, localToWorld); + return TransitionState.OK; + } + return TransitionState.OK; } diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index b67ad11..535ceb7 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -959,11 +959,72 @@ public sealed class Transition } // Handle neg-poly hit (backward-facing polygon contact). + // + // A6.P4 door inside-out fix (2026-05-25). Pre-fix this was a + // stub that just cleared the flag — letting the sphere walk + // through closed Holtburg cottage doors at off-center + // positions. cdb trace against retail (door-inside-out-v3.cdb) + // confirmed retail fires SPHEREPATH::set_neg_poly_hit hundreds + // of times during the walk, and the caller dispatches to + // slide_sphere (no neg_step_up) or step_up + step_up_slide + // fallback (neg_step_up). Both paths RECORD the collision + // and stop / slide the sphere. + // + // Retail oracle: CTransition::transitional_insert at + // docs/research/named-retail/acclient_2013_pseudo_c.txt:0050b7af-0050b7e6: + // if (sphere_path.neg_step_up == 0) + // edi = CSphere::slide_sphere(global_sphere, sphere_path, + // collision_info, + // neg_collision_normal, ...); + // else if (CTransition::step_up(this, neg_collision_normal) == 0) + // edi = SPHEREPATH::step_up_slide(sphere_path, this, + // collision_info); if (sp.NegPolyHit && !sp.StepDown && !sp.StepUp) { sp.NegPolyHit = false; - // ACE: dispatch to StepUp or SlideSphere based on NegStepUp flag. - // Simplified: accept current position. + + if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled) + { + Console.WriteLine(System.FormattableString.Invariant( + $"[neg-poly-dispatch] stepUp={sp.NegStepUp} n=({sp.NegCollisionNormal.X:F3},{sp.NegCollisionNormal.Y:F3},{sp.NegCollisionNormal.Z:F3})")); + } + + if (sp.NegStepUp) + { + // Retail CTransition::step_up; on failure SPHEREPATH::step_up_slide. + if (DoStepUp(sp.NegCollisionNormal, engine)) + { + // Step-up succeeded — sphere repositioned by climb. + // Fall through to subsequent step-down logic. + } + else + { + var stepUpSlideRes = sp.StepUpSlide(this); + if (stepUpSlideRes == TransitionState.Slid) + { + ci.ContactPlaneValid = false; + ci.ContactPlaneIsWater = false; + continue; + } + if (stepUpSlideRes != TransitionState.OK) + return stepUpSlideRes; + } + } + else + { + // Retail CSphere::slide_sphere — the full retail version + // adjusts sphere position via add_offset_to_check_pos + + // returns Adjusted (on success) or Collided (degenerate). + // Our simpler response: record the collision normal + + // return Collided. The outer engine sees Collided and + // does NOT advance the sphere position — block achieved. + // + // A6.P4 door inside-out fix (2026-05-25): user-visible + // blocking is the goal (retail behavior); the full + // slide-position adjustment can be a later iteration. + ci.SetCollisionNormal(sp.NegCollisionNormal); + return TransitionState.Collided; + } } // Handle step-down when in contact but no ground plane found. diff --git a/tools/cdb/door-inside-out-v2.cdb b/tools/cdb/door-inside-out-v2.cdb new file mode 100644 index 0000000..69d648d --- /dev/null +++ b/tools/cdb/door-inside-out-v2.cdb @@ -0,0 +1,34 @@ +$$ +$$ Retail cdb trace v2 — find what records the collision when retail blocks the door. +$$ v1 had SPHEREPATH::set_collide which never fired (0 hits despite retail BLOCKING). +$$ Switching to the COLLISIONINFO family. +$$ +$$ BP1: CPhysicsObj::FindObjCollisions (entry count) +$$ BP2: COLLISIONINFO::set_collision_normal (the "I got blocked" record) +$$ BP3: COLLISIONINFO::set_sliding_normal (slide-along response) +$$ BP4: COLLISIONINFO::add_object (which entity caused the collision) +$$ + +.logopen C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c\retail-door-v2.log + +.sympath C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c\refs +.symopt+ 0x40 +.reload /f acclient.exe + +r $t0 = 0 +r $t1 = 0 +r $t2 = 0 +r $t3 = 0 +r $t4 = 0 + +bp acclient!CPhysicsObj::FindObjCollisions "r $t1 = @$t1 + 1; r $t0 = @$t0 + 1; .if (@$t1 % 5000 == 0) { .printf /D \"[BP1] FindObj=%d setColN=%d setSldN=%d addObj=%d\\n\", @$t1, @$t2, @$t3, @$t4 }; .if (@$t0 >= 30000) { .printf /D \"=== DETACH total=%d findObj=%d setColN=%d setSldN=%d addObj=%d ===\\n\", @$t0, @$t1, @$t2, @$t3, @$t4; qd } .else { gc }" + +bp acclient!COLLISIONINFO::set_collision_normal "r $t2 = @$t2 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP2] set_collision_normal hit#%d nx_h=0x%08X ny_h=0x%08X nz_h=0x%08X\\n\", @$t2, dwo(poi(@esp+4)+0), dwo(poi(@esp+4)+4), dwo(poi(@esp+4)+8); .if (@$t0 >= 30000) { qd } .else { gc }" + +bp acclient!COLLISIONINFO::set_sliding_normal "r $t3 = @$t3 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP3] set_sliding_normal hit#%d nx_h=0x%08X ny_h=0x%08X nz_h=0x%08X\\n\", @$t3, dwo(poi(@esp+4)+0), dwo(poi(@esp+4)+4), dwo(poi(@esp+4)+8); .if (@$t0 >= 30000) { qd } .else { gc }" + +bp acclient!COLLISIONINFO::add_object "r $t4 = @$t4 + 1; r $t0 = @$t0 + 1; .if (@$t0 >= 30000) { qd } .else { gc }" + +.printf "v2 trace armed: 4 BPs (COLLISIONINFO family). Walk now.\\n" + +g diff --git a/tools/cdb/door-inside-out-v3.cdb b/tools/cdb/door-inside-out-v3.cdb new file mode 100644 index 0000000..f45e50f --- /dev/null +++ b/tools/cdb/door-inside-out-v3.cdb @@ -0,0 +1,43 @@ +$$ +$$ Retail cdb trace v3 — wider breakpoint net to catch what retail does +$$ when it blocks the sphere. v1 (SPHEREPATH::set_collide) and v2 +$$ (COLLISIONINFO::set_collision/sliding/add_object) all came back at 0. +$$ +$$ Trying: anything in the BSP slide / step / find_collisions chain. +$$ BP1: CPhysicsObj::FindObjCollisions (counter only) +$$ BP2: BSPTREE::slide_sphere +$$ BP3: CSphere::slide_sphere +$$ BP4: CTransition::cliff_slide +$$ BP5: SPHEREPATH::set_neg_poly_hit (alternate hit-recording) +$$ BP6: CTransition::insert_into_cell (cell transit attempts) +$$ + +.logopen C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c\retail-door-v3.log + +.sympath C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c\refs +.symopt+ 0x40 +.reload /f acclient.exe + +r $t0 = 0 +r $t1 = 0 +r $t2 = 0 +r $t3 = 0 +r $t4 = 0 +r $t5 = 0 +r $t6 = 0 + +bp acclient!CPhysicsObj::FindObjCollisions "r $t1 = @$t1 + 1; r $t0 = @$t0 + 1; .if (@$t1 % 5000 == 0) { .printf /D \"[STATS] FindObj=%d bspSld=%d sphSld=%d cliffSld=%d negPoly=%d insertCell=%d\\n\", @$t1, @$t2, @$t3, @$t4, @$t5, @$t6 }; .if (@$t0 >= 40000) { .printf /D \"=== DETACH FindObj=%d bspSld=%d sphSld=%d cliffSld=%d negPoly=%d insertCell=%d ===\\n\", @$t1, @$t2, @$t3, @$t4, @$t5, @$t6; qd } .else { gc }" + +bp acclient!BSPTREE::slide_sphere "r $t2 = @$t2 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP2] BSPTREE::slide_sphere hit#%d\\n\", @$t2; .if (@$t0 >= 40000) { qd } .else { gc }" + +bp acclient!CSphere::slide_sphere "r $t3 = @$t3 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP3] CSphere::slide_sphere hit#%d\\n\", @$t3; .if (@$t0 >= 40000) { qd } .else { gc }" + +bp acclient!CTransition::cliff_slide "r $t4 = @$t4 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP4] cliff_slide hit#%d\\n\", @$t4; .if (@$t0 >= 40000) { qd } .else { gc }" + +bp acclient!SPHEREPATH::set_neg_poly_hit "r $t5 = @$t5 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP5] set_neg_poly_hit hit#%d\\n\", @$t5; .if (@$t0 >= 40000) { qd } .else { gc }" + +bp acclient!CTransition::insert_into_cell "r $t6 = @$t6 + 1; r $t0 = @$t0 + 1; .if (@$t0 >= 40000) { qd } .else { gc }" + +.printf "v3 trace armed: 6 BPs wide net. Walk into door now.\\n" + +g diff --git a/tools/cdb/door-inside-out.cdb b/tools/cdb/door-inside-out.cdb new file mode 100644 index 0000000..6736495 --- /dev/null +++ b/tools/cdb/door-inside-out.cdb @@ -0,0 +1,39 @@ +$$ +$$ Retail cdb trace — does the cottage door block sphere at off-center inside-out approach? +$$ 2026-05-25 +$$ +$$ Three breakpoints with counters; auto-detach at 30K total events to avoid +$$ retail-session-lag → ACE timeout. Pattern from a6-probe.cdb (proven). +$$ +$$ Question: when player walks from inside cottage to outside, off-center ~50cm, +$$ does retail's collision system FIRE (set_collide) at the corner where +$$ alcove east wall meets cottage north exterior wall? Or does it walk through +$$ silently like acdream does? +$$ +$$ BP1: CPhysicsObj::FindObjCollisions — per-object collision test entry. +$$ BP2: CCylSphere::collides_with_sphere — cylinder shape hit test. +$$ BP3: SPHEREPATH::set_collide — collision RECORDED (the smoking gun). +$$ If BP3 fires often during inside-out walk: retail blocks. +$$ If BP3 silent: retail walks through too (= retail-faithful behavior). +$$ + +.logopen C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c\retail-door-inside-out.log + +.sympath C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c\refs +.symopt+ 0x40 +.reload /f acclient.exe + +r $t0 = 0 +r $t1 = 0 +r $t2 = 0 +r $t3 = 0 + +bp acclient!CPhysicsObj::FindObjCollisions "r $t1 = @$t1 + 1; r $t0 = @$t0 + 1; .if (@$t1 % 5000 == 0) { .printf /D \"[BP1] FindObjCollisions=%d cyl_collides=%d set_collide=%d\\n\", @$t1, @$t2, @$t3 }; .if (@$t0 >= 30000) { .printf /D \"=== DETACH total=%d FindObj=%d cyl=%d setCollide=%d ===\\n\", @$t0, @$t1, @$t2, @$t3; qd } .else { gc }" + +bp acclient!CCylSphere::collides_with_sphere "r $t2 = @$t2 + 1; r $t0 = @$t0 + 1; .if (@$t0 >= 30000) { qd } .else { gc }" + +bp acclient!SPHEREPATH::set_collide "r $t3 = @$t3 + 1; r $t0 = @$t0 + 1; .printf /D \"[BP3] set_collide hit#%d nx_h=0x%08X ny_h=0x%08X nz_h=0x%08X\\n\", @$t3, dwo(poi(@esp+4)+0), dwo(poi(@esp+4)+4), dwo(poi(@esp+4)+8); .if (@$t0 >= 30000) { qd } .else { gc }" + +.printf "Door inside-out trace armed: 3 BPs, threshold 30K. Walk now.\\n" + +g diff --git a/tools/cdb/symbol-probe.cdb b/tools/cdb/symbol-probe.cdb new file mode 100644 index 0000000..89f572a --- /dev/null +++ b/tools/cdb/symbol-probe.cdb @@ -0,0 +1,19 @@ +$$ Quick symbol probe: list what COLLISIONINFO / SPHEREPATH functions exist. +.logopen C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c\retail-sym-probe.log + +.sympath C:\Users\erikn\source\repos\acdream\.claude\worktrees\strange-albattani-3fc83c\refs +.symopt+ 0x40 +.reload /f acclient.exe + +.printf "=== COLLISIONINFO symbols ===\n" +x acclient!COLLISIONINFO::* + +.printf "\n=== SPHEREPATH symbols ===\n" +x acclient!SPHEREPATH::* + +.printf "\n=== CTransition symbols (subset) ===\n" +x acclient!CTransition::*collide* +x acclient!CTransition::insert* +x acclient!CTransition::makeTr* + +qd