docs(R3-W0): ambiguity pins A1-A10 — all textually resolved, adversarially verified
Workflow-produced pin pass over the R3 decomp extraction (3 independent raw re-readers + adversarial refuters on the two load-bearing pins + synthesis). No pin was refuted; none blocks on cdb. Headlines: - A1: motion_allows_jump 0x005279e0 is a BLOCKLIST (0 = pass, 0x48 = blocked) — the BN extraction's whitelist annotation was inverted (corrected in-place in §3a + §10). Retail blocks FALLEN 0x40000008 and PASSES Falling 0x40000015; ACE mis-transcribed the exact-id term as Falling (one-off slip — ACE's own charge gates use Fallen). Definitive literal-uint blocklist table recorded. - A3: the raw-vs-interpreted dual-dispatch gate is IsThePlayer (vtable slot +0x14, bound via the ACCWeenieObject vftable dump @0x007e3ea0), NOT ACE's IsCreature — in all four functions. Anti-artifact proof: HitGround/LeaveGround nearby call the +0x2c IsCreature slot, so BN distinguishes the slots locally. Copying ACE's gate would send remotes down apply_raw_movement against an empty raw state. - A4: MovementParameters absolute-mask table pinned from acclient.h's own bitfield struct; retail ctor default 0x1EE0F has can_charge CLEAR (ACE sets it true) and walk_run_threshold 15.0 (ACE 1.0). - A5/A6: both jump-velocity epsilons are 0.000199999995f (acdream's 0.001 must change); get_leave_ground_velocity's fallback matrix is GLOBAL→LOCAL (index-pattern match against Frame::globaltolocalvec). - A10: definitive error-code table from an exhaustive return-site sweep, incl. a second 0x24 site (DoInterpretedMotion action-class contact block) absent from the plan row. - Adjacent: move_to_interpreted_state's apply_current_movement arg2 is a garbled allowJump = (motion_allows_jump(old fwd) == 0) — polarity trap for W-commits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a6235a36f5
commit
cd0289bea2
2 changed files with 309 additions and 25 deletions
277
docs/research/2026-07-02-r3-motioninterp/W0-pins.md
Normal file
277
docs/research/2026-07-02-r3-motioninterp/W0-pins.md
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
# R3-W0 — ambiguity pins and ACE-oddity adjudications
|
||||||
|
|
||||||
|
The verbatim extraction is `r3-motioninterp-decomp.md` (line-anchored); the port
|
||||||
|
plan with the full ambiguity table is `r3-port-plan.md` §0. This note records the
|
||||||
|
PINNED resolutions the W1–W6 ports code against. Every pin below was produced by an
|
||||||
|
independent raw re-read of `docs/research/named-retail/acclient_2013_pseudo_c.txt`
|
||||||
|
(line numbers cited are that file's) and A1/A3 additionally survived an adversarial
|
||||||
|
refutation pass (independent Ghidra MCP decompiles + vtable-dump slot resolution).
|
||||||
|
**No pin was refuted; none blocks on cdb.**
|
||||||
|
|
||||||
|
## Pinned
|
||||||
|
|
||||||
|
- **A1 — `motion_allows_jump` (0x005279e0) polarity: PINNED as a BLOCKLIST.
|
||||||
|
`0` = jump allowed (pass), `0x48` = jump BLOCKED** (a
|
||||||
|
"YouCantJumpFromThisPosition"-class error code). The extraction doc's original
|
||||||
|
§3a "whitelist / 0x48 = allowed" note was inverted (now corrected in-place, same
|
||||||
|
commit). Confidence: **high** (adversarially verified; refutation failed).
|
||||||
|
|
||||||
|
Definitive blocklist (port as **literal uint ranges**, NOT enum-ordinal ranges):
|
||||||
|
|
||||||
|
| Motion id (literal uint) | MotionCommand names | Returns |
|
||||||
|
|---|---|---|
|
||||||
|
| `[0x1000006f, 0x10000078]` | MagicPowerUp01..MagicPowerUp10 | `0x48` BLOCK |
|
||||||
|
| `[0x10000128, 0x10000131]` | TripleThrustLow..MagicPowerUp07Purple | `0x48` BLOCK |
|
||||||
|
| `0x40000008` (exact) | **Fallen** | `0x48` BLOCK |
|
||||||
|
| `[0x40000016, 0x40000018]` | Reload, Unload, Pickup | `0x48` BLOCK |
|
||||||
|
| `[0x4000001e, 0x40000039]` | AimLevel..MagicPray | `0x48` BLOCK |
|
||||||
|
| `[0x41000012, 0x41000014]` | Crouch, Sitting, Sleeping | `0x48` BLOCK |
|
||||||
|
| everything else — incl. **Falling `0x40000015`**, Ready `0x41000003`, Dead `0x40000011`, all turn/sidestep ids (`0x6500000d` TurnRight etc.) | — | `0` PASS |
|
||||||
|
|
||||||
|
Evidence (all boundaries re-derived from the verbatim body, raw 304908–304931):
|
||||||
|
`005279e9 if (arg2 > 0x40000018) { 00527a24 if (arg2 > 0x41000014) return 0;
|
||||||
|
00527a39 if ((arg2 < 0x41000012 && (arg2 < 0x4000001e || arg2 > 0x40000039)))
|
||||||
|
return 0; } 005279e9 else if (arg2 < 0x40000016) { 005279f7 if (arg2 > 0x10000131)
|
||||||
|
{ 00527a18 if (arg2 != 0x40000008) 00527a1c return 0; } 005279f7 else if ((arg2 <
|
||||||
|
0x10000128 && (arg2 < 0x1000006f || arg2 > 0x10000078))) 00527a10 return 0; }
|
||||||
|
00527a40 return 0x48;` — the middle band `[0x40000016..0x40000018]` satisfies
|
||||||
|
neither outer branch and falls straight to `return 0x48`. Polarity sealed by four
|
||||||
|
independent callers plus the top of the chain:
|
||||||
|
1. `jump_is_allowed` 0x005282b0 (raw 305539–305542): `eax_7 =
|
||||||
|
motion_allows_jump(this, this->interpreted_state.forward_command); if (eax_7
|
||||||
|
!= 0) return eax_7;` — nonzero returned verbatim as the failure code; all
|
||||||
|
success paths return `eax_7 == 0`.
|
||||||
|
2. `DoInterpretedMotion` 0x00528360 (raw 305597–305607): params bit `0x20000`
|
||||||
|
(disable_jump_during_link) FORCES `eax_5 = 0x48` — a jump-*disable* bit
|
||||||
|
forcing the value only makes sense if `0x48` = block; `eax_5` is stashed as
|
||||||
|
the queue node's `jump_error_code` (add_to_queue arg4 → node +0xc, raw
|
||||||
|
305044), which `jump_is_allowed`'s head peek returns as an error when nonzero.
|
||||||
|
3. `CMotionInterp::jump` 0x00528780 (raw 305801–305812): `result =
|
||||||
|
jump_is_allowed(...); if (result != 0) { standing_longjump = 0; return
|
||||||
|
result; }` — the jump only executes on the zero path.
|
||||||
|
4. Sibling convention: `jump_charge_is_allowed` (raw 304940–304948) and
|
||||||
|
`charge_jump` (raw 305453–305466) both `return 0` on pass / `0x48`/`0x49` on
|
||||||
|
block; `charge_jump`'s success path arms `standing_longjump` then `return 0`.
|
||||||
|
|
||||||
|
**Falling vs Fallen: retail blocks Fallen (0x40000008) and PASSES Falling
|
||||||
|
(0x40000015). ACE mis-transcribed** — ACE MotionInterp.cs:770-779's exact-id
|
||||||
|
term is `substate == (uint)MotionCommand.Falling` and Fallen appears nowhere in
|
||||||
|
its motion_allows_jump: the exact inverse of retail on those two ids. ACE's own
|
||||||
|
jump_charge_is_allowed (MotionInterp.cs:736) uses Fallen, matching retail's
|
||||||
|
0x40000008 in both charge gates — the Falling term is a one-off ACE transposition
|
||||||
|
slip, not a deliberate change. Falling still can't yield mid-air jumps in retail:
|
||||||
|
`jump_is_allowed`'s airborne gate (raw 305559–305570) independently returns
|
||||||
|
`0x24` unless transient_state has Contact(1)+OnWalkable(2). **Port: block Fallen,
|
||||||
|
pass Falling.**
|
||||||
|
|
||||||
|
- **A2 — `jump_is_allowed` pending-head peek: PINNED — the peek fires whenever the
|
||||||
|
queue is non-empty; there is NO `Count > 1` gate.** Raw 305524–305556
|
||||||
|
(0x005282b0): `if (CPhysicsObj::IsFullyConstrained(this->physics_obj) != 0)
|
||||||
|
return 0x47;` THEN `class LListData* head_ = this->pending_motions.head_; ...
|
||||||
|
if (head_ != 0) eax_6 = *(int32_t*)((char*)head_ + 0xc); if ((head_ == 0 ||
|
||||||
|
eax_6 == 0)) { eax_6 = jump_charge_is_allowed(this); ... } return eax_6;` — if
|
||||||
|
the head exists AND its `jump_error_code` (+0xc) is nonzero, the whole
|
||||||
|
charge/motion/stamina chain is skipped and the peeked code returned. Ordering
|
||||||
|
note (both decompilers agree): **IsFullyConstrained (→0x47) is checked BEFORE
|
||||||
|
the peek** — port in that order. ACE L752-753's `PendingMotions.Count > 1 &&`
|
||||||
|
has no retail counterpart; do not copy. ACE L746's `WeenieObj == null &&
|
||||||
|
!WeenieObj.IsCreature()` NPE-typo also confirmed wrong — retail (raw
|
||||||
|
305517–305520) is `if (weenie_obj != 0) eax_2 = weenie_obj->vtable->IsCreature();
|
||||||
|
if ((weenie_obj != 0 && eax_2 == 0))` → non-creature-with-weenie skips the ground
|
||||||
|
check. Confidence: **high**.
|
||||||
|
|
||||||
|
- **A3 — dual-dispatch gate: PINNED to `IsThePlayer` (vtable slot +0x14), NOT
|
||||||
|
`IsCreature` (+0x2c), in all four functions.** Confidence: **high**
|
||||||
|
(adversarially verified: independent Ghidra decompile shows raw offset +0x14 at
|
||||||
|
all four sites vs +0x2c in HitGround; the ACCWeenieObject vftable dump at
|
||||||
|
0x007e3ea0, raw 1035101–1035135, binds +0x14 → `ACCWeenieObject::IsThePlayer`
|
||||||
|
0x0058C3D0 and +0x2c → `IsCreature` 0x0058C3F0).
|
||||||
|
|
||||||
|
| Function | Address @ raw line | Weenie tested | Predicate → raw path | Dispatch args | Entry gate |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `apply_current_movement` | 0x00528870 @305838 | `this->weenie_obj` | `(weenie==0 \|\| IsThePlayer()) && movement_is_autonomous()` | `(arg2, arg3)` passthrough | `physics_obj != 0 && initted != 0` |
|
||||||
|
| `ReportExhaustion` | 0x005288d0 @305861 | `this->weenie_obj` | same | `(0, 0)` | same |
|
||||||
|
| `SetWeenieObject` | 0x00528920 @305884 | **incoming `arg2`** (stored to `this->weenie_obj` first) | same | `(1, 0)` | same |
|
||||||
|
| `SetPhysicsObject` | 0x00528970 @305911 | `this->weenie_obj` | same | `(1, 0)` | `arg2 != 0 && initted != 0` (after `this->physics_obj = arg2`) |
|
||||||
|
|
||||||
|
Predicate true → `apply_raw_movement(a, b)`; false → `apply_interpreted_movement(a, b)`.
|
||||||
|
Verbatim (apply_current_movement, raw 305846–305855): `if (weenie_obj != 0)
|
||||||
|
eax_2 = weenie_obj->vtable->IsThePlayer(); if (((weenie_obj == 0 || eax_2 != 0)
|
||||||
|
&& CPhysicsObj::movement_is_autonomous(this->physics_obj) != 0)) {
|
||||||
|
apply_raw_movement(this, arg2, arg3); return; } apply_interpreted_movement(this,
|
||||||
|
arg2, arg3);`. Semantics: `IsThePlayer` (raw 406494–406500) = `this->id ==
|
||||||
|
SmartBox::smartbox->player_id` (neg/sbb idiom); `movement_is_autonomous` (raw
|
||||||
|
276443–276447) = `return this->last_move_was_autonomous;`. Anti-artifact proof:
|
||||||
|
`HitGround` (raw 306005) and `LeaveGround` (raw 306031), ~0x250 bytes away in
|
||||||
|
the same decomp region, call `IsCreature()` — BN distinguishes the two slots
|
||||||
|
locally, ruling out a slot-shift mislabel. **ACE MotionInterp.cs:430-438's
|
||||||
|
`IsCreature` predicate is a genuine server-side divergence — do NOT copy** (a
|
||||||
|
remote player is a creature but not the player; the wrong gate sends remotes
|
||||||
|
down `apply_raw_movement` against an empty raw state).
|
||||||
|
Watchouts: (a) a FIFTH IsThePlayer site exists in `move_to_interpreted_state`
|
||||||
|
(raw 305977): per queued action node, `if ((weenie_obj->vtable->IsThePlayer()
|
||||||
|
== 0 || node->autonomous == 0))` → execute — i.e. server-echoed autonomous
|
||||||
|
actions are SKIPPED for the local player (local-echo suppression). (b) the BASE
|
||||||
|
`CWeenieObject::vftable` data dump at 0x007e3df4 (raw 1035061–1035094) is
|
||||||
|
misaligned (BN data-label overlap artifact) — only the derived ACC table at
|
||||||
|
0x007e3ea0 is trustworthy. (c) the PDB also has a distinct
|
||||||
|
`ACCWeenieObject::IsPlayer` (slot +0x10, 0x0058C890) — never conflate the three
|
||||||
|
`Is*Player*` virtuals.
|
||||||
|
|
||||||
|
- **A4 — MovementParameters bit numbering: PINNED via the retail PDB's own
|
||||||
|
bitfield struct, acclient.h:31423-31443** — bit-for-bit identical to ACE's
|
||||||
|
`MovementParamFlags`. Confidence: **high**. Absolute masks:
|
||||||
|
|
||||||
|
| Mask | Flag | Mask | Flag |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `0x1` | can_walk | `0x400` | use_spheres |
|
||||||
|
| `0x2` | can_run | `0x800` | set_hold_key |
|
||||||
|
| `0x4` | can_sidestep | `0x1000` | autonomous |
|
||||||
|
| `0x8` | can_walk_backwards | `0x2000` | modify_raw_state |
|
||||||
|
| `0x10` | can_charge | `0x4000` | modify_interpreted_state |
|
||||||
|
| `0x20` | fail_walk | `0x8000` | cancel_moveto |
|
||||||
|
| `0x40` | use_final_heading | `0x10000` | stop_completely |
|
||||||
|
| `0x80` | sticky | `0x20000` | disable_jump_during_link |
|
||||||
|
| `0x100` | move_away | | |
|
||||||
|
| `0x200` | move_towards | | |
|
||||||
|
|
||||||
|
Ctor default (raw 300510–300534, 0x00524380): `(bitfield & 0xfffdee0f) |
|
||||||
|
0x1ee0f` → **0x1EE0F** sets {can_walk, can_run, can_sidestep,
|
||||||
|
can_walk_backwards, move_towards, use_spheres, set_hold_key, modify_raw_state,
|
||||||
|
modify_interpreted_state, cancel_moveto, stop_completely}; clears {can_charge,
|
||||||
|
fail_walk, use_final_heading, sticky, move_away, autonomous,
|
||||||
|
disable_jump_during_link}. Runtime reads all consistent: DoMotion @306183
|
||||||
|
byte1-sign = `0x8000` cancel_moveto → interrupt; @306188 byte1&8 = `0x800`
|
||||||
|
set_hold_key → `SetHoldKey(hold_key_to_apply, ((__inner0 >> 0xf) & 1))` — note
|
||||||
|
**SetHoldKey's second arg is the cancel_moveto bit**, not a constant; @306213
|
||||||
|
byte1&0x20 = `0x2000` modify_raw_state → RawMotionState::ApplyMotion mirror.
|
||||||
|
DoInterpretedMotion @305597 full-dword `& 0x20000` = disable_jump_during_link →
|
||||||
|
jump_error_code forced `0x48`; @305609/305617 byte1&0x40 = `0x4000`
|
||||||
|
modify_interpreted_state. StopMotion @305684 sign → interrupt; @305705
|
||||||
|
byte1&0x20 → RawState.RemoveMotion.
|
||||||
|
**ACE-divergence traps for W1:** ACE's MovementParameters ctor sets
|
||||||
|
`CanCharge = true` (MovementParameters.cs:58) — retail's default has `0x10`
|
||||||
|
CLEAR; port `can_charge = false`. ACE also changed Default_WalkRunThreshold to
|
||||||
|
1.0 (L50) vs retail's **15.0** (@300519). Retail's ctor caches the computed
|
||||||
|
default in a static (`normal_bitfield`, @300523–300533) whose AND-mask preserves
|
||||||
|
uninitialized bits 18–31 on first call — harmless (only bits 0–17 defined); port
|
||||||
|
as a plain 0x1EE0F default.
|
||||||
|
|
||||||
|
- **A5 — `get_jump_v_z` (0x00527aa0): PINNED to ACE's shape; the BN text is
|
||||||
|
x87-flag garbled exactly as suspected** (raw 304953–304984 returns FCOM flag
|
||||||
|
synthesis on two paths — unreadable either way; the Ghidra decompile is clean
|
||||||
|
and adjudicates). Behavior: `jump_extent < 0.000199999995f` → **0.0f**; else
|
||||||
|
clamp extent to max **1.0**; `weenie_obj == NULL` → **10.0f** (`___real_41200000`);
|
||||||
|
`InqJumpVelocity(extent, &out)` false → **0.0f**; true → out. Matches ACE
|
||||||
|
L634-652. The epsilon literal is verbatim in the raw @304959: `long double
|
||||||
|
temp0 = ((long double)0.000199999995f);`. **acdream's 0.001
|
||||||
|
(MotionInterpreter.cs:278 JumpExtentEpsilon) must become 0.000199999995f.**
|
||||||
|
Confidence: **high**.
|
||||||
|
|
||||||
|
- **A6 — `get_leave_ground_velocity` (0x005280c0) zero-fallback matrix direction:
|
||||||
|
PINNED as GLOBAL→LOCAL** — decisively, by index-pattern match against Frame's
|
||||||
|
own transform pair. The fallback (raw 305434–305440) computes `out.i =
|
||||||
|
fl2gv[3i+0]*v.x + fl2gv[3i+1]*v.y + fl2gv[3i+2]*v.z` — the IDENTICAL row-linear
|
||||||
|
pattern as `Frame::globaltolocalvec` 0x00452550 (raw 91466–91471), while
|
||||||
|
`Frame::localtoglobalvec` 0x004524f0 (raw 91452–91457) uses the transpose
|
||||||
|
(`fl2gv[0]*x + fl2gv[3]*y + fl2gv[6]*z`). BN's "m_fl2gv = local→global" reading
|
||||||
|
was name-misled; the applied math is global→local, matching ACE's
|
||||||
|
`GlobalToLocalVec(Velocity)`. **acdream's `Quaternion.Inverse(Orientation)`
|
||||||
|
transform (MotionInterpreter.cs:1024) IS global→local — keep.** Mechanics:
|
||||||
|
function is void (writes the caller's Vector3); body order =
|
||||||
|
`get_state_velocity(out)` → `out.z = get_jump_v_z()` → fallback fires ONLY when
|
||||||
|
`|x| AND |y| AND |z|` are ALL `< 0.000199999995f` (epsilon appears three times,
|
||||||
|
@305412/305420/305427, one per component) and then overwrites ALL THREE
|
||||||
|
components (including z, discarding the jump v_z) with
|
||||||
|
`globaltolocal(physics_obj->m_velocityVector)`. Epsilon must become 0.0002
|
||||||
|
(currently 0.001). Confidence: **high**.
|
||||||
|
|
||||||
|
- **A7 — `MovementManager::MotionDone` uninit-`edx` relay + dead `arg2` in
|
||||||
|
`CMotionInterp::MotionDone`.** Decompiler artifact (adjudicated in the decomp
|
||||||
|
doc §6d). Port as `MotionDone(uint motion, bool success)` passing the completed
|
||||||
|
node's motion id; the interp body reads NEITHER param in this build — **pop the
|
||||||
|
HEAD unconditionally, never match by motion id**. Keep the params for R5
|
||||||
|
signature parity. (Copied verbatim from the plan §0 adjudication — no new
|
||||||
|
research needed.)
|
||||||
|
|
||||||
|
- **A8 — `enter_default_state` (0x00528c80) does NOT clear `pending_motions`: it
|
||||||
|
APPENDS the `{0, 0x41000003, 0}` sentinel onto whatever queue exists.** Raw
|
||||||
|
306128–306154: the body resets only raw_state + interpreted_state (fresh
|
||||||
|
default-constructed copies) and calls `CPhysicsObj::InitializeMotionTables`,
|
||||||
|
then `void* eax_2 = operator new(0x10); ... *(+8) = 0x41000003; *(+0xc) = 0;`
|
||||||
|
followed by the tail-append (`tail_ == 0 ? head_ = eax_2 : *tail_ = eax_2;
|
||||||
|
tail_ = eax_2`) — byte-identical in shape to `add_to_queue`'s append (raw
|
||||||
|
305047–305057), just inlined — then `initted = 1; LeaveGround(this);`. No
|
||||||
|
drain/clear anywhere. ACE's `PendingMotions = new LinkedList` reset is the
|
||||||
|
divergence; pre-existing nodes survive in retail (W2 test asserts this). The
|
||||||
|
MovementManager lazy-create double-call (§6g) is genuine retail — do not "fix";
|
||||||
|
R3's direct-bind construction calls it once explicitly. Confidence: **high**.
|
||||||
|
|
||||||
|
- **A9 — `StopCompletely` (0x00527e40) jump-snapshot quirk: PINNED verbatim.**
|
||||||
|
Raw 305216–305227 ordering: `interrupt_current_movement(physics_obj);` → `eax_2
|
||||||
|
= motion_allows_jump(this, this->interpreted_state.forward_command);` (**OLD**
|
||||||
|
forward_command, BEFORE the overwrite) → write raw_state
|
||||||
|
{forward_command=0x41000003, forward_speed=1f, sidestep_command=0,
|
||||||
|
turn_command=0} → same four writes to interpreted_state →
|
||||||
|
`StopCompletely_Internal(physics_obj)` → `add_to_queue(this, 0, 0x41000003,
|
||||||
|
eax_2)` → cell==0 → `RemoveLinkAnimations` → `return 0`. Entry: physics_obj==0
|
||||||
|
→ return 8. **Retail touches ONLY forward cmd/speed + sidestep/turn COMMANDS —
|
||||||
|
it does NOT write sidestep_speed or turn_speed** (acdream's 1.0 speed resets
|
||||||
|
are a divergence to remove per plan J9). Golden: the queued node's error code
|
||||||
|
reflects the pre-stop command. Confidence: **high**.
|
||||||
|
|
||||||
|
- **A10 — error-code semantics: PINNED with the definitive table** (exhaustive
|
||||||
|
`return <code>` sweep over raw 304908–306277 + 300150–300540; 19 return sites +
|
||||||
|
1 store site, all attributed). Codes are local-only (never on the wire) — safe
|
||||||
|
renumber in W1. Confidence: **high**.
|
||||||
|
|
||||||
|
| Code | Retail meaning | Sites (address @ raw line) |
|
||||||
|
|---|---|---|
|
||||||
|
| `8` | no physics_obj | StopCompletely @305214; DoInterpretedMotion @305579; StopInterpretedMotion @305639; StopMotion @305680; jump @305798; DoMotion @306165 |
|
||||||
|
| `0x24` | not grounded / no contact | jump_is_allowed @305570 (0x005282f0 — gravity-state creature without Contact+OnWalkable; **also covers its physics_obj==0 case**, see note); **DoInterpretedMotion @305622-305623** (action-class motion `arg2 & 0x10000000` blocked by contact_allows_move — second site, absent from the plan row) |
|
||||||
|
| `0x3f` | Crouch 0x41000012 in combat stance | DoMotion @306196 |
|
||||||
|
| `0x40` | Sitting 0x41000013 in combat stance | DoMotion @306199 |
|
||||||
|
| `0x41` | Sleeping 0x41000014 in combat stance | DoMotion @306202 |
|
||||||
|
| `0x42` | `motion & 0x2000000` outside NonCombat (0x8000003d) | DoMotion @306205 |
|
||||||
|
| `0x45` | action depth: GetNumActions ≥ 6 | DoMotion @306209 |
|
||||||
|
| `0x47` | general movement failure | jump_is_allowed @305525 (IsFullyConstrained); jump_is_allowed @305549+305556 (staged, returned when `JumpStaminaCost(arg2, arg3) == 0`); CMotionInterp::PerformMovement @306227 (`type-1 > 4`); MovementManager::PerformMovement @300201 (`type-1 > 8`) |
|
||||||
|
| `0x48` | jump BLOCKED by motion/position (**NOT a success sentinel** — the A1 polarity fix) | motion_allows_jump @304930; jump_charge_is_allowed @304948 (Fallen or Crouch..Sleeping); charge_jump @305459 (same predicate); STORED (not returned) as node jump_error_code in DoInterpretedMotion @305605 on disable_jump_during_link |
|
||||||
|
| `0x49` | weenie `CanJump(jump_extent)` refused | jump_charge_is_allowed @304941; charge_jump @305454 |
|
||||||
|
|
||||||
|
Note: `jump_is_allowed` returns **0x24** (not 8) when `physics_obj == 0` (raw
|
||||||
|
structure 305512 + 305570: the `if (this->physics_obj != 0) {...}` wrapper falls
|
||||||
|
out to `return 0x24;`) — the "8 = no physics obj" convention holds everywhere
|
||||||
|
else but NOT there; ACE (L744-745) returns NoPhysicsObject there — small ACE
|
||||||
|
divergence, port retail. W1 renames acdream's WeenieError values to these
|
||||||
|
numerics (adds 0x3f/0x40/0x41/0x42/0x45; fixes 0x24/0x47/0x48 semantics — the
|
||||||
|
current enum returns 0x48 for airborne where retail returns 0x24).
|
||||||
|
|
||||||
|
## Adjacent findings (load-bearing for W-commits, discovered while pinning)
|
||||||
|
|
||||||
|
- **`move_to_interpreted_state` call-site garble (W-commit trap):** raw
|
||||||
|
305946–305949 renders `int32_t esi_1 = -(eax_2); ...
|
||||||
|
apply_current_movement(this, 1, -((esi_1 - esi_1)));` — the second arg
|
||||||
|
algebraically collapses to constant 0, which is decompiler nonsense (sbb/neg
|
||||||
|
idiom). Intended semantics per ACE MotionInterp.cs:796-799: `allowJump =
|
||||||
|
(motion_allows_jump(OLD forward_command) == 0)` passed as
|
||||||
|
apply_current_movement's second arg. Port `allowJump = (eax_2 == 0)` — another
|
||||||
|
minor polarity trap in the A1 family.
|
||||||
|
- **`jump` (0x00528780) details for J7:** contains `interrupt_current_movement`
|
||||||
|
(@305800); on success stores `jump_extent = arg2` and calls
|
||||||
|
`set_on_walkable(obj, 0)`; clears `standing_longjump` ONLY on the failure path.
|
||||||
|
|
||||||
|
## W0 cdb capture (pending, non-blocking)
|
||||||
|
|
||||||
|
No pin above depends on it — all ten are textually pinned with adversarial
|
||||||
|
verification. One live session feeds the R3 goldens (W2/W3/W5/W6): bp
|
||||||
|
`CMotionInterp::MotionDone` / `add_to_queue` / `jump` / `jump_is_allowed` /
|
||||||
|
`jump_charge_is_allowed` / `charge_jump` / `motion_allows_jump` / `HitGround` /
|
||||||
|
`LeaveGround` / `StopCompletely` / `set_hold_run` / `SetHoldKey` /
|
||||||
|
`enter_default_state` with arg+ret logging (pattern `tools/cdb/l2g-observer.cdb`);
|
||||||
|
user protocol per `r3-port-plan.md` §0 (idle / walk / run / shift-toggle /
|
||||||
|
tap-jump / full-charge jump / running jump / standing long-jump / jump-while-
|
||||||
|
crouched / jump-in-combat-stance (0x3f–0x42 family) / walk off a ledge (the A6
|
||||||
|
momentum fallback) / land / emote-while-running / 7 rapid emotes (0x45 cap) /
|
||||||
|
logout drain). Nice-to-have live spot-checks it will also provide: A1 polarity
|
||||||
|
(log `motion_allows_jump` arg2+eax during walk/run/mid-fall/crouch jumps) and the
|
||||||
|
A2 head-peek short-circuit.
|
||||||
|
|
@ -402,17 +402,22 @@ that interpreted/remote commands skip.
|
||||||
005279e0 }
|
005279e0 }
|
||||||
```
|
```
|
||||||
|
|
||||||
Cleaned: returns `0x48` (nonzero = "jump is currently allowed given this in-flight
|
Cleaned **[polarity corrected per W0-pins A1 — the original note here was inverted]**:
|
||||||
motion id") for a specific whitelist of motion-id ranges, else `0` (disallowed):
|
returns `0x48` = jump BLOCKED ("YouCantJumpFromThisPosition"-class error code) for a
|
||||||
- `arg2 in [0x1000006f, 0x10000078]` (movement/locomotion range 1) — allowed.
|
specific **blocklist** of motion-id ranges, else `0` = jump allowed (pass). Callers
|
||||||
- `arg2 in [0x10000128, 0x10000131]` — allowed.
|
treat nonzero as the error (`jump_is_allowed` @305541: `if (eax_7 != 0) return eax_7;`;
|
||||||
- `arg2 == 0x40000008` — allowed.
|
`jump` 0x00528780 executes only when the chain returns 0). Blocked set:
|
||||||
- `arg2 == 0x40000016` through `0x40000018` inclusive — allowed (falls through the
|
- `arg2 in [0x1000006f, 0x10000078]` (MagicPowerUp01..MagicPowerUp10) — BLOCKED.
|
||||||
`else if` since neither branch rejects it; note `< 0x40000016` is the else-if guard,
|
- `arg2 in [0x10000128, 0x10000131]` (TripleThrustLow..MagicPowerUp07Purple) — BLOCKED.
|
||||||
so `0x40000016..0x40000018` skip both inner branches and hit `return 0x48` directly).
|
- `arg2 == 0x40000008` (**Fallen** — NOT Falling; ACE mis-transcribed this as Falling) — BLOCKED.
|
||||||
- `arg2 in [0x4000001e, 0x40000039]` — allowed.
|
- `arg2 == 0x40000016` through `0x40000018` inclusive (Reload/Unload/Pickup) — BLOCKED
|
||||||
- `arg2 in [0x41000012, 0x41000014]` — allowed (jump-charge stance ids).
|
(falls through the `else if` since neither branch passes it; note `< 0x40000016` is
|
||||||
- everything else outside `(0x40000018, 0x41000014]` range or the whitelisted holes → `0`.
|
the else-if guard, so `0x40000016..0x40000018` skip both inner branches and hit
|
||||||
|
`return 0x48` directly).
|
||||||
|
- `arg2 in [0x4000001e, 0x40000039]` (AimLevel..MagicPray) — BLOCKED.
|
||||||
|
- `arg2 in [0x41000012, 0x41000014]` (Crouch/Sitting/Sleeping) — BLOCKED.
|
||||||
|
- everything else → `0` (pass), including **Falling `0x40000015`**, Ready `0x41000003`,
|
||||||
|
Dead `0x40000011`, and all ids > `0x41000014` (turns etc.).
|
||||||
|
|
||||||
This is the "can retail let you jump given the motion currently applying" check — used
|
This is the "can retail let you jump given the motion currently applying" check — used
|
||||||
by `StopCompletely`, `DoInterpretedMotion`, `move_to_interpreted_state` to compute the
|
by `StopCompletely`, `DoInterpretedMotion`, `move_to_interpreted_state` to compute the
|
||||||
|
|
@ -435,12 +440,12 @@ by `StopCompletely`, `DoInterpretedMotion`, `move_to_interpreted_state` to compu
|
||||||
00527a50 }
|
00527a50 }
|
||||||
```
|
```
|
||||||
|
|
||||||
Cleaned: `0x49` if the weenie's `CanJump(jump_extent)` virtual rejects the current
|
Cleaned **[polarity corrected per W0-pins A1]**: `0x49` if the weenie's
|
||||||
charge amount (e.g. stamina/burden gate). Otherwise: allowed (`0x48`) only if the
|
`CanJump(jump_extent)` virtual rejects the current charge amount (e.g. stamina/burden
|
||||||
**current interpreted forward_command** is either exactly `0x40000008` OR strictly
|
gate). `0x48` = charge BLOCKED if the **current interpreted forward_command** is either
|
||||||
inside `(0x41000011, 0x41000014]` (i.e. already mid-jump-charge motion ids
|
exactly `0x40000008` (Fallen — prone) OR strictly inside `(0x41000011, 0x41000014]`
|
||||||
`0x41000012..0x41000014`); anything else → `0` (not allowed to *start* charging a jump
|
(Crouch/Sitting/Sleeping `0x41000012..0x41000014`); anything else → `0` = charge
|
||||||
from this forward-motion state).
|
allowed (pass).
|
||||||
|
|
||||||
### 3c. `CMotionInterp::get_jump_v_z` — `00527aa0` @ line 304953
|
### 3c. `CMotionInterp::get_jump_v_z` — `00527aa0` @ line 304953
|
||||||
|
|
||||||
|
|
@ -561,10 +566,10 @@ Cleaned flow: compute the leave-ground (jump) launch velocity vector.
|
||||||
005281c0 }
|
005281c0 }
|
||||||
```
|
```
|
||||||
|
|
||||||
Cleaned: `0x49` if `CanJump` virtual rejects the current charge. `0x48` ("already
|
Cleaned **[polarity corrected per W0-pins A1]**: `0x49` if `CanJump` virtual rejects
|
||||||
charging") if `forward_command` is already `0x40000008` or in `[0x41000012,0x41000014]`
|
the current charge. `0x48` = charge BLOCKED if `forward_command` is `0x40000008`
|
||||||
(no-op re-entry guard — mirrors `jump_charge_is_allowed`'s allowed set but this
|
(Fallen) or in `[0x41000012,0x41000014]` (Crouch/Sitting/Sleeping) — the same blocked
|
||||||
function is the "you're already there" early-out, not the gate). Otherwise:
|
set as `jump_charge_is_allowed`'s gate. Otherwise:
|
||||||
`standing_longjump = 1` **iff** the physics object is currently grounded
|
`standing_longjump = 1` **iff** the physics object is currently grounded
|
||||||
(`transient_state` bits `0x1` AND `0x2` both set — matches `CPhysicsObj::on_ground`,
|
(`transient_state` bits `0x1` AND `0x2` both set — matches `CPhysicsObj::on_ground`,
|
||||||
§3g) AND the mover is perfectly idle (`forward_command == 0x41000003 /*none*/`,
|
§3g) AND the mover is perfectly idle (`forward_command == 0x41000003 /*none*/`,
|
||||||
|
|
@ -901,8 +906,8 @@ forward/sidestep/turn commands to "none" (`forward_command = 0x41000003`,
|
||||||
`forward_speed = 1.0`, `sidestep_command = turn_command = 0`), call
|
`forward_speed = 1.0`, `sidestep_command = turn_command = 0`), call
|
||||||
`CPhysicsObj::StopCompletely_Internal` (the actual physics-level full stop — outside
|
`CPhysicsObj::StopCompletely_Internal` (the actual physics-level full stop — outside
|
||||||
CMotionInterp), then unconditionally enqueue a `MotionNode{0, 0x41000003, eax_2}` onto
|
CMotionInterp), then unconditionally enqueue a `MotionNode{0, 0x41000003, eax_2}` onto
|
||||||
`pending_motions` (this is the ONLY caller that passes the **precomputed** jump-allowed
|
`pending_motions` (this is the ONLY caller that passes the **precomputed** jump-error
|
||||||
flag as the queued node's `jump_error_code`, rather than recomputing after applying the
|
code (0 = allowed) as the queued node's `jump_error_code`, rather than recomputing after applying the
|
||||||
new motion — note the semantic quirk: `eax_2` was computed from the OLD
|
new motion — note the semantic quirk: `eax_2` was computed from the OLD
|
||||||
`forward_command` value, before the overwrite two lines later). If the physics object
|
`forward_command` value, before the overwrite two lines later). If the physics object
|
||||||
has no cell (detached / not in world), also strip link animations. Always returns `0`
|
has no cell (detached / not in world), also strip link animations. Always returns `0`
|
||||||
|
|
@ -1680,8 +1685,10 @@ only as confirmation that `set_hold_run`'s two callers exist and roughly where.
|
||||||
bit `0x400` set — `jump_is_allowed`), `0x3f`/`0x40`/`0x41`/`0x42` (combat-stance
|
bit `0x400` set — `jump_is_allowed`), `0x3f`/`0x40`/`0x41`/`0x42` (combat-stance
|
||||||
jump-charge rejections, `DoMotion`), `0x45` (action queue depth ≥ 6, `DoMotion`),
|
jump-charge rejections, `DoMotion`), `0x45` (action queue depth ≥ 6, `DoMotion`),
|
||||||
`0x47` (bad `MovementStruct.type`, OR `IsFullyConstrained`, OR
|
`0x47` (bad `MovementStruct.type`, OR `IsFullyConstrained`, OR
|
||||||
`JumpStaminaCost` failure), `0x48` ("jump currently allowed" success sentinel from
|
`JumpStaminaCost` failure), `0x48` (jump BLOCKED by the current motion/position —
|
||||||
`motion_allows_jump`/`jump_charge_is_allowed`/`charge_jump`'s early-out), `0x49`
|
the blocklist reject from `motion_allows_jump`/`jump_charge_is_allowed`/
|
||||||
|
`charge_jump`; **NOT a success sentinel** — 0 is the success/pass value across the
|
||||||
|
whole jump-gate family, per W0-pins A1), `0x49`
|
||||||
(`CanJump` virtual rejected the charge — stamina/burden gate).
|
(`CanJump` virtual rejected the charge — stamina/burden gate).
|
||||||
- `0.000199999995f` (~0.0002) — the epsilon used throughout for "is this velocity
|
- `0.000199999995f` (~0.0002) — the epsilon used throughout for "is this velocity
|
||||||
component effectively zero" (`get_jump_v_z`, `get_leave_ground_velocity` x3).
|
component effectively zero" (`get_jump_v_z`, `get_leave_ground_velocity` x3).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue