The contract's D0 byte-pin refuted the tangent-placement premise: validate_walkable @0x0050d010 is planted for every normal mover (Ghidra + BN + ACE agree; ours is already byte-faithful; only the camera branch is tangent). With the byte-proven bare-radius push-out the coherent retail mechanism is plant-then-lift — the push fires once per settle, raises the body to tangent equilibrium, and both checks go quiet there. The slope float comes from the push, not the placement, exactly as the original substitution's own comment argued. The 84% live fire rate was measured against our push-disabled steady state; the #341 assert-shape flip now reads as order-dependent settle state. The user's 'port the retail pair' therefore maps to relanding AD-66's bare radius alone, with the ten-run stability protocol. The STOP rule paid for itself: an implementer without it would have made ValidateWalkable DIVERGE from retail in the name of faithfulness. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
262 lines
14 KiB
Markdown
262 lines
14 KiB
Markdown
# S4b D0 — byte-pin of `OBJECTINFO::validate_walkable`'s distance basis
|
||
|
||
**Date:** 2026-08-07. **Author:** S4b implementer (single Sonnet agent, no
|
||
subagents). **Status: PREMISE REFUTED — SLICE STOPPED AT D0.** No code was
|
||
changed. D1/D2/D3/D4 were not performed. Nothing is committed.
|
||
|
||
## The question D0 was scoped to answer
|
||
|
||
Per `docs/research/2026-08-07-s4b-tangent-rest-contract.md`: does retail's
|
||
`OBJECTINFO::validate_walkable` measure the **perpendicular sphere clearance**
|
||
(`dot(center,N)+D − radius`, tangent semantics — the same shape as
|
||
`CPolygon::adjust_sphere_to_plane`), or a **vertical foot point**
|
||
(`lowPoint = center − (0,0,radius)`, planted semantics — the shape acdream's
|
||
current `Transition.ValidateWalkable` already implements)?
|
||
|
||
The contract's explicit, pre-authorized stop condition: *"If retail's
|
||
validate_walkable turns out to ALSO use a vertical foot point, STOP — the
|
||
whole slice premise changes and the session lead re-decides."*
|
||
|
||
**Answer: vertical foot point (planted), for the branch that governs every
|
||
normal mover (players, creatures — anything not the camera/viewer).** The
|
||
stop condition is triggered by direct byte evidence from two independent
|
||
decompilers plus ACE's independent C# port. All three agree with each other
|
||
and with acdream's current code.
|
||
|
||
## Byte evidence
|
||
|
||
### 1. Ghidra decompile (primary; PDB-paired, `patchmem.gpr`, port 8081)
|
||
|
||
`OBJECTINFO::validate_walkable @ 0x0050d010`:
|
||
|
||
```c
|
||
TransitionState __thiscall
|
||
OBJECTINFO::validate_walkable(OBJECTINFO *this, CSphere *param_1, Plane *param_2,
|
||
int param_3, float param_4, SPHEREPATH *param_5,
|
||
COLLISIONINFO *param_6, ulong param_7)
|
||
{
|
||
...
|
||
if ((this->state & 4) != 0) { // IsViewer branch
|
||
fVar2 = (N.x*center.x + N.y*center.y + N.z*center.z + d) - radius; // BARE radius — tangent
|
||
...
|
||
return OK_TS / ADJUSTED_TS;
|
||
}
|
||
// else branch — every normal mover (player, creature, missile, etc.)
|
||
fVar2 = center.x*N.x + (center.z - radius)*N.z + center.y*N.y + d + param_4;
|
||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
// = dot(center, N) + d - radius*N.z + waterDepth -- PLANTED, not tangent
|
||
if (-EPSILON <= fVar2) { ... return OK_TS; }
|
||
else {
|
||
if (param_5->check_walkable != 0) return COLLIDED_TS;
|
||
fVar2 = fVar2 / N.z; // zDist
|
||
... push sphere up by -fVar2 along Z, step-down interp, etc.
|
||
return ADJUSTED_TS;
|
||
}
|
||
}
|
||
```
|
||
|
||
Full raw decompile captured via `GET /decompile_function?address=0x0050d010`
|
||
against the currently-open `patchmem.gpr` project (Sept 2013 EoR build, same
|
||
source as `docs/research/named-retail/`).
|
||
|
||
**`this->state & 4` is `ObjectInfoState.IsViewer` (`= 0x4`)** — confirmed by
|
||
`references/ACE/Source/ACE.Server/Physics/ObjectInfo.cs:13` and by
|
||
`LandCell.cs:56`'s `!objInfo.State.HasFlag(ObjectInfoState.IsViewer) &&
|
||
!objInfo.Object.State.HasFlag(PhysicsState.Missile)` guard on env-collision
|
||
processing. IsViewer marks the camera/eye collision object, not the player's
|
||
own movement sphere. Every walking mover (player body, creature, NPC) takes
|
||
the **else** branch — the planted one.
|
||
|
||
**`param_4` is waterDepth** — confirmed by the caller (below): it is used
|
||
exactly once, added directly after the `d` term:
|
||
`... + param_2->d + param_4`. It is NOT folded into the `lowPoint` subtraction
|
||
— it modifies the threshold, not the sphere's virtual bottom. This is
|
||
identical in shape and placement to ACE's
|
||
`... contactPlane.D + waterDepth` and to acdream's current
|
||
`contactPlane.D + waterDepth` (`TransitionTypes.cs:3673`). AP-10's sink-in
|
||
byte-pin: **`dist = dot(center,N) + D − radius·N.z + waterDepth`**,
|
||
unchanged from acdream's current formula.
|
||
|
||
### 2. Binary Ninja pseudo-C (secondary; corroborates, was initially ambiguous)
|
||
|
||
`acclient_2013_pseudo_c.txt:274479-274617`. The `state & 4` branch (lines
|
||
274487-274488) computes `((N.x*cx + N.y*cy + cz*N.z) + d) - radius` — bare
|
||
radius, matching Ghidra's IsViewer arm exactly.
|
||
|
||
The `else` branch (lines 274531-274532) computes
|
||
`N.y*cy + (cz - radius)*N.z + N.x*cx + d` — matching Ghidra's planted arm
|
||
exactly, **modulo one artifact**: BN reuses the SSA name `x87_r7_16` for both
|
||
the bare `radius` value (line 274531) and the freshly-computed dot-product
|
||
expression one line later (274532), because both values transiently occupy
|
||
the same logical x87 stack slot. This is the "flag-idiom artifact class" the
|
||
project CLAUDE.md warns about — a naming collision, not an algebraic one; the
|
||
raw expression at 274532 is unambiguous regardless of what BN chose to call
|
||
its result. **`arg5` (waterDepth) never appears by name inside BN's rendering
|
||
of this function** — another BN-only artifact (the float stack parameter gets
|
||
folded directly into the compound expression without a preserved `argN`
|
||
token). Ghidra's `param_4` resolves this cleanly. This is exactly the
|
||
"disassemble if BN is ambiguous" case D0 anticipated, and Ghidra is the
|
||
byte-pin of record here.
|
||
|
||
### 3. ACE's independently-authored C# port (tertiary cross-check)
|
||
|
||
`references/ACE/Source/ACE.Server/Physics/ObjectInfo.cs:101-172`,
|
||
`ValidateWalkable`:
|
||
|
||
```csharp
|
||
if (State.HasFlag(ObjectInfoState.IsViewer))
|
||
{
|
||
var dist = Vector3.Dot(checkPos.Center, contactPlane.Normal) + contactPlane.D - checkPos.Radius;
|
||
... // bare radius — tangent
|
||
}
|
||
else
|
||
{
|
||
var dist = Vector3.Dot(checkPos.Center - new Vector3(0, 0, checkPos.Radius), contactPlane.Normal)
|
||
+ contactPlane.D + waterDepth;
|
||
... // vertical foot point — planted, waterDepth added after
|
||
}
|
||
```
|
||
|
||
Independently produced (ACE was reverse-engineered years before this
|
||
project's PDB-named decomp existed), and it agrees with Ghidra byte-for-byte
|
||
on both branches, including waterDepth's placement. Three independent
|
||
sources, zero disagreement.
|
||
|
||
### 4. Caller context — confirms AD-69 is a separate, correctly-scoped finding
|
||
|
||
`CLandCell::find_env_collisions @ 0x00532f20` (pseudo-C
|
||
lines 317070-317113, Ghidra-cross-readable) is the sole caller of
|
||
`validate_walkable` for outdoor terrain. It calls
|
||
`LandDefs::get_block_offset` **twice**: once to convert
|
||
`global_low_point` into the terrain-poly lookup's block frame (for
|
||
`find_terrain_poly`), and again (0x00533007) to subtract the block offset
|
||
from `global_sphere->center` **before** the sphere passed into
|
||
`validate_walkable` is built. This is the cell-relative correction AD-69
|
||
already names, scoped to `CTransition::adjust_offset @ 0x0050a370`
|
||
(a **different** function, `pc:272271-272393` per the register row) — not to
|
||
`validate_walkable`. AD-69 is unaffected by this finding; it was already
|
||
correctly scoped before this slice started.
|
||
|
||
### 5. `CPolygon::adjust_sphere_to_plane @ 0x00538210` — re-confirmed independently, unaffected
|
||
|
||
Ghidra decompile (`GET /decompile_function?address=0x00538210`):
|
||
|
||
```c
|
||
fVar3 = dot(N, center) + d;
|
||
fVar4 = dot(N, movement_dir);
|
||
if (fVar4 <= EPS) { if (-EPS <= fVar4) return 0; fVar3 -= radius; } // bare radius
|
||
else { fVar3 = -radius - fVar3; } // bare radius
|
||
fVar3 /= fVar4;
|
||
...
|
||
```
|
||
|
||
Bare radius, both branches — matches AD-66's byte evidence exactly and
|
||
matches acdream's existing `BSPQuery.cs:364 AdjustSphereToPlane` (already a
|
||
faithful port, confirmed by direct read: `dist = dpPos - validPos.Radius` /
|
||
`dist = -validPos.Radius - dpPos`, bare `Radius`, no `N.z` factor). This
|
||
function's tangent semantics are correct and were never in question.
|
||
|
||
## What this means for the contract's premise
|
||
|
||
The contract's framing was: *"acdream today mixes tangent (BSP walk solve)
|
||
with planted (ValidateWalkable) and the planted one wins the resting height
|
||
on terrain [instead of retail's own consistent tangent pairing]."* That
|
||
framing assumed retail pairs tangent-with-tangent (`adjust_sphere_to_plane`
|
||
+ `validate_walkable` both bare-radius) and that acdream's planted
|
||
`ValidateWalkable` is therefore a deviation to fix.
|
||
|
||
**The byte evidence shows retail itself pairs tangent-with-planted**, not
|
||
tangent-with-tangent:
|
||
|
||
| Function | Retail semantics | acdream today |
|
||
|---|---|---|
|
||
| `CPolygon::adjust_sphere_to_plane` @0x00538210 (BSP walk / push-out during collision resolution) | tangent, bare radius | tangent, bare radius (`BSPQuery.cs:364`) — matches |
|
||
| `OBJECTINFO::validate_walkable` @0x0050d010, mover branch (per-tick terrain rest/step validation) | **planted**, radius·N.z, waterDepth added after | **planted**, radius·N.z, waterDepth added after (`TransitionTypes.cs:3652-3730`) — matches |
|
||
| `CTransition::adjust_offset` @0x0050a370 safety push-out (AD-66) | tangent, bare radius (byte-confirmed, withheld pending #341) | planted substitution (radius·N.z), the AD-66 deviation | ← genuinely still a deviation, unaffected by this finding |
|
||
|
||
acdream's `ValidateWalkable` is **already a byte-faithful port** of retail's
|
||
mover-path formula — same low-point construction, same epsilon bracket
|
||
(`dist >= -EPSILON` / `dist <= EPSILON`), same `zDist = dist/N.z` push-up,
|
||
same step-down interpolation guard, same `OnWalkable`/`Contact` flag-setting
|
||
conditions (state bits 1/2 map onto `oi.Contact`/`oi.OnWalkable` exactly).
|
||
There is no `ValidateWalkable` distance-basis change to port — porting one
|
||
would make acdream **diverge from** retail, not converge to it.
|
||
|
||
**D1 item 1 (ValidateWalkable's distance basis → tangent) is refuted and must
|
||
not be implemented.** Because the contract frames the pair as inseparable
|
||
("The pair ports together or not at all" / "STOP — the whole slice premise
|
||
changes"), **the whole D1/D2/D3 program as specified is out**, not just item
|
||
1 in isolation. Items 2 (AD-66 bare-radius landing in `AdjustOffset`) and 3
|
||
(AD-69's `get_block_offset` correction) remain independently well-founded by
|
||
their own byte evidence and their own register rows — but landing them was
|
||
explicitly conditioned by this contract on the ValidateWalkable pairing
|
||
argument that just failed, and the #341 anomaly this slice was named for
|
||
(the flipping absorbed-tick behavior on AD-66 alone) has **not been
|
||
explained** by anything found here. Landing AD-66/AD-69 now, under a
|
||
different justification than the one this contract authorized, is a
|
||
decision for the session lead, not a call for the implementer to make
|
||
unilaterally under a refuted contract.
|
||
|
||
## What was NOT done (by design, per the contract's own stop clause)
|
||
|
||
- D1 (port the pair) — not implemented. No changes to `TransitionTypes.cs`
|
||
`ValidateWalkable` or `AdjustOffset`, no changes to `BSPQuery.cs` /
|
||
`FlatBspQuery.cs`.
|
||
- D2 (ten-run `RuntimeRemoteUphillProgressTests` retest) — not run under
|
||
this slice's changes, because there are no changes to test. (The existing
|
||
`S4AdjustOffsetConformanceTests.Uphill_NoContactFlapAcrossTicks` and the
|
||
two `[Skip]`-ed AD-66 tests were read but left untouched and un-unskipped.)
|
||
- D3 (conformance tests + sabotage) — not written.
|
||
- D4 (register/ISSUES bookkeeping) — not performed; AD-66 and AD-69 remain
|
||
exactly as they are (AD-66 ACTIVE/WITHHELD, AD-69 FILED, #341 open).
|
||
- No files under `src/` were edited. No commits were made.
|
||
|
||
## Open question for the session lead
|
||
|
||
The #341 measurement anomaly (AD-66's bare radius alone producing
|
||
contradictory absorbed-tick outcomes that flip with test-assert shape) is
|
||
still unexplained. This slice's finding rules out "ValidateWalkable should
|
||
also be tangent" as the fix, since that would itself be a retail deviation.
|
||
Candidate directions the session lead may want to weigh (not investigated
|
||
here — out of this slice's scope once D0 refuted the premise):
|
||
|
||
- The anomaly may be a test-harness artifact (the tests' own docstring
|
||
already flags "flipping with nothing but the shape of the test's post-tick
|
||
asserts" as suspicious) rather than a real engine behavior difference.
|
||
- AD-69's `get_block_offset` gap could be entangled with AD-66's in a way
|
||
that only shows up when both change together — but that is a testable
|
||
hypothesis, not yet tested.
|
||
- The #331 absorb-tick semantics (explicitly OUT of scope for both this
|
||
contract and S4b) may be the actual source of the flip, independent of
|
||
AD-66/AD-69 entirely.
|
||
|
||
## Citations
|
||
|
||
- `OBJECTINFO::validate_walkable` @ 0x0050d010 (Ghidra `patchmem.gpr`
|
||
decompile; BN pseudo-C `acclient_2013_pseudo_c.txt:274479-274617`)
|
||
- `ObjectInfoState.IsViewer = 0x4`
|
||
(`references/ACE/Source/ACE.Server/Physics/ObjectInfo.cs:13`)
|
||
- `references/ACE/Source/ACE.Server/Physics/ObjectInfo.cs:101-172`
|
||
(`ValidateWalkable`, independent port)
|
||
- `references/ACE/Source/ACE.Server/Physics/Common/LandCell.cs:56`
|
||
(IsViewer/Missile env-collision guard)
|
||
- `CLandCell::find_env_collisions` @ 0x00532f20
|
||
(`acclient_2013_pseudo_c.txt:317070-317113`; the `get_block_offset` calls
|
||
and `validate_walkable` call site)
|
||
- `CPolygon::adjust_sphere_to_plane` @ 0x00538210 (Ghidra decompile; AD-66's
|
||
cited address, re-confirmed)
|
||
- `src/AcDream.Core/Physics/TransitionTypes.cs:3652-3730` (acdream's current
|
||
`ValidateWalkable` — read directly, confirmed byte-faithful to the mover
|
||
branch above)
|
||
- `src/AcDream.Core/Physics/TransitionTypes.cs:5501-5655` (acdream's current
|
||
`AdjustOffset`, including the AD-66 WITHHELD comment block and AD-69's
|
||
target `dist` computation)
|
||
- `src/AcDream.Core/Physics/BSPQuery.cs:364-428` (acdream's current
|
||
`AdjustSphereToPlane` — read directly, confirmed byte-faithful bare-radius)
|
||
- `docs/architecture/retail-divergence-register.md` rows AD-66 (line 65
|
||
header context), AD-69 (line 166)
|
||
- `tests/AcDream.Core.Tests/Physics/S4AdjustOffsetConformanceTests.cs`
|
||
(the two `[Skip]`-ed AD-66 tests, read but left untouched)
|
||
- `docs/research/2026-08-07-s4b-tangent-rest-contract.md` (this slice's
|
||
pinned contract; the stop clause invoked above is quoted from its D0
|
||
section verbatim)
|