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

205 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.