acdream/docs/plans/2026-07-29-physics-parity-campaign.md
Erik 978ce1dcda docs: Campaign P - physics retail-feel parity plan (P1-P7 + final visual matrix)
User-directed pre-vendor detour from the 2026-07-29 physics audit. Goal:
Retail Movement Parity v1 - zero physics TS rows, no unargued
feel-affecting AP rows, issues #262/#165/#166/#116/#167/#72/#153 closed,
one batched connected visual matrix. Sonnet implements, Opus reviews at
slice boundaries. Roadmap gains the Campaign P entry and records Campaign
N's user-accepted closure; CLAUDE.md current-state pointer updated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 23:32:42 +02:00

11 KiB
Raw Blame History

Campaign P — Physics Retail-Feel Parity

Filed: 2026-07-29. Directed by the user as a pre-vendor-management detour after the same-day physics/collision retail-fidelity audit. The world-interaction program (Slice 5, vendor browsing) resumes when this campaign closes.

Execution model: Claude drives autonomously slice-to-slice. Sonnet subagents implement bounded chunks against this plan's specs; an Opus review subagent gates every slice boundary. The only user stops are (a) the final batched connected visual matrix, (b) a DO-NOT-RETRY conflict, (c) anything destructive. All the CLAUDE.md workflow rules apply: grep-named-first → pseudocode → port → conformance test; register moves in the same commit; no workarounds.


The parity goal (the autonomy contract)

"Retail Movement Parity v1" — the campaign is DONE when all of the following are auditable-true:

  1. Zero physics-scope temporary stopgaps. TS-1, TS-4, TS-5, TS-23, TS-46 retired by porting the retail mechanism. TS-24, TS-35, TS-40 either retired or re-classified (IA/AD) with a recorded justification.
  2. No feel-affecting approximations left unargued. AP-7 resolved by decoding retail's friction state gate; AP-10 restored to retail's 0.1 m water sink-in; AP-25 replaced by the retail effective-skill chain (vitae/enchantment-aware); AP-71 (check_entry_restrictions) ported.
  3. Issue ledger: #262, #165, #166, #116, #167, #72, #153 closed; the stale "pending visual gate" statuses on #172/#173/#174/#175/#41 reconciled (folded into the final matrix below). Explicitly excluded: #235 (user-deferred 2026-07-27) and #256/#257 (lifecycle/memory, not physics feel — separate track).
  4. Verification: each port carries decomp citations + conformance tests; dotnet build + full Release suite green at every slice commit; one batched connected visual matrix (below) passes at the end, run by the user.

Anything not in this list is out of scope for the campaign — file it, don't chase it.


Slices

P1 — Stat-coupled movement (burden / stamina / vitae) — retires TS-5, AP-25, the burden gap

Today PlayerWeenie.SetBurden has zero callers, CanJump is always true, JumpStaminaCost is 0, and pushed run/jump skill is attributeBonus + init + ranks only. Retail modulates movement by character state continuously.

Retail anchors (named decomp, verified 2026-07-29):

  • ACCWeenieObject::{CanJump 0x0058c400, JumpStaminaCost 0x0058c440, InqJumpVelocity 0x0058c520, InqRunRate 0x0058c560, InqMaxRunRate 0x0058c5a0} — thin delegations to the qualities DB (pseudo-C ~406512).
  • CACQualities::{InqMaxRunRate 0x00591b20, CanJump 0x00591b50, JumpStaminaCost 0x00591b90, InqRunRate 0x00592800, InqJumpVelocity 0x00592980, InqLoad 0x0058f130} (pseudo-C ~412901413975, ~409756) — the real load/skill/vitae composition. InqJumpVelocity ends in sqrt(GetJumpHeight(...) * 19.6) (pc 413975).
  • MovementSystem::{GetRunRate 0x006b0950, GetJumpHeight 0x006b09b0, JumpStaminaCost 0x006b0a40} (pseudo-C ~695958) — GetJumpHeight readable: LoadMod(load) * (skill/(skill+1300) * 22.2 + 0.05) * power / scaling, floor 0.35; JumpStaminaCost readable: ceil(((power + 0.5) * load) * 8 + 2) on the arg3==0 branch.
  • EncumbranceSystem::{EncumbranceCapacity 0x004fcc00, Load 0x004fcc40, LoadMod 0x004fcc70} (pseudo-C ~256393).
  • Cross-refs: ACE MovementSystem/EncumbranceSystem C# ports; holtburger if it models load.

Work:

  1. Port the full chain into Core (EncumbranceSystem + MovementSystem statics; PlayerWeenie becomes the CACQualities-shaped composition). Where BN x87 mush blocks a branch (GetRunRate body), use Ghidra MCP or the ACE port as the tiebreaker and cite which.
  2. Determine, from CACQualities::InqRunRate's own body, exactly which skill level retail feeds (base vs enchantment/vitae-adjusted) and port THAT — scoped to the run/jump query path only, reading vitae + relevant skill enchantments from the M3 active-effect state. Do not build a general effective-skill engine.
  3. Plumb the inputs from Runtime: burden (EncumbranceVal/capacity from PlayerDescription + property updates), current stamina (vitals), vitae. Extend RuntimeMovementSkillState (J4.4 seam) so updates flow mid-session, same as run/jump skill today.
  4. Wire the existing jump_is_allowed stamina-refusal branch and the ReportExhaustion dual-dispatch gate (R3-W4 seam) to a real consumer, matching retail's refusal/weak-jump behavior.
  5. Conformance tests: formula tables (golden values incl. 800-skill cap, load-mod knees at 100%/200%, stamina cost ceil), gating tests (no-stamina jump refusal), plumbing tests (burden/stamina/vitae changes move the produced rate). Register: delete TS-5 + AP-25 rows, note the retirement in the same commit.

P2 — Response-layer edge family — retires TS-1, TS-4, AP-7; closes #166, #116

The collision response layer (what happens after a hit): ground friction, cliff edges, downhill landings, near-perpendicular wall slides. One oracle-driven pass; the physics digest's DO-NOT-RETRY table binds every subagent here.

Work (research doc FIRST, then port):

  1. AP-7: decode the state gate on retail's friction block (calc_friction region, pseudo-C ~276702-276705) that lets retail use threshold 0.25 without hammering normal locomotion (the reverted L.3c attempt). Ghidra MCP for the x87 branch if BN is garbled.
  2. TS-1: port the EdgeSlide → PrecipiceSlide / CliffSlide chain (precipice context, steep-plane bookkeeping) replacing our stop-at-edge.
  3. #166: port the landing "sled" (Sledding state set/clear sites; the sled friction constants already sit in calc_friction).
  4. TS-4: replace the Path-6 steep-poly in-place-slide shortcut with retail's SetCollide → Path-4 → ContactPlane landing chain, and remove the two BSP-layer SetSlidingNormal writes (retail's only in-transition writer is validate_transition).
  5. #116: the near-perpendicular lateral-slide loss + first-airborne- frame divergence, driven by the existing tick-22760 replay and D4 pins.
  6. Apparatus: extend the trajectory-replay tests; capture fixtures before changing behavior. Register: delete TS-1/TS-4/AP-7 rows.

P3 — Remote-object residuals — retires TS-46, TS-23; closes #165; narrows/retires AD-25

  1. TS-46: pass the Setup's verbatim sphere LIST into the transition (CPhysicsObj::transition 0x00512dc0 → init_sphere) instead of the two-scalar reconstruction, for local player and remotes; derive remote step-up/step-down from the Setup instead of the pinned 0.4 m. Captured-fixture replays must stay green or be re-baselined with evidence.
  2. AD-25: align the remote post-resolve with the ported handle_all_collisions (grounded-bounce rule) as the player half already did in #182.
  3. #165: remotes visibly penetrate walls before stopping — diagnose against the (now Setup-true) sweep; suspect list starts at the catch-up step length vs sweep sub-steps.
  4. TS-23: parse PlayerKillerStatus from PlayerDescription/property updates and plumb PK/PKLite/Impenetrable onto local + remote player movers (OBJECTINFO::init 0x0050cf30 state bits). Non-PK ACE behavior must be provably unchanged.

P4 — World specials — retires AP-71, AP-10

  1. AP-71: port the CObjCell::check_entry_restrictions gate at the head of find_env_collisions (pc:309576) — barred house cells block at the threshold client-side.
  2. AP-10: restore retail's 0.1 m water sink-in; while there, verify the water-contact step behavior (WATER_CONTACT_TS consumers) against retail and file anything found.

P5 — Deferred fidelity — closes #167, #153, #72

  1. #167: decode the two unknown x87 ConstraintManager constants (Ghidra) and port leash arming.
  2. #153: the far-teleport arrival onto an unstreamed landblock near a 192 m edge — apparatus first (the issue's own trigger table), then the streaming-gap hold shape ALREADY sketched there (freeze the per-tick resolve until the landblock loads — the async equivalent of retail's synchronous load; this is an AD row, not a workaround, and gets filed as one).
  3. #72: close on the R6 evidence (DAT-authored omega ±1.5 rad/s is live; the cdb confirmation ask is obsolete).

P6 — #262 login run-on-the-spot (live defect)

Probe-instrumented fresh-process login repros (ACDREAM_PROBE_RESOLVE=1

  • net probes) against local ACE; the issue's hypothesis list is the script. Root cause, fix, regression test. No workarounds (no auto-recall, no synthetic position kick). Runs serialized (owns the build tree + client).

P7 — Ledger + camera feel

  1. Retire the stale TS-25 row (outbound stance ships via RawState.CurrentStyle since #219) and refresh TS-24/TS-35/TS-40 classifications.
  2. #115 camera-drag: investigation-only against CameraManager constants (AD-37's vector-nlerp vs retail quaternion-slerp is the prime suspect); fix if a concrete divergence falls out, otherwise re-classify with evidence.
  3. Reconcile #172/#173/#174/#175/#41 statuses via the final matrix.

Final batched connected visual matrix (the ONE user gate)

  1. Burden >100%: run slows, jump shrinks; ~200%: barely moves/jumps.
  2. Repeated jumps drain stamina; low stamina → weak/refused jump; exhaustion behavior matches retail.
  3. Fresh vitae: movement penalty present.
  4. Walk off a cliff/roof edge: slides over like retail, no dead stop.
  5. Downhill jump landing: sled glide + bounce.
  6. Shallow-angle wall graze: lateral slide preserved.
  7. Packed crowd: spacing + shuffle-out unchanged (regression).
  8. Two-client: remote stops at walls without visible penetration; remote ceiling-jump bounces down immediately (#173); Holtburg portal platform step-up (#172); door Use after jumping (#174); closed-door collision matches the visual door (#175); observed-player blips gone (#41).
  9. Locked/barred house: blocked at the threshold.
  10. Wading: slight retail sink-in.
  11. ~20 fresh logins: no run-on-the-spot.
  12. Regression sweep: walk/run/strafe/turn/jump/stairs/doors/water edges feel unchanged from the accepted R6 baseline.

Risk notes

  • P2 and P3 touch the frozen-adjacent transition internals — every subagent prompt must carry the digest's DO-NOT-RETRY table and the no-workarounds rule; 3 failed attempts on any item = stop and build apparatus, per feedback_apparatus_for_physics_bugs.
  • TS-46 changes the collision capsule of every mover; the captured replay fixtures pin behavior — re-baseline only with a recorded retail argument.
  • P1's enchantment-aware skill read is the scope-creep risk; it is bounded to the run/jump query path by this plan.