docs(research): #167 leash constants recovered from raw binary + arming flow (Campaign P P5)
Byte-decoded GetStart/MaxConstraintDistance (0x0050ebc0/0x0050ec10) from the PDB-paired v11.4186 binary: start = outdoor 10 m / indoor 5 m, max = outdoor 50 m / indoor 20 m; the player-vs-remote branch is vestigial (identical constant pairs). ACE's start mapping is inverted - do not copy. Full SmartBox::HandleReceivedPosition 0x00453fd0 arming flow transcribed (remote self-anchor post-MoveOrTeleport, player anchors to received position, teleport branch zeroes velocity). TS-35 + #167 retire together at the P5 port. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8cbe45f0b2
commit
9a0d1ae6f5
1 changed files with 96 additions and 0 deletions
96
docs/research/2026-07-30-constraint-leash-constants.md
Normal file
96
docs/research/2026-07-30-constraint-leash-constants.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# #167 ConstraintManager leash — constants recovered + arming flow (Campaign P P5)
|
||||
|
||||
**2026-07-30.** Both #167 blockers are now research-solved; only the port
|
||||
remains. No cdb session was needed: the two "unknown x87 constants" were
|
||||
recovered by decoding the raw machine code of the matching binary
|
||||
(`C:\Users\erikn\Downloads\acclient.exe`, v11.4186, PDB-paired — verified
|
||||
GUID match per the retail debugger toolchain doc).
|
||||
|
||||
## 1. The getters, byte-decoded (FACT)
|
||||
|
||||
`CPhysicsObj::GetStartConstraintDistance @ 0x0050ebc0` and
|
||||
`GetMaxConstraintDistance @ 0x0050ec10` are FPU-return getters whose
|
||||
`fld` operands Binary Ninja elided (the pseudo-C shows a bare
|
||||
`this->m_position;`). Raw bytes (file offset 0x10ebc0/0x10ec10):
|
||||
|
||||
```
|
||||
3b 0d 58 3d 84 00 cmp ecx, [0x00843d58] ; this == player_object?
|
||||
75 1d jnz non_player
|
||||
8b 41 4c mov eax, [ecx+0x4c] ; m_position.objcell_id
|
||||
25 ff ff 00 00 and eax, 0xFFFF
|
||||
3d 00 01 00 00 cmp eax, 0x100
|
||||
73 07 jae indoor ; low16 >= 0x100 = EnvCell
|
||||
d9 05 <rdata> fld dword [outdoor_const]
|
||||
c3 ret
|
||||
indoor: d9 05 <..> fld dword [indoor_const]
|
||||
c3 ret
|
||||
non_player: ; identical cell test, second constant pair
|
||||
```
|
||||
|
||||
Constant values read from `.rdata`:
|
||||
|
||||
| | player outdoor | player indoor | remote outdoor | remote indoor |
|
||||
|---|---|---|---|---|
|
||||
| Start (0x007c6abc..c8) | **10.0** | **5.0** | 10.0 | 5.0 |
|
||||
| Max (0x007c6acc..d8) | **50.0** | **20.0** | 50.0 | 20.0 |
|
||||
|
||||
Two consequences (FACT):
|
||||
|
||||
1. **The player-vs-remote branch is vestigial** — both sides load
|
||||
identical values. Effective semantics: `start = outdoor 10 m /
|
||||
indoor 5 m`, `max = outdoor 50 m / indoor 20 m` (indoor = cell low16
|
||||
≥ 0x100).
|
||||
2. **ACE's `GetStartConstraintDistance` is INVERTED**
|
||||
(`ACE PhysicsObj.cs:620`: outdoor 5 / indoor 10). ACE's max mapping
|
||||
(outdoor 50 / indoor 20) matches the binary. Do NOT copy ACE's start
|
||||
mapping. (feedback_acme_oracle / binary-wins rule.)
|
||||
|
||||
## 2. The arming flow — `SmartBox::HandleReceivedPosition @ 0x00453fd0` (FACT)
|
||||
|
||||
Pseudo-C lines ~92940-93060. After the update-time staleness gates and
|
||||
`unset_parent`/`SetPlacementFrame` handling:
|
||||
|
||||
- **Remote object** (`arg2 != this->player`): call
|
||||
`MoveOrTeleport(obj, &recvPos, ts, arg5, arg6)`; **only if it returns
|
||||
nonzero** (the position was NOT hard-teleport-applied), arm the leash
|
||||
**anchored to the object's own current position**:
|
||||
`ConstrainTo(obj, &obj->m_position, start, max)` (0x00454254-72).
|
||||
- **Player, teleport-newer** (`newer_event(TELEPORT_TS, ts)`):
|
||||
`SmartBox::TeleportPlayer(&recvPos)`, then
|
||||
`ConstrainTo(player, &recvPos, start, max)` — anchored to the
|
||||
**received** position — then `set_velocity(player, {0,0,0}, 1)`
|
||||
(0x0045415f-c0).
|
||||
- **Player, normal**: `ConstrainTo(player, &recvPos, start, max)`
|
||||
anchored to the received position; then, if
|
||||
`cmdinterp->UsePositionFromServer() && arg5`,
|
||||
`InterpolateTo(&recvPos, -GetAutonomyLevel())` (0x004541c9-422c).
|
||||
|
||||
The taper/enforcement side (`ConstraintManager::UseTime` feeding
|
||||
`adjust_offset`, `IsFullyConstrained = ConstraintDistanceMax * 0.9 <
|
||||
offset`) is already ported in
|
||||
`src/AcDream.Core/Physics/Motion/ConstraintManager.cs` (R5-V1,
|
||||
`docs/research/2026-07-03-r5-managers/`); it has simply never been armed.
|
||||
|
||||
## 3. Port shape for P5 (INFERENCE — implementation guidance)
|
||||
|
||||
1. Add the four-constant getters (outdoor/indoor by full cell id low16)
|
||||
at the body/host layer; keep the vestigial player/remote split OUT
|
||||
(note it in a code comment with this doc as the cite).
|
||||
2. Arm at acdream's inbound-position equivalents of the three branches:
|
||||
the remote UpdatePosition acceptance tail (post-`MoveOrTeleport`
|
||||
routing in the live-entity network update path) and the local
|
||||
player's accepted-Position path (normal + teleport). Anchor per §2.
|
||||
3. `PhysicsBody.IsFullyConstrained` (register TS-35 stub) becomes a read
|
||||
through `PositionManager`/`ConstraintManager`, so
|
||||
`jump_is_allowed`'s ported gate fires (WeenieError 0x47) while
|
||||
rubber-banding. TS-35 and #167 retire together, same commit.
|
||||
4. Conformance tests: constant table incl. the ACE-inversion pin
|
||||
(outdoor start MUST be 10, not 5); leash-armed jump refusal;
|
||||
remote-vs-player anchor difference; teleport-branch velocity zero.
|
||||
|
||||
## Open questions
|
||||
|
||||
None for the constants/flow. Remaining implementation risk is only
|
||||
where acdream's position-acceptance seams sit today (J6.3 moved
|
||||
teleport correlation into Runtime — the implementer must find the
|
||||
current owner rather than trusting older file cites).
|
||||
Loading…
Add table
Add a link
Reference in a new issue