docs: #345 D0 verdict — the stuck-tick fingerprint is retail's own algorithm; fix attempt correctly stopped
Five links traced from the named decomp: the below-push never executes (OnWalkable guard — the probe printed the wrong guard pair), the from-scratch retry is retail-identical, and validate_transition's failure path manufactures every captured field including the (0,0,1) default. Stopping dead may simply BE retail. Two validations remain: the user observing their RETAIL client at a comparable slope (the cheapest decisive test there is), and — only if retail visibly slides — the find_cell_list broadphase question via the cdb toolchain. The mechanism paragraph's wrong-cause framing is retained and corrected in place, per the register's own honesty pattern. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
2098fa6690
commit
18289f95f8
2 changed files with 292 additions and 1 deletions
|
|
@ -178,7 +178,32 @@ position. Zero yield. (`oiContact=True, spStepDown=False, guardPassed=False`
|
|||
on every line — the morning's `collN=(0,0,1)` comes from the failure path's
|
||||
result-filling, not from ValidateWalkable's guard, which never passes here.)
|
||||
|
||||
**The fix contract's question is now precise:** what does RETAIL's
|
||||
### D0 VERDICT, same day — the fingerprint IS retail's own algorithm; the fix attempt STOPPED itself
|
||||
|
||||
The fix implementer's mandatory pseudocode pass traced all five links from
|
||||
the named decomp (addresses in
|
||||
`docs/research/2026-08-08-345-pseudocode.md`) and found the mechanism
|
||||
paragraph above named the right symptom with the WRONG cause: the below-push
|
||||
never executes at all (its guard is `step_down || !OnWalkable || walkable`,
|
||||
and the player IS OnWalkable on the flat approach — the probe printed the
|
||||
SetCollisionNormal guards, not this one); `Adjusted` retrying the whole
|
||||
insert from scratch is retail-identical (`transitional_insert` @0x0050b6f0);
|
||||
and `validate_transition` @0x0050aa70 on failure kills velocity, restores
|
||||
the flat plane, DEFAULTS the collision normal to (0,0,1), reverts CheckPos,
|
||||
and forces OK — producing every field of the captured fingerprint from
|
||||
retail's own code. **Stopping dead here may simply BE retail.**
|
||||
|
||||
**Two open validations before this closes either way:**
|
||||
1. **The cheapest decisive test needs no debugger: the USER walks their
|
||||
RETAIL client into a comparable just-too-steep hillside at ~45° and
|
||||
reports slide vs dead stop.** Their expectation of sliding is currently
|
||||
the only evidence against retail-faithfulness.
|
||||
2. If retail visibly slides: the remaining suspect is UPSTREAM of the
|
||||
walkable response — whether retail's `find_cell_list` broadphase even
|
||||
queries the steep cell from this position (the pseudocode doc's open
|
||||
question) — a runtime question for the cdb toolchain, not code-reading.
|
||||
|
||||
**The fix contract's question (superseded by the verdict above, retained):** what does RETAIL's
|
||||
transitional_insert do with validate_walkable's Adjusted on a non-walkable
|
||||
plane — does the adjusted CheckPos feed the NEXT attempt (convergence), or
|
||||
does retail take a different branch entirely (slide/collision) instead of
|
||||
|
|
|
|||
266
docs/research/2026-08-08-345-pseudocode.md
Normal file
266
docs/research/2026-08-08-345-pseudocode.md
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
# #345 D0 — retail pseudocode pin: does `transitional_insert` slide on a too-steep OTHER-cell plane while already grounded?
|
||||
|
||||
**Order:** implementation-session D0, per the mechanism contract
|
||||
(`docs/research/2026-08-08-345-mechanism-contract.md`) and the
|
||||
mechanism-caught finding in `docs/ISSUES.md` #345. Grep-named-first,
|
||||
pseudocode-before-port, per `CLAUDE.md`'s workflow.
|
||||
|
||||
**Verdict up front: retail does NOT slide here either.** Followed by hand
|
||||
through four retail functions (cross-checked against an independent C#
|
||||
reference for one of them), the mechanism converges to the exact same
|
||||
"0% yield, repeat forever, `collN=(0,0,1)`, `slidingNormal=(0,0,0)`"
|
||||
fingerprint the capture shows. This is **not** a carry-forward bug — see
|
||||
"What the mechanism-session's framing got wrong" below. Per the session
|
||||
contract's explicit fallback, this STOPS here without a D1/D2/D3 fix.
|
||||
|
||||
## The scenario being traced
|
||||
|
||||
Player is grounded, walking on a nearly-flat (~6.8°) approach terrain
|
||||
triangle (the PRIMARY cell). The requested horizontal offset carries the
|
||||
sphere's overlap into an ADJACENT (OTHER) terrain cell whose triangle is
|
||||
too steep to be walkable: `N=(0.799,0.050,0.599)`, `N.z=0.599` just under
|
||||
`FloorZ`(≈0.664). Captured fingerprint (`345-mechanism.log:279`, mover
|
||||
`0x000F4243`, and `docs/ISSUES.md` #345's own summary): `dist=-0.34680`
|
||||
(a different capture in the same class logged `-0.268` — same mechanism,
|
||||
different exact stance), `oiContact=True`, `spStepDown=False`,
|
||||
`guardPassed=False`, `outcome=Adjusted`, **identical on every one of 6
|
||||
attempts within the stuck tick**, final resolve position byte-identical
|
||||
to input, `collN=(0,0,1)`, `slidingNormal=(0,0,0)`, carried contact =
|
||||
the flat approach terrain, `transient=0x3` (Contact|OnWalkable).
|
||||
|
||||
## 1. `OBJECTINFO::validate_walkable` (retail 0x0050d010, pc:274479-274617)
|
||||
|
||||
Grep-named-first target from the contract. The "below the surface"
|
||||
branch (our scenario: `dist < -EPSILON`) is:
|
||||
|
||||
```
|
||||
zDist = dist / N.z
|
||||
walkable = is_valid_walkable(N) // N.z >= FloorZ, ours: sp.WalkableAllowance
|
||||
if (step_down != 0 || (state & ON_WALKABLE) == 0 || walkable != 0) {
|
||||
set_contact_plane(plane, cellId)
|
||||
if (step_down) { ...interpolation reject... }
|
||||
AddOffsetToCheckPos(0, 0, -zDist) // THE PUSH — pc:274604-274607
|
||||
}
|
||||
if ((state & CONTACT) == 0 && step_down == 0) {
|
||||
set_collision_normal(plane)
|
||||
collided_with_environment = 1
|
||||
}
|
||||
return ADJUSTED_TS
|
||||
```
|
||||
|
||||
Cross-checked against `references/ACE/Source/ACE.Server/Physics/ObjectInfo.cs:142-170`
|
||||
(`ObjectInfo.ValidateWalkable`, independently-authored C# port of the
|
||||
same algorithm) — **identical structure**, same three-way OR gate before
|
||||
the push.
|
||||
|
||||
**The push (`AddOffsetToCheckPos`) is gated behind
|
||||
`step_down || !OnWalkable || walkable`.** In our scenario: `step_down`
|
||||
is false (confirmed by the trace's `spStepDown=False`), `walkable` is
|
||||
false (0.599 < FloorZ), so the push fires **only if `OnWalkable` is
|
||||
false**. Our C# (`TransitionTypes.cs:3746-3769`) ports this gate
|
||||
verbatim: `if (sp.StepDown || !oi.OnWalkable || walkable)`.
|
||||
|
||||
**Is `OnWalkable` true here?** Yes — confirmed by
|
||||
`PhysicsEngine.cs:2026-2047`: at the start of every resolve, when the
|
||||
body is in contact with a valid plane and not moving away from it
|
||||
(`check_contact`'s success branch, ported faithfully per the #32 commit
|
||||
history), `if (body.OnWalkable) transition.ObjectInfo.State |= OnWalkable;`.
|
||||
The player IS resting on the flat approach terrain — `body.OnWalkable`
|
||||
is true — so `oi.OnWalkable` is true for this entire resolve.
|
||||
|
||||
**Conclusion: the push never fires. `ValidateWalkable` returns
|
||||
`ADJUSTED_TS` with ZERO state mutation** — no `SetContactPlane`, no
|
||||
`AddOffsetToCheckPos`, and (since `!oi.Contact` is false per the trace's
|
||||
`oiContact=True`) no `SetCollisionNormal` either. This is retail's own
|
||||
intentional design: a sphere already stably grounded elsewhere does not
|
||||
get shoved around by an incidental graze against a DIFFERENT, non-walkable
|
||||
patch. **There is no "adjustment" to carry forward — there never was one
|
||||
to begin with.** The mechanism-session's framing ("push-up... the
|
||||
adjustment does not carry forward between attempts") named the right
|
||||
symptom (identical `dist` every attempt) but the wrong cause (it isn't
|
||||
that a real push gets discarded; it's that the push never executes at
|
||||
all, precisely as retail specifies for a grounded-elsewhere mover).
|
||||
|
||||
## 2. `CTransition::transitional_insert` (retail 0x0050b6f0, pc:273137-273364)
|
||||
|
||||
```
|
||||
edi = INVALID_TS
|
||||
for (attempt = 0; attempt < numAttempts; attempt++) {
|
||||
edi = insert_into_cell(check_cell, numAttempts)
|
||||
switch (edi) {
|
||||
case OK_TS:
|
||||
edi = check_other_cells(check_cell) // overwrites edi
|
||||
if (edi != OK_TS) neg_poly_hit = 0
|
||||
if (edi == COLLIDED_TS) return COLLIDED_TS
|
||||
break // falls to "if edi==OK_TS" below
|
||||
case COLLIDED_TS:
|
||||
neg_poly_hit = 0
|
||||
return edi
|
||||
case ADJUSTED_TS:
|
||||
neg_poly_hit = 0
|
||||
break // falls straight to loop-bottom, no retry-with-state
|
||||
case SLID_TS:
|
||||
contact_plane_valid = 0; contact_plane_is_water = 0
|
||||
neg_poly_hit = 0
|
||||
break
|
||||
}
|
||||
if (edi == OK_TS) {
|
||||
...sphere_path.collide handling (Phase 3)...
|
||||
...neg_poly_hit dispatch (step_up / step_up_slide / slide_sphere)...
|
||||
}
|
||||
// loop-bottom: unconditional retry up to numAttempts, no early exit besides
|
||||
// the explicit returns above
|
||||
}
|
||||
return edi
|
||||
```
|
||||
|
||||
Our C# (`TransitionTypes.cs:1991-2037` for the switch,
|
||||
`2052-2065` for the `check_other_cells` dispatch) matches this
|
||||
line-for-line: `InsertIntoCell` result dispatches through the same
|
||||
Collided-returns/Adjusted-clears-neg-poly-continues/Slid-clears-contact-
|
||||
continues shape; `OK_TS` alone proceeds to `RunCheckOtherCellsAndAdvance`
|
||||
(our name for `check_other_cells`), whose non-OK result also just
|
||||
`continue`s the outer loop with **no special-cased state restoration** —
|
||||
identical to retail's `break` that skips the big Phase-3 block and falls
|
||||
to the unconditional loop-bottom retry.
|
||||
|
||||
**Neither retail nor our port does anything to "feed the adjusted
|
||||
CheckPos forward" on an `ADJUSTED_TS` from `check_other_cells` — both
|
||||
simply retry the WHOLE `insert_into_cell` from scratch.** Since
|
||||
`ValidateWalkable` made zero mutation (§1), retrying from scratch
|
||||
necessarily reproduces the identical primary-insert-OK,
|
||||
other-cell-Adjusted-with-identical-`dist` sequence every attempt. This
|
||||
is exactly the observed fingerprint, and it is retail-faithful.
|
||||
|
||||
## 3. `CTransition::check_other_cells` (retail 0x0050ae50, pc:272717-272798)
|
||||
|
||||
Iterates the sphere's overlapping OTHER cells (`find_cell_list`), calling
|
||||
each cell's virtual `find_collisions`. Its switch: `COLLIDED_TS` and
|
||||
`ADJUSTED_TS` (cases 2 and 3) **both `return result` immediately** — no
|
||||
further cells are tried, no retry loop of its own. `SLID_TS` (case 4)
|
||||
clears the contact plane fields then also returns immediately. Only
|
||||
`OK_TS` continues to the next cell. Our C# `CheckOtherCells` /
|
||||
`ApplyOtherCellResult` (`TransitionTypes.cs:2952-3030`) halts the same
|
||||
way. No divergence found here.
|
||||
|
||||
## 4. `CTransition::validate_transition` (retail 0x0050aa70, pc:272547-272689)
|
||||
|
||||
Called as `validate_transition(this, transitional_insert(this, 3), &out)`
|
||||
directly from `find_transitional_position` (retail 0x0050bdf0,
|
||||
pc:273743 — the ordinary per-substep walking driver, confirmed calling
|
||||
exactly `transitional_insert(this, 3)` then `validate_transition`,
|
||||
matching our `TransitionTypes.cs:1611/1621`
|
||||
`TransitionalInsert(3, engine)` → `ValidateTransition(result)` pairing
|
||||
byte-for-byte). On a non-OK, non-INVALID result (COLLIDED/ADJUSTED/SLID
|
||||
— **all three, treated identically**):
|
||||
|
||||
```
|
||||
if (last_known_contact_plane_valid) {
|
||||
kill_velocity()
|
||||
if (radius + EPSILON > |dot(N_lkcp, curr_center) + d_lkcp|) // still within
|
||||
set_contact_plane(last_known_contact_plane) // reach of LKCP?
|
||||
}
|
||||
if (!collision_normal_valid)
|
||||
set_collision_normal(UP) // the (0,0,1) DEFAULT FILL
|
||||
set_check_pos(curr_pos, curr_cell) // DISCARD — revert to pre-step position
|
||||
result = OK_TS // FORCE OK — the whole substep nets zero
|
||||
...
|
||||
if (collision_normal_valid)
|
||||
set_sliding_normal(collision_normal) // sliding_normal = f(UP) below
|
||||
```
|
||||
|
||||
Our C# `ValidateTransition` (`TransitionTypes.cs:6194-6226`) ports this
|
||||
exactly, including the `LastKnownContactPlaneValid` proximity gate
|
||||
(`TransitionTypes.cs:6203-6219`), the `!CollisionNormalValid` UP default
|
||||
(`6221-6222`, the literal source of the trace's `collN=(0,0,1)` —
|
||||
confirming the mechanism-session's own annotation that this normal
|
||||
"comes from the failure path's result-filling, not from
|
||||
`ValidateWalkable`'s guard"), the `SetCheckPos` revert + forced `OK_TS`
|
||||
(`6224-6225`), and the `SetSlidingNormal(CollisionNormal)` call
|
||||
(`6229-6230`).
|
||||
|
||||
**Because the player is still resting on the flat approach terrain,
|
||||
`curr_center` is (by construction) essentially ON that plane, so the
|
||||
LKCP-proximity check always passes — the flat terrain gets restored as
|
||||
the CURRENT contact plane on every failed substep.** That is why the
|
||||
capture shows `carried contact = the flat approach terrain`, not the
|
||||
steep face — the steep face never gets registered as a contact at all
|
||||
(§1), and this LKCP restore keeps re-confirming `OnWalkable = true`
|
||||
(`ContactPlane.Normal.Z(0.993) >= FloorZ`) at the tail of `ValidateTransition`
|
||||
(`TransitionTypes.cs:6252-6255`) — which is exactly the `OnWalkable`
|
||||
seed §1 needs to keep suppressing the push on the NEXT resolve. **This
|
||||
is a self-sustaining, retail-faithful attractor**: stay resting on flat
|
||||
ground behind you → steep OTHER-cell touch is silently ignored →
|
||||
contact plane keeps re-anchoring to the flat ground → `OnWalkable` stays
|
||||
true → the steep touch keeps being silently ignored. There is no state
|
||||
transition inside this mechanism that would break the cycle.
|
||||
|
||||
## 5. `COLLISIONINFO::set_sliding_normal` (the "#331 absorb")
|
||||
|
||||
`TransitionTypes.cs:553-559` projects the incoming normal to XY only
|
||||
and re-normalizes:
|
||||
|
||||
```csharp
|
||||
SlidingNormal = new Vector3(normal.X, normal.Y, 0f);
|
||||
if (SlidingNormal.LengthSquared() > EpsilonSq)
|
||||
SlidingNormal = Vector3.Normalize(SlidingNormal);
|
||||
```
|
||||
|
||||
Fed the §4 UP default `(0,0,1)`, this produces `SlidingNormal=(0,0,0)`
|
||||
— **exactly the captured `slidingNormal=(0,0,0)`.** Per the trap
|
||||
inventory (`docs/research/2026-08-08-345-mechanism-contract.md` +
|
||||
`CLAUDE.md` "Current state"), this XY-projection is the retail-faithful
|
||||
#331 absorb and is explicitly off-limits to touch. It is not the cause
|
||||
here — it is a correct, downstream consequence of the §4 UP default,
|
||||
which is itself a correct, downstream consequence of §1's guard never
|
||||
firing.
|
||||
|
||||
## What the mechanism-session's framing got wrong
|
||||
|
||||
The probe (`ACDREAM_DUMP_TRANSIT_FAIL`) correctly found WHERE the
|
||||
identical-`dist` loop lives (`ValidateWalkable`'s below-branch,
|
||||
`TransitionalInsert`'s retry). It inferred WHY from the symptom's shape
|
||||
("push-up... doesn't carry forward") without visibility into `oi.OnWalkable`
|
||||
or the push guard itself — the probe's trace only carries `oiContact`/
|
||||
`spStepDown` (the *second*, `SetCollisionNormal`, guard), not the *first*
|
||||
(`AddOffsetToCheckPos`) guard's inputs. Reading the guard from source
|
||||
(`sp.StepDown || !oi.OnWalkable || walkable`, all three legs resolvable
|
||||
statically for this scenario) shows the push is **never attempted**, so
|
||||
there is nothing to "carry forward" in the first place.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Every function in the chain — `ValidateWalkable`, `TransitionalInsert`,
|
||||
`CheckOtherCells`, `ValidateTransition`, `SetSlidingNormal` — is a
|
||||
faithful, citable port, and hand-tracing them against this exact
|
||||
scenario reproduces every byte of the captured fingerprint (`collN=(0,0,1)`,
|
||||
`slidingNormal=(0,0,0)`, identical `dist` per attempt, carried contact =
|
||||
approach terrain, byte-identical position in/out). **Retail's own
|
||||
algorithm, run by hand against this geometry, does not slide — it
|
||||
converges to the same zero-yield stop.** Per the mechanism-session
|
||||
contract's explicit fallback ("If retail turns out NOT to slide here
|
||||
either, STOP and report — the user's expectation would then be the
|
||||
divergence, a different decision"), this session stops here. No
|
||||
`ValidateWalkable`/`TransitionalInsert`/`ValidateTransition` change is
|
||||
made; #331, #32, and AD-65 are untouched, matching the trap inventory.
|
||||
|
||||
## Open question for whoever picks this up next
|
||||
|
||||
The one link in this chain NOT fully verified against retail is **cell
|
||||
membership**: does retail's `CObjCell::find_cell_list` (feeding
|
||||
`check_other_cells`'s cell array) actually include this neighboring
|
||||
too-steep OTHER cell from the player's exact resting position, or does
|
||||
our `CellTransit`/other-cells construction query a cell retail's
|
||||
narrower geometry test would not have reached at all? That is a
|
||||
genuinely different question from anything traced above (a broadphase/
|
||||
cell-array question, not a walkable-response question), and it is the
|
||||
one place this D0 pass had to reason from citation rather than from a
|
||||
direct retail-vs-acdream A/B. The toolchain's own guidance applies
|
||||
here: this is exactly the "what does retail actually DO at runtime"
|
||||
class of question the cdb toolchain
|
||||
(`memory/reference_retail_debugger.md`) exists for — attach to a live
|
||||
retail client at the identical Rithwic steep face and confirm whether
|
||||
retail's player is ALSO immovable at this exact stance, or whether it
|
||||
is already sliding by the time the sphere reaches this position (which
|
||||
would point at the cell-array question above, not at anything in this
|
||||
document).
|
||||
Loading…
Add table
Add a link
Reference in a new issue