# CPhysicsObj::set_description @ 0x00514F40 — three FPU-elided gates recovered ## Verification chain 1. **Binary/PDB pairing**: `py tools/pdb-extract/check_exe_pdb.py "C:/Users/erikn/Downloads/acclient.exe"` → `=== MATCH: this exe pairs with our acclient.pdb ===` (GUID `9e847e2f-777c-4bd9-886c-22256bb87f32`, linker timestamp 2013-09-06T00:17:56Z). Confirmed before any byte reads. 2. **PE section mapping** (hand-parsed via a one-off script, `scratchpad/pe_read.py`): image base `0x00400000`; `.text` VA=0x00401000 RawPtr=0x00001000; `.rdata` VA=0x00792000 RawPtr=0x00392000 (holds the FP constants below). VA→file-offset: `file_off = raw_ptr + (VA - image_base - section_virt_addr)`. 3. Raw bytes of the function (`0x00514F40`–`0x00515153`) were dumped and hand-disassembled instruction-by-instruction, cross-checked line-by-line against `docs/research/named-retail/acclient_2013_pseudo_c.txt` lines 283130–283251 (function body) so every address in the trace lines up with a named pseudo-C statement. 4. **Ghidra MCP**: not available this session — no CodeBrowser open on port 8080/8081 (both probes returned empty). Not needed; binary + ACE agreement below is already two independent confirmations. 5. **ACE cross-check**: `references/ACE/Source/ACE.Server/Physics/PhysicsObj.cs` (main checkout, not the af5e worktree — ACE isn't vendored there), `set_description`, lines 3557–3568. ACE's C# independently reproduces all three predicates exactly as decoded from the binary below. Binary is the ground truth per project policy; ACE here is 100% consistent with it, so no conflict to adjudicate. Confidence: **byte-certain** for all three. Every constant was read directly from `.rdata`, every comparison/jump opcode was decoded from the raw instruction stream, and the result matches ACE's independent port line-for-line. --- ## Conditional 1 — friction OUTER gate (pseudo-C line 283219, VA 0x0051505a) ### Bytes ``` 0051504f: d9 46 68 FLD DWORD PTR [ESI+0x68] ; ST(0) = (double)esi->friction (PhysicsDesc.friction @ +0x68) 00515052: dc 15 10 46 79 00 FCOM QWORD PTR [0x00794610] ; compare ST(0) vs constant, no pop (value reused below) 00515058: df e0 FNSTSW AX 0051505a: f6 c4 05 TEST AH, 0x05 ; mask = C0(bit0) | C2(bit2) 0051505d: 7b 15 JNP 0x00515074 ; jump (skip friction block) iff PF=0 ``` ### Constant VA `0x00794610` (.rdata, file offset `0x00394610`), 8 bytes: `00 00 00 00 00 00 00 00` → **`0.0` (double, exact)**. ### Decoding the jump `TEST AH,0x05` ANDs AH with the C0|C2 status bits, then the parity flag (PF) reflects the parity of that AND result. Case table for `FCOM esi->friction, 0.0` (ST0=friction): | relation | C0 | C2 | C3 | AH&0x05 | popcount | PF | |---|---|---|---|---|---|---| | friction > 0.0 | 0 | 0 | 0 | 0x00 | 0 | 1 | | friction < 0.0 | 1 | 0 | 0 | 0x01 | 1 | **0** | | friction == 0.0 | 0 | 0 | 1 | 0x00 | 0 | 1 | | unordered (NaN) | 1 | 1 | 1 | 0x05 | 2 | 1 | `JNP` (jump on PF=0) only fires for the strict `<` case. So the jump (which SKIPS the whole friction reassignment block, landing at the shared cleanup at `0x00515074`) is taken **only when `friction < 0.0`**; every other case (`>= 0.0`, and — as an accepted compiler-quirk edge case irrelevant to real game data — unordered/NaN) falls through into the block. ### Recovered predicate ```c // outer gate: proceed to the friction-assignment logic only when friction is non-negative if (esi->friction >= 0.0f) { // ... inner compare (Conditional 2) ... } ``` --- ## Conditional 2 — friction INNER compare (pseudo-C line 283226, VA 0x0051506a) ### Bytes ``` 0051505f: dc 15 c0 28 79 00 FCOM QWORD PTR [0x007928c0] ; compare ST(0)=friction vs constant, no pop 00515065: df e0 FNSTSW AX 00515067: f6 c4 41 TEST AH, 0x41 ; mask = C0(bit0) | C3(bit6) 0051506a: 74 08 JZ 0x00515074 ; jump (skip assignment) iff (AH&0x41)==0 0051506c: d9 9f bc 00 00 00 FSTP DWORD PTR [EDI+0xbc] ; this->friction = friction (field @ +0xbc), pops ST(0) ``` Pseudo-C had already fully rendered the C0/C2/C3 synthetic-byte construction for this one (only the final `test ah,0x41`→bool collapse was marked unimplemented), so the byte read is a confirmation rather than a fresh recovery. ### Constant VA `0x007928c0` (.rdata, file offset `0x003928c0`), 8 bytes: `00 00 00 00 00 00 f0 3f` → **`1.0` (double, exact; IEEE-754 bit pattern `0x3FF0000000000000`)**. ### Decoding the jump Mask `0x41` = C0(below) | C3(equal). `JZ` (jump when the TEST result is zero, i.e. neither bit set) skips the assignment when friction is strictly `>` 1.0. Falls through (assigns `this->friction`) when `friction <= 1.0` (below-or-equal family, exactly as flagged in the task). This is the canonical `jbe` idiom. ### Recovered predicate ```c // inner compare: only assign if friction also passes the upper bound if (esi->friction <= 1.0f) this->friction = esi->friction; ``` ### Combined (conditionals 1+2) ```c if (esi->friction >= 0.0f && esi->friction <= 1.0f) this->friction = esi->friction; ``` This is byte-for-byte what ACE's port does at `PhysicsObj.cs:3557-3558`: `if (desc.Friction >= 0.0f && desc.Friction <= 1.0f) Friction = desc.Friction;` --- ## Conditional 3 — translucency gate (pseudo-C line 283240, VA 0x0051509f) ### Bytes ``` 0051508b: d9 44 24 24 FLD DWORD PTR [ESP+0x24] ; ST(0) = (float)translucency (local copy of esi->translucency, field @ esi+0x70) 0051508f: d8 1d 80 6a 7c 00 FCOMP DWORD PTR [0x007c6a80] ; compare ST(0) vs constant, WITH pop (single precision, reg field=3) 00515095: 8b d1 MOV EDX, ECX 00515097: 89 97 b8 00 00 00 MOV [EDI+0xb8], EDX ; this->translucencyOriginal = translucency (unconditional) 0051509d: df e0 FNSTSW AX 0051509f: f6 c4 44 TEST AH, 0x44 ; mask = C2(bit2) | C3(bit6) 005150a2: 7b 15 JNP 0x005150b9 ; jump (skip live-translucency apply) iff PF=0 005150a4: ... ; fallthrough: this->translucency = translucency; PartArray propagation ``` ### Constant VA `0x007c6a80` (.rdata, file offset `0x003c6a80`), 4 bytes: `00 00 00 00` → **`0.0f` (single-precision float, exact)**. Note this compare is single-precision (`d8`/`FCOMP m32`), unlike the two friction compares above which are double-precision (`dc`/`FCOM m64`) — matches the pseudo-C's `((long double)0f)` literal notation (the `f` suffix is BN flagging a float-typed constant) versus `((long double)0.0)` for the friction case. ### Decoding the jump Case table for `FCOMP translucency, 0.0f` (ST0=translucency), mask `0x44` = C2(unordered) | C3(equal): | relation | C0 | C2 | C3 | AH&0x44 | popcount | PF | |---|---|---|---|---|---|---| | translucency > 0.0 | 0 | 0 | 0 | 0x00 | 0 | 1 | | translucency < 0.0 | 1 | 0 | 0 | 0x00 | 0 | 1 | | translucency == 0.0 | 0 | 0 | 1 | 0x40 | 1 | **0** | | unordered (NaN) | 1 | 1 | 1 | 0x44 | 2 | 1 | `JNP` (PF=0) fires **only** for the exact-equal-to-zero case. So the jump — which skips applying live `translucency`/PartArray propagation, leaving only the unconditional `translucencyOriginal` write — is taken **only when `translucency == 0.0f`**. Every other case (`>0`, `<0`, and unordered/NaN as a compiler-quirk edge case) falls through and applies. ### Recovered predicate ```c // translucencyOriginal is ALWAYS written (this happens before the gate, unconditionally) this->translucencyOriginal = translucency; // live translucency + PartArray propagation only when translucency is non-zero if (translucency != 0.0f) { this->translucency = translucency; if (this->part_array != 0) CPartArray::SetTranslucencyInternal(this->part_array, translucency); } ``` Matches ACE's port at `PhysicsObj.cs:3562-3568` exactly: ```csharp TranslucencyOriginal = desc.Translucency; if (desc.Translucency != 0.0f) { Translucency = desc.Translucency; if (PartArray != null) PartArray.SetTranslucencyInternal(desc.Translucency); } ``` --- ## Summary table | # | Gate | Predicate (apply-when) | Constant | Cert. | |---|---|---|---|---| | 1 | friction outer | `friction >= 0.0f` | `0.0` (double) @ VA 0x00794610 | byte-certain | | 2 | friction inner | `friction <= 1.0f` | `1.0` (double) @ VA 0x007928c0 | byte-certain | | 3 | translucency | `translucency != 0.0f` | `0.0f` (float) @ VA 0x007c6a80 | byte-certain | All three: no unresolved cases. The only caveat on all three is a decompiler/compiler-codegen edge case around NaN (unordered operands fall into the "true"/apply bucket rather than IEEE-strict "always false"), which is a documented quirk of this exact MSVC x87 codegen pattern and not something the retail struct's `float` fields would ever hit in practice (friction/translucency are authored data, never NaN). ## Port note (C3b, `RuntimeRemoteBodyDescription.cs`) acdream's friction port uses `f >= 0.0f && f <= 1.0f`, which deliberately SKIPS NaN rather than reproducing the unordered-goes-to-apply codegen quirk tabled above — the sanctioned modern-boundary deviation this doc pre-declared. Elasticity's setter port (`!(e >= 0f)` first arm) and translucency's `!= 0.0f` gate both route NaN exactly as the binary does (0f and apply respectively); ACE's `set_elasticity` sends NaN to 0.1f and is divergent from the binary on that edge.