Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Retail's OBJECTINFO::validate_walkable @0x0050d010 initializes its return slot to OK (0x0050d025) and assigns ADJUSTED only inside the below-plane guard, immediately after the push executes (0x0050d249). The guard-fail path — grounded, OnWalkable, plane too steep — jumps past the contact write, the push, and the assignment (0x0050d1b9 -> 0x0050d251): retail deliberately IGNORES the steep plane at primary validation so the insert proceeds, the step-down phase fails on the steep landing, and the edge family produces the per-tick lateral glide. ACE flattened this into an unconditional return Adjusted (ObjectInfo.cs:169) and we inherited it; our TransitionalInsert then retried the byte-identical Adjusted forever — the user's stop-instead-of-slide. Evidence chain: the user's retail observation (the axiom), the live cdb glide profile (edge_slide/cliff_slide 594 each in lockstep, step_up 0), the D0 implementer's correct STOP (fixtures reproduced the stuck fingerprint while faithfully executing the ACE-shaped reading — refuting the reading, not the code), and the capstone byte-decode both Opus reviewers re-derived independently, including the stack-slot frame arithmetic and every ret site's eax. The conformance fixture is the live topology: flat and steep terrain triangles sharing ONE cell's diagonal (a cell-boundary face does NOT reproduce the loop — the cell-scoped primary sample never validates a neighbour's triangle — and is pinned as supplementary). Sabotage: restoring the unconditional Adjusted reds the discriminator with the exact stuck position (0.325 m lateral, 28/30 stuck ticks vs 2.602 m / 14/30 fixed; reviewer B's independent five-angle table is monotone 10-85 degrees). Stuck ticks are counted from positions so the assertion survives the eventual probe strip. In-game glide gate PASSED 2026-08-08: "Well it works, we are sliding. I cant detect any speed change from retail." Filed alongside: #347 + AD-70 (our glide alternates arm/move at half retail's per-tick rate — retail redirects within the tick; next up by user direction), AD-71 (the guard's mutable WalkableAllowance operand vs retail's fixed is_valid_walkable global — now return-value-bearing), and the reviewers' named residuals in the #345 closure entry (placement-arm flip, other-cell coverage gap, EdgeSlide-less projectiles, ACE's server-side shared misport predicting remote drift-then-snap on steep terrain). The unported IsViewer arm of validate_walkable is noted in the D0 doc. Suite: clean-room complete solution 11,271 passed / 4 skipped / 0 failed; Core assembly re-run green after the review-driven test hardening. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
276 lines
17 KiB
Markdown
276 lines
17 KiB
Markdown
# #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.
|
||
|
||
## D0 RESOLVED (2026-08-08, parent session): the missed branch is validate_walkable's RETURN SCOPING — byte-proven
|
||
|
||
Capstone disassembly of the PDB-paired binary (`C:\Users\erikn\Downloads\
|
||
acclient.exe`, v11.4186) at `OBJECTINFO::validate_walkable` @0x0050d010:
|
||
|
||
```
|
||
0050d020 mov edi, 1
|
||
0050d025 mov [esp+0xc], edi ; var_1c = 1 = OK_TS (return-value slot)
|
||
...below-plane arm...
|
||
0050d1a9 test ecx, ecx ; sp->step_down
|
||
0050d1af jne 0x50d1bf ; set → guard body
|
||
0050d1b1 test byte [ebp+4], 2 ; oi->state & OnWalkable
|
||
0050d1b5 je 0x50d1bf ; clear → guard body
|
||
0050d1b7 test eax, eax ; is_valid_walkable(N)
|
||
0050d1b9 je 0x50d251 ; TOO STEEP → SKIP guard body ENTIRELY
|
||
0050d1bf ...set_contact_plane, step-down interp (fail: mov eax,2 → COLLIDED),
|
||
0050d244 call add_offset_to_check_pos ; the push
|
||
0050d249 mov dword [esp+0x10], 3 ; var_1c = ADJUSTED — ONLY after the push
|
||
0050d251 ...collision-normal tail (Contact set → skipped)...
|
||
0050d271 mov eax, [esp+0x10] ; return var_1c
|
||
```
|
||
|
||
**The guard-fail path (grounded mover, OnWalkable, too-steep plane) never
|
||
touches var_1c: retail returns OK.** The steep below-plane is deliberately
|
||
ignored by primary walkable validation; the insert proceeds, the OK-arm
|
||
step-down phase runs at the advanced position, its walkable probe fails on
|
||
the steep landing (`check_walkable` early-out: `0050d187 mov eax,2` —
|
||
COLLIDED, also byte-confirmed), `StepDown` fails, and `EdgeSlide` /
|
||
`CliffSlide` produce the per-tick lateral glide. Every round-1 counter
|
||
(594 edge/cliff lockstep, step_up=0, walkable_hits_sphere=0,
|
||
adjust_sphere_to_plane=0, vwalk high) is reproduced by this reading.
|
||
|
||
**ACE misported this** (`ObjectInfo.cs:169` returns Adjusted
|
||
unconditionally after the guard block) and acdream inherited the shape.
|
||
Binary Ninja had `var_1c = 3` correctly scoped inside the guard all along
|
||
(pc:274525+ region) — but its `void` return typing and bare `return;`
|
||
statements hid the return-value mechanics from every prior reading.
|
||
|
||
**The fix (supersedes the contract's D1 scope, per its own "find the real
|
||
branch" clause):** in our `ValidateWalkable` below-plane arm
|
||
(`TransitionTypes.cs:3744-3785`), return `Adjusted` only when the guard
|
||
passed and the push executed; return `OK` when the guard fails. No routing
|
||
change in `TransitionalInsert` is needed — the existing OK-arm step-down
|
||
block + edge family already produce the cascade (proven live-healthy by
|
||
this morning's 18 clean edge-family entries).
|
||
|
||
Also byte-confirmed while here: the step-down interp failure returns
|
||
COLLIDED (`0050d28b mov eax,2`) — ACE and our port are RIGHT there; BN's
|
||
bare `return` hid it. And the check_walkable early-out returns COLLIDED
|
||
(`0050d187`) — all three agree.
|
||
|
||
**Flagged secondary (file, don't chase):** retail's guard calls
|
||
`CPhysicsObj::is_valid_walkable(N)` (fixed retail threshold) at BOTH the
|
||
resting and below-plane sites; ours tests `N.z >= sp.WalkableAllowance`.
|
||
In this scenario both reject N.z=0.6, so it is not #345's cause — but the
|
||
operand difference needs its own conformance check.
|