fix(physics): #345 — a grounded mover glides along a too-steep face; validate_walkable's return is scoped as retail's bytes scope it
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
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>
This commit is contained in:
parent
7542cfd3c2
commit
ab89ebdf92
5 changed files with 413 additions and 5 deletions
|
|
@ -217,3 +217,60 @@ seeding difference at insert entry.
|
|||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue