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
|
|
@ -158,9 +158,76 @@ resolution.
|
|||
|
||||
---
|
||||
|
||||
## #345 — Walking angled into a too-steep slope: 100% of input eaten with an UP collision normal, no slide
|
||||
## #347 — Steep-slope glide runs at HALF retail's rate: arm/move alternation instead of within-tick redirect
|
||||
|
||||
**Status:** OPEN — HIGH (user-felt, capture-backed). **A/B COMPLETE, same
|
||||
**Status:** OPEN — MEDIUM (behavioral residual of the #345 fix, trace-backed).
|
||||
With #345 fixed, the glide works but alternates in a strict two-tick cycle:
|
||||
the arming tick absorbs the whole request while the edge response sets the
|
||||
sliding normal ((0.707,-0.707,0) on the conformance fixture), and only the
|
||||
NEXT tick's `AdjustOffset` pre-projection consumes it and moves (+0.115,
|
||||
+0.115 per moving tick on the 45-degree fixture) — then the clean move
|
||||
clears the normal and the cycle repeats. Fixture trace: 14 of 30 post-
|
||||
crossing ticks stuck, positions advancing every other tick
|
||||
(`Issue345SteepSlopeGlideTests` + the deleted Scratch345 dump, 2026-08-08).
|
||||
Retail instead redirects WITHIN the tick: the live cdb profile
|
||||
(`345-retail-glide.cdb.log`) fired `edge_slide`/`cliff_slide` 594 times
|
||||
EACH in lockstep with `set_sliding_normal` 538 over a ~15 s glide — every
|
||||
30 Hz tick, which an alternation would halve — so retail's
|
||||
`transitional_insert` re-enters the redirected offset in the SAME
|
||||
transition and yields motion every tick. Ours ends the transition on the
|
||||
arming tick with zero yield. The fix lives in the edge-family response
|
||||
semantics (`EdgeSlideAfterStepDownFailed` and the insert's continuation
|
||||
after an applied edge constraint) — deliberately NOT touched by the #345
|
||||
landing (fresh AD-66 in the same block; the Campaign S response-layer
|
||||
landings all user-gated). Net user-visible effect: gliding along a steep
|
||||
face at ~half retail's lateral speed; direction and angle-scaling correct.
|
||||
If the #345 in-game gate reports "slides but slower than retail," this is
|
||||
the mechanism, already filed.
|
||||
|
||||
## #345 — Walking angled into a too-steep slope: 100% of input eaten with an UP collision normal, no slide — CLOSED 2026-08-08 (in-game glide gate PASSED: "Well it works, we are sliding. I cant detect any speed change from retail")
|
||||
|
||||
**Dual Opus review verdicts: CONFIRMED-FAITHFUL (conformance, independent
|
||||
byte re-decode incl. the stack-slot frame arithmetic and every ret site) and
|
||||
SAFE (blast radius, truth-table instrumented: exactly one row moves; the
|
||||
placement/teleport family proven immune via mover-flag grep + the
|
||||
RuntimeSetPositionMoverPreparationTests pin; independent five-angle sabotage
|
||||
table, monotone angle→lateral 10°–85°). Named non-blocking residuals, all
|
||||
retail-consistent or filed: the placement-arm REJECT→ACCEPT flip at
|
||||
DoStepDown's final insert and the Collide-branch re-test (retail's
|
||||
validate_walkable is insert-type-agnostic — same behavior, untested
|
||||
topology); the other-cell ValidateWalkable site has zero fixture coverage
|
||||
(a neighbouring-cell steep fixture would close it); projectiles (no
|
||||
EdgeSlide flag) resting on a steep face commit below-plane instead of
|
||||
pushing out (retail-mover-agnostic, narrow); and ACE SHARES the misport
|
||||
server-side, so NPCs/remotes may show lateral drift-then-snap at steep
|
||||
terrain until the next UpdatePosition — name that mechanism before
|
||||
misdiagnosing any future remote-prediction report. AD-71 (mutable
|
||||
WalkableAllowance operand) filed the same session.**
|
||||
|
||||
**FIX (one conditional return, byte-proven):** retail's
|
||||
`OBJECTINFO::validate_walkable` @0x0050d010 initializes its return slot to
|
||||
OK (`0x0050d025: mov [esp+0xc], 1`) and assigns ADJUSTED only INSIDE the
|
||||
below-plane guard, immediately after the push executes (`0x0050d249`,
|
||||
after `add_offset_to_check_pos`). The guard-fail path — grounded mover,
|
||||
OnWalkable, plane too steep (`0x0050d1b9 je 0x50d251`) — skips the
|
||||
contact write, the push, AND the Adjusted assignment: retail returns OK
|
||||
and simply IGNORES the steep plane at primary validation, letting the
|
||||
insert proceed so the step-down phase fails and the edge family produces
|
||||
the glide. ACE flattened this into an unconditional `return Adjusted`
|
||||
(`ObjectInfo.cs:169`) and we inherited it — the dead loop was our
|
||||
`TransitionalInsert` retrying the byte-identical Adjusted forever.
|
||||
`ValidateWalkable`'s below-plane arm now scopes the return exactly as the
|
||||
bytes do. Evidence chain: the live cdb glide profile (594 edge/cliff
|
||||
lockstep, step_up=0), the capstone byte-decode
|
||||
(`docs/research/2026-08-08-345-d0-branch-pin.md`), the D0 implementer's
|
||||
correct STOP (synthetic fixtures reproduced the stuck fingerprint while
|
||||
faithfully executing the ACE-shaped reading — refuting the reading, not
|
||||
the code), and the discriminating conformance fixture (in-cell diagonal
|
||||
split, flat+steep triangles sharing one cell — sabotage red with the
|
||||
exact stuck position; the cell-boundary topology does NOT discriminate
|
||||
and is pinned as supplementary). Residual: #347 (half-rate alternation).
|
||||
|
||||
**Original report:** OPEN — HIGH (user-felt, capture-backed). **A/B COMPLETE, same
|
||||
morning: PRE-EXISTING, not a campaign regression.** The pre-campaign binary
|
||||
(d4e956b4, built in a throwaway worktree, same recorder, same slope, same
|
||||
protocol) shows the identical signature — eleven consecutive 2-second windows
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue