docs(physics): #345 D0 — implementer's correct STOP + ACE cross-check addendum + round-2 stack-capture script
The synthetic fixtures reproduce our stuck fingerprint while faithfully executing the documented control flow; ACE's independent port shows EdgeSlide reachable only via the OK arm's step-down failure. Together they force the sharper question: retail's insert returns OK per tick where ours returns Adjusted. The round-2 cdb script (stack samples on edge_slide/cliff_slide/step_down + a step_down counter round 1 never had) carries falsifiable predictions written down BEFORE the capture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
064b82e94b
commit
7542cfd3c2
2 changed files with 241 additions and 0 deletions
219
docs/research/2026-08-08-345-d0-branch-pin.md
Normal file
219
docs/research/2026-08-08-345-d0-branch-pin.md
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
# #345 D0 — branch pin, round 2: the routing is retail-faithful; the profile contradiction stands unresolved
|
||||
|
||||
**Order:** implementation session, per `docs/research/2026-08-08-345-fix-contract.md` and
|
||||
`docs/research/2026-08-08-345-mechanism-contract.md`. This session's mandate was to
|
||||
find the branch the *first* D0 pass (`docs/research/2026-08-08-345-pseudocode.md`)
|
||||
missed — the fix contract's premise, from the live cdb profile, is that retail's
|
||||
`transitional_insert` reaches `edge_slide`/`cliff_slide` every tick during the glide
|
||||
while ours dead-loops without ever reaching it.
|
||||
|
||||
**Verdict: the premise's specific mechanism ("Adjusted lets the insert proceed to
|
||||
step-down, ours treats it as retry-from-scratch") is REFUTED by a clean re-trace and
|
||||
by two independent, empirical, byte-for-byte reproductions in a synthetic fixture.
|
||||
Our `TransitionalInsert`/`InsertIntoCell`/`CheckOtherCells` routing is faithful to
|
||||
the decompiled retail control flow for this exact topology (grounded-on-flat,
|
||||
step crosses into too-steep terrain). The contradiction against the live retail
|
||||
profile is real and unresolved — this session SOPS per the fix contract's explicit
|
||||
fallback rather than making an unproven change.**
|
||||
|
||||
## What this session did beyond the first D0 pass
|
||||
|
||||
The first pass (`2026-08-08-345-pseudocode.md`) traced `ValidateWalkable`,
|
||||
`TransitionalInsert`'s switch, `CheckOtherCells`, `ValidateTransition`, and
|
||||
`SetSlidingNormal`, and concluded retail's own algorithm converges to the same
|
||||
zero-yield stop. It left one explicit open question: does retail's
|
||||
`CObjCell::find_cell_list`/our `CellTransit.FindCellSet` broadphase actually
|
||||
include the neighboring too-steep cell as an "other cell" to test, or does the
|
||||
narrower real geometry not reach it at all — a question it said needed a live
|
||||
cdb trace, not more code reading.
|
||||
|
||||
This session did three additional things the first pass did not:
|
||||
|
||||
1. **Read `CTransition::step_down` (0x0050b2a0) in full** — the function
|
||||
`transitional_insert`'s step-down/edge-slide block actually calls before
|
||||
`edge_slide`. This is the literal mechanism the fix contract's hypothesis
|
||||
named. Confirmed: `step_down` sets `sphere_path.step_down=1`, moves
|
||||
`check_pos.z -= arg2`, and calls `transitional_insert(this, 5)` recursively
|
||||
at the LOWERED position — but this whole apparatus is gated behind
|
||||
`transitional_insert`'s own `if (edi == OK_TS)` block (pc:273191,
|
||||
0x0050b787), which is reached **only** when both the primary
|
||||
`insert_into_cell` **and** `check_other_cells` return `OK_TS` for the
|
||||
CURRENT (un-lowered) attempt.
|
||||
2. **Read `CTransition::check_other_cells`'s switch (0x0050ae50, pc:272733-272753)
|
||||
byte-for-byte** — case `COLLIDED_TS`/`ADJUSTED_TS` (2/3) both `return result`
|
||||
immediately, no further cells tried, no state reset. This is a clean 4-way
|
||||
integer switch on a virtual call's return value — no x87, no missing flag
|
||||
tests, no BN garbling. High confidence in this reading.
|
||||
3. **Built two independent synthetic terrain fixtures** (not committed — thrown
|
||||
away after use, see below) and ran them through the *real*
|
||||
`PhysicsEngine.ResolveWithTransition` → `Transition.FindTransitionalPosition`
|
||||
→ `TransitionalInsert` pipeline with the project's own instrumentation
|
||||
(`ACDREAM_PROBE_STEP_WALK`, `ACDREAM_DUMP_TRANSIT_FAIL`,
|
||||
`ACDREAM_PROBE_INDOOR_BSP`, `ACDREAM_DUMP_EDGE_SLIDE`) to get ground truth
|
||||
instead of continuing to hand-derive float comparisons.
|
||||
|
||||
## The two empirical fixtures
|
||||
|
||||
Both: a grounded player-flagged mover (radius 0.48, `ObjectInfoState.IsPlayer |
|
||||
EdgeSlide`, `oi.OnWalkable=true`, `oi.Contact=true`), resting on flat terrain
|
||||
(N=(0,0,1)), given a diagonal (45°-ish) horizontal-only request that lands on a
|
||||
terrain triangle with `N.z=0.6` (< `PhysicsGlobals.FloorZ`=0.6642, i.e.
|
||||
genuinely too steep — matching the fix contract's "N.z≈0.6" scenario).
|
||||
|
||||
**Fixture A — steep triangle in a NEIGHBORING outdoor cell** (request crosses a
|
||||
24 m terrain-cell boundary). Result: the PRIMARY cell's terrain query is
|
||||
out-of-bounds for the new XY (`SampleTerrainWalkableInCell`'s fixed-cell bounds
|
||||
check, `PhysicsEngine.cs:1076-1080` — itself a documented, intentional retail
|
||||
mirror: "retail continues dispatching the captured `CObjCell*`") and passes
|
||||
through as `OK`. `CheckOtherCells` (`TransitionTypes.cs:2999-3010`) then finds
|
||||
the true neighboring cell, calls `ValidateWalkable`, gets the below-surface
|
||||
"too steep" branch (`dist=-0.04`, `sp.StepDown=false` at this point since we're
|
||||
still in the un-lowered outer attempt, `walkable=false` since `N.z(0.6) <
|
||||
WalkableAllowance(FloorZ=0.6642)`), returns `TransitionState.Adjusted`
|
||||
unconditionally (the branch always returns Adjusted regardless of whether the
|
||||
push guard fired — `ValidateWalkable`, `TransitionTypes.cs:3785`).
|
||||
`ApplyOtherCellResult` (`TransitionTypes.cs:3316-3333`) halts on Adjusted.
|
||||
`RunCheckOtherCellsAndAdvance` returns Adjusted. `TransitionalInsert`
|
||||
(`TransitionTypes.cs:2055-2065`) sees `otherState != OK`, sets
|
||||
`transitState = otherState`, and `continue`s the outer retry — **never
|
||||
reaching the `ci.ContactPlaneValid`/step-down gate at line 2248/2252 at all.**
|
||||
Three outer attempts, byte-identical `dist=-0.04000` each time (the query
|
||||
never changes because nothing in the ADJUSTED path perturbs `sp.CheckPos`).
|
||||
`ValidateTransition` then forces `OK`, reverts to the start position: **0%
|
||||
XY yield**, `[transit-fail]` STUCK-TICK fires.
|
||||
|
||||
**Fixture B — steep triangle WITHIN the SAME primary cell** (both start and
|
||||
target inside one 24 m cell's own quad; the too-steep triangle is one of the
|
||||
cell's own two split triangles, `N=(-0.8,0,0.6)`, reached via the diagonal
|
||||
split so no other-cell dispatch is needed at all). Result: even cleaner —
|
||||
`InsertIntoCell`'s OWN inner retry loop (`TransitionTypes.cs:2383-2390`, up to
|
||||
3 attempts) calls `FindPrimaryCellCollisions` → `FindEnvCollisions` →
|
||||
`ValidateWalkable` directly, gets `Adjusted` on every one of its 3 attempts
|
||||
(`dist=-9.74400` identical all 3, deep penetration by design), and returns
|
||||
`Adjusted` from `InsertIntoCell` itself — before `check_other_cells` is even
|
||||
called. `TransitionalInsert`'s outer switch, `case ADJUSTED_TS: { neg_poly_hit
|
||||
= 0; break; }` (`TransitionTypes.cs:2030-2037`), skips the entire `if (edi ==
|
||||
OK_TS)` block. 3 outer attempts × 3 inner attempts = 9 total
|
||||
`[transit-fail-insert]` lines, every one `env=Adjusted`, never reaching
|
||||
`environment=OK`. `DoStepDown`/`EdgeSlideAfterStepDownFailed` are **never
|
||||
called** — zero `edge-slide:` diagnostic lines fire in this fixture's log,
|
||||
confirmed by grep. **0% XY yield**, STUCK-TICK fires, identical fingerprint to
|
||||
Fixture A and to the original captured bug (`spStepDown=False`,
|
||||
`guardPassed=False`, `outcome=Adjusted`, byte-identical `dist` every attempt).
|
||||
|
||||
Both fixtures independently, by two structurally different routes (neighbor
|
||||
cell vs. same cell), reproduce the exact captured #345 fingerprint from
|
||||
`docs/ISSUES.md` — and both do so by faithfully executing the documented
|
||||
retail control flow (`check_other_cells`'s unconditional early return on
|
||||
Adjusted; `transitional_insert`'s post-switch `edi==OK_TS` gate). Neither
|
||||
fixture ever reaches `DoStepDown`/`EdgeSlideAfterStepDownFailed`/`CliffSlide` —
|
||||
which the trap-list machinery (`RetailEdgeResponseOrderingTests.cs`,
|
||||
already green) proves is itself faithful and reachable once a *clean* `OK`
|
||||
attempt occurs. **The problem, if it is a problem in acdream's code at all, is
|
||||
never reaching a clean `OK` attempt in the first place — which is exactly what
|
||||
retail's own decompiled `insert_into_cell`/`check_other_cells` say should
|
||||
happen for this topology too.**
|
||||
|
||||
## Why this is a STOP, not a D1 fix
|
||||
|
||||
The fix contract's own hypothesis was specific: *"does retail's grounded step
|
||||
run a step-down phase per sub-step such that validate_walkable's Adjusted-on-
|
||||
steep lets the insert PROCEED to that phase ... where ours treats the
|
||||
primary's Adjusted as retry-the-insert?"* This session traced the exact
|
||||
function (`step_down`) and the exact gate (`transitional_insert`'s
|
||||
`if (edi == OK_TS)`) the hypothesis named, byte-for-byte, and found retail's
|
||||
own code does **not** let an Adjusted result proceed to the step-down phase —
|
||||
it retries from scratch, identically to acdream's port. The hypothesis, taken
|
||||
literally, is refuted, not confirmed. Two independently-constructed fixtures
|
||||
running acdream's real `TransitionalInsert`/`InsertIntoCell`/`CheckOtherCells`
|
||||
production code confirm this refutation empirically, not just on paper.
|
||||
|
||||
This leaves the contradiction between:
|
||||
- the pseudo-C (hand-traced + empirically reproduced twice): this topology
|
||||
should dead-loop, in BOTH retail and acdream, and
|
||||
- the live cdb profile (`345-retail-glide.cdb.log`): retail's glide fires
|
||||
`edge_slide`/`cliff_slide` 594 times each, in lockstep, `step_up` zero,
|
||||
|
||||
genuinely unresolved. Per the fix contract ("if the hypothesis is wrong, keep
|
||||
going until you find the branch that produces the profile") this session kept
|
||||
going — through `step_down`, `check_other_cells`, `InsertIntoCell`'s own
|
||||
retry loop, `CheckOtherCells`'s cell-set dispatch (`RunCheckOtherCellsAndAdvance`,
|
||||
`TransitionTypes.cs:3562-3636`, `CellTransit.FindCellSet`) — and found every
|
||||
one of those to be a faithful, citable, non-garbled port. No garbled
|
||||
Binary-Ninja decision point was found in the functions actually exercised by
|
||||
this topology (`insert_into_cell`, `transitional_insert`'s switch,
|
||||
`check_other_cells`'s switch, `step_down`) — all were clean integer
|
||||
dispatches, not x87/flag-test mush, so this is not a "note the address and
|
||||
STOP for byte-decode" situation either.
|
||||
|
||||
**What remains unresolved, and is out of this session's scope to fix by
|
||||
guessing:**
|
||||
- Whether retail's *actual* Rithwic terrain topology differs structurally from
|
||||
both synthetic fixtures here in some way that changes which branch fires —
|
||||
e.g. a shallower approach that keeps the primary/other-cell query in the
|
||||
"above" branch for more of the glide, letting more attempts reach a clean
|
||||
`OK` and hence `step_down`/`edge_slide` (this is plausible in principle: the
|
||||
live profile's `set_sliding_normal` count, 538, is *lower* than `edge_slide`/
|
||||
`cliff_slide`'s 594, meaning not every `edge_slide` call reaches
|
||||
`cliff_slide`'s effective branch — consistent with a live approach that
|
||||
spends *some* attempts in "above" and only *some* in "below-and-caught").
|
||||
- Whether `CellTransit.FindCellSet`'s broadphase (feeding `CheckOtherCells`'s
|
||||
`cellSet`) is over- or under-inclusive relative to retail's real
|
||||
`CObjCell::find_cell_list` for a moving sphere near a terrain-cell boundary
|
||||
— the same open question the first D0 pass flagged, still open, still
|
||||
requiring the live cdb trace named in that pass's own recommendation
|
||||
(`validate_walkable @0x0050d010` / `transitional_insert @0x0050b6f0` /
|
||||
`adjust_sphere_to_plane @0x00538210` while the glide happens), which this
|
||||
text-only session cannot execute.
|
||||
|
||||
Per the fix contract's acceptance rule #4 ("Any contradiction between the
|
||||
profile, the pseudo-C, and our code that you cannot resolve → STOP and report
|
||||
with the evidence. A correct STOP is a success."), this session stops here.
|
||||
**No production code was changed.** The two throwaway diagnostic fixtures used
|
||||
to gather this evidence were deleted before this document was written; they
|
||||
are reproducible from the exact parameters quoted above if a future session
|
||||
wants to re-verify or extend them (in particular: a graduated multi-triangle
|
||||
terrain fixture that keeps some attempts in the "above" branch, to test the
|
||||
"live approach spends only some attempts below" theory above).
|
||||
|
||||
## Trap list compliance
|
||||
|
||||
No change was made to `ValidateWalkable`'s math, `AdjustOffset`, the #331
|
||||
absorb, the #32 setter split, `DoStepUp`/`DoStepDown` internals, the
|
||||
edge-family response bodies, or the `stepDownHeight = oi.StepUpHeight`
|
||||
oddity. `RetailEdgeResponseOrderingTests.cs` (unmodified) already pins that
|
||||
`DoStepDown`→`EdgeSlideAfterStepDownFailed`→`CliffSlide` chain as faithful and
|
||||
reachable from a clean `OK` attempt — this session's fixtures corroborate that
|
||||
finding rather than contradicting it; the gap is entirely upstream of it.
|
||||
|
||||
## Parent addendum (2026-08-08, post-STOP): the ACE cross-check sharpens the question
|
||||
|
||||
`references/ACE/Source/ACE.Server/Physics/Transition.cs:779-934`
|
||||
(`TransitionalInsert`) — an independent port of the same retail loop — shows
|
||||
`EdgeSlide` is reachable ONLY from the `transitState == OK` arm: insert
|
||||
succeeds → the grounded step-down block runs → `StepDown` fails →
|
||||
`EdgeSlide(ref transitState, ...)`. On `Adjusted` the loop merely clears
|
||||
`NegPolyHit` and retries, exactly as our port and this doc's fixtures show.
|
||||
|
||||
Therefore the live profile (594 `edge_slide`/`cliff_slide` lockstep per run,
|
||||
`step_up`=0) FORCES the conclusion that retail's primary insert RETURNS OK
|
||||
per tick in the glide scenario, while ours returns `Adjusted` 3×3 per tick.
|
||||
The missed branch is not routing-after-Adjusted; it is whatever prevents
|
||||
retail's per-tick check position from tripping `validate_walkable`'s
|
||||
below-plane push (`@0x0050d010` mover arm, `return ADJUSTED_TS`) in the
|
||||
first place — most plausibly the interplay of the previous tick's
|
||||
edge/cliff slide having already moved the request, or a check-position
|
||||
seeding difference at insert entry.
|
||||
|
||||
**Falsifiable predictions for the round-2 capture
|
||||
(`tools/cdb/345-glide-stacks.cdb`):**
|
||||
1. `tins` (transitional_insert) counts ~1 per tick — NO retry storm in
|
||||
retail (ours: 9 attempts/tick).
|
||||
2. `stepdown` runs in lockstep with `edge` (≈594 scale).
|
||||
3. The `edge_slide` stacks show `transitional_insert` (or
|
||||
`find_transitional_position`) directly — the OK-arm step-down block —
|
||||
with NO intervening validate/adjust retry frames.
|
||||
|
||||
If prediction 1 fails (retail also retry-storms), the divergence is inside
|
||||
the retry's convergence instead, and the Adjusted-production question
|
||||
reopens. Either way the capture discriminates.
|
||||
Loading…
Add table
Add a link
Reference in a new issue