diff --git a/docs/research/2026-07-30-constraint-leash-constants.md b/docs/research/2026-07-30-constraint-leash-constants.md new file mode 100644 index 00000000..20015f6b --- /dev/null +++ b/docs/research/2026-07-30-constraint-leash-constants.md @@ -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 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).