20 KiB
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:
- 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.
- 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. - 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).
- 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
Status (2026-07-30): COMPLETE. Landed at 9355ddce; Opus review
APPROVE at 001e466d (which also retired UN-8 — the CanJump polarity is
byte-PROVEN load < 2.0 from the PDB-paired binary — and recorded the
PK-timer jump-cost decode for P3). Full Release suite 9,880/0/5 at the
slice gate. Retail's vitae/enchant chain reuses the M3 bucket-4
representation; JumpStaminaCost never refuses (weak-jump only) — the
plan's formula shorthand had the +0.5 operand wrong and the
implementation follows the decomp's (load+0.5)*power*8+2. AP-127 filed
(two minor bonus properties, out of bounded scope).
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 ~412901–413975, ~409756) — the real load/skill/vitae composition.InqJumpVelocityends insqrt(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/EncumbranceSystemC# ports; holtburger if it models load.
Work:
- Port the full chain into Core (
EncumbranceSystem+MovementSystemstatics;PlayerWeeniebecomes 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. - 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. - 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. - Wire the existing
jump_is_allowedstamina-refusal branch and theReportExhaustiondual-dispatch gate (R3-W4 seam) to a real consumer, matching retail's refusal/weak-jump behavior. - 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
Status (2026-07-30, FINAL — Campaign P final physics slice): TS-1 and
AP-7 retired same-day as originally recorded. TS-4 is now ALSO
RETIRED — the oracle follow-up pass
(docs/research/2026-07-30-ts4-116-oracle-plan.md) found the freeze the
first implementation attempt hit was one layer downstream of Path 6
(inside AdjustOffset's crease projection against a purely-vertical
offset — a genuine retail-identical degeneracy, not a bug) and ran the
plan's own decisive confirming test: Ts4SteepRoofWedgeCaptureTests's
horizontal-velocity variant (matching the realistic live-play input that
originally validated the shortcut) converges cleanly with the shortcut
removed. The Path-6 steep-poly shortcut is deleted from both
BSPQuery.cs and FlatBspQuery.cs; the pure-vertical degenerate case is
pinned (not fixed) as register row AD-56. #116 is narrowed, not
closed: shape-2 (D4 first-airborne-frame hard-stop) is CLOSED — the
oracle plan's structural dispatch-routing hypothesis was confirmed by
instrumentation with no cdb session needed, and the D4 pin is un-skipped.
Shape-1 (tick-22760 lateral-slide loss) got a real, independently-decomp-
confirmed fix (Path 6's foot-clear/head-hit branch now returns Collided
SetCollisionNormaldirectly, matching pc:323824-323834/ACEBSPTree.cs:221-230), but the confirming replay showed this does NOT explain tick-22760 itself — that mover is grounded (dispatches through Path 5, not Path 6) and the actual "no normal recorded" mechanism (SpherePath.PrecipiceSlide'sfind_crossed_edge-false fallback) is independently confirmed byte-exact retail behavior too. The remaining divergence is most likely this test's simplified door-registration fixture, not the response layer — see ISSUES.md #116 and the oracle plan's Addendum 2 for the full trace and the concrete next step (re-run against the faithful Setup-based door registration). AD-55 (the sled slope-flatness constant, split out of AP-7's retirement) is ALSO retired this same slice — byte-provencos(10°)per the oracle plan's Addendum.
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):
- AP-7: decode the state gate on retail's friction block
(
calc_frictionregion, 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. - TS-1: port the
EdgeSlide → PrecipiceSlide / CliffSlidechain (precipice context, steep-plane bookkeeping) replacing our stop-at-edge. - #166: port the landing "sled" (Sledding state set/clear sites;
the sled friction constants already sit in
calc_friction). - TS-4: replace the Path-6 steep-poly in-place-slide shortcut with
retail's
SetCollide → Path-4 → ContactPlanelanding chain, and remove the two BSP-layerSetSlidingNormalwrites (retail's only in-transition writer isvalidate_transition). - #116: the near-perpendicular lateral-slide loss + first-airborne- frame divergence, driven by the existing tick-22760 replay and D4 pins.
- 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
- 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. - AD-25: align the remote post-resolve with the ported
handle_all_collisions(grounded-bounce rule) as the player half already did in #182. - #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.
- TS-23: parse PlayerKillerStatus from PlayerDescription/property
updates and plumb PK/PKLite/Impenetrable onto local + remote player
movers (
OBJECTINFO::init 0x0050cf30state bits). Non-PK ACE behavior must be provably unchanged.
P4 — World specials — retires AP-71, AP-10
Status (2026-07-30): COMPLETE, including a same-day Opus review
fix. AP-71 landed at d6c3f865 (20 new conformance tests); AP-10 landed at
cc8d57a2 (12 new conformance tests). Complete solution suite at that gate:
9,946 total across 9 test projects, 9,941 passed, 5 skipped, 0 failed on a
clean run. One run in the same session saw a single unrelated flake
(AcDream.Content.Tests.Vfx.RetailDatLoaderTests .AnimationCache_CoalescesSameDidAndAllowsUnrelatedReadsInParallel, a
parallel-cache-coalescing timing test untouched by either commit) that
passed 3/3 in isolation and on the immediate re-run — full-suite parallel
contention, not a regression (independently fixed afterward at dc0468cc).
P4 review verdict: FIX-FIRST (2026-07-30). RestrictionObjPrevalenceInspectionTests
(3b5e0992) measured the installed cell DAT: 103,766 of 729,888 EnvCells
across 1,293 landblocks — the entire housing estate, restrictionObj GUIDs
0x70xxxxxx — carry a baked RestrictionObj. AP-71's fail-closed default
(landed with CanMoveInto deliberately unmodeled, per the original AP-129
row) would have locked every apartment/cottage/villa interior for every
player, including its own owner — a live regression, not the "inert in dev
content" the row assumed. Fixed at 7a0f836a: ACCWeenieObject::CanMoveInto
(0x0058da40) and RestrictionDB::IsAllowedIn (0x005ae8f0) are now ported
verbatim, fed end-to-end from CreateObject's HouseOwner/HouseRestrictions/
Monarch PWD-tail fields (previously parsed-and-discarded) plus a new live
House_UpdateRestrictions (0x0248) parser, and resolved through a new
PhysicsEngine.Objects property wired to the canonical ClientObjectTable
in RuntimeEntityObjectLifetime (production fix, not just gate logic — an
unwired table still fails closed). AP-129 is narrowed (not retired) to the
genuine residual: no sequence-based staleness rejection for
House_UpdateRestrictions (low-probability, self-correcting), and the
outdoor CLandCell restriction path (a separate DAT structure) remains
unported, unaffected by this fix. Gate: AcDream.Core.Tests 4,049 passed / 2
skipped / 0 failed; AcDream.Core.Net.Tests 761 passed / 0 skipped / 0
failed; complete solution suite 9,961 total, 9,956 passed, 5 skipped, 0
failed.
- AP-71: port the
CObjCell::check_entry_restrictionsgate at the head offind_env_collisions(pc:309576) — barred house cells block at the threshold client-side. Landed: the gate is wired at the top of the indoor branch ofTransition.FindEnvCollisions;CellPhysics .RestrictionObjis fed from the DAT-bakedEnvCell.RestrictionObjfield (§4.3's open question resolved via ACE's DatLoader + an independentChorizite.DatReaderWriterreflection probe — it's a plain per-cell DAT field, not a live wire override) in both the dev and production caching paths, at zero bake-format cost. The mover'sCanBypassMoveRestrictions(BF_ADMIN & BF_IMMUNE_CELL_RESTRICTIONS) is decoded via the TS-23 PWD-bitfield pipeline. The original landing deliberately leftCanMoveIntounmodeled (fail-closed default, filed as AP-129) — the P4 review found this fails closed for the ENTIRE housing estate and required the fix-first pass described above. - AP-10: restore retail's 0.1 m water sink-in; while there, verify
the water-contact step behavior (
WATER_CONTACT_TSconsumers) against retail and file anything found. Landed: the dry-corner constant is restored (full suite green — the sticky Contact/OnWalkable bit argument held);WaterContactis now produced at everyContact/OnWalkablecommit site. No confirmed retail consumer ofWATER_CONTACT_TSwas found this pass; filed as #264 along with two other explicitly-unverified water items (theENTIRELY_WATERethereal/swim terrain-collision exemption, and jump/swim movement-effects) — none block this port.
P4 review addendum (2026-07-30): APPROVED after one FIX-FIRST round.
The initial AP-71 landing failed closed with CanMoveInto unmodeled; the
prevalence inspection (3b5e0992) proved that locks all 103,766 housing
EnvCells. 7a0f836a ports CanMoveInto/IsAllowedIn verbatim
(owner/self/null-db admit; unresolved object blocks), captures the
previously-discarded HouseOwner/Monarch PWD fields, parses
House_UpdateRestrictions 0x0248 live, and wires the canonical object
table into the gate. AP-129 narrowed to the sequence-byte and outdoor
RestrictionTables residuals. Suite 9,956/0/5.
P5 — Deferred fidelity — closes #167, #153, #72
Status (2026-07-30): item 1 (#167) COMPLETE. Both blockers resolved
without Ghidra/cdb — the two x87-elided constants were byte-decoded
straight from the matching binary's raw machine code
(docs/research/2026-07-30-constraint-leash-constants.md). The leash is
now armed at every current acdream inbound-position acceptance seam
(ConstraintDistance, LiveEntityNetworkUpdateController,
PlayerMovementController.SetPosition/BlipPosition), the per-tick
PhysicsBody.IsFullyConstrained push replaces the always-false stub, and
register row TS-35 is deleted. Full Core/Runtime/App suites pass with new
conformance tests (leash-armed jump refusal, teleport-vs-blip
anchor/teardown, taper reduction, remote-tick push). Items 2 (#153) and 3
(#72) remain open.
- #167: decode the two unknown x87 ConstraintManager constants (Ghidra) and port leash arming.
- #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).
- #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
- Retire the stale TS-25 row (outbound stance ships via
RawState.CurrentStylesince #219) and refresh TS-24/TS-35/TS-40 classifications. - #115 camera-drag: investigation-only against
CameraManagerconstants (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. - Reconcile #172/#173/#174/#175/#41 statuses via the final matrix.
Implementation-phase closeout (2026-07-30) — awaiting the matrix
Every implementation slice is COMPLETE and Opus-reviewed; the campaign now waits on the single user gate below.
Register scorecard: goal-enumerated physics stopgaps at ZERO — TS-1, TS-4 (+ its FlatBspQuery twin), TS-5, TS-23, TS-35, TS-46 retired by ports; TS-25 retired on #219 evidence; TS-24→AD-57, TS-40→AD-58 re-argued. AP-7, AP-10, AP-25, AP-71 retired; UN-8 and AD-55 retired by raw-byte proof; AD-25 retired. New argued rows: AP-127/128/129, AD-53/54/55(retired)/56/57/58.
Issues: #72, #153, #167, #255 closed; #116 shape-2 closed /shape-1 narrowed to a probable harness artifact (response layer byte-verified); #165 diagnosed to the render-lag candidate (matrix scenario 8a decides); #166 all four composite deviations landed (scenario 5 decides); #262 apparatus permanently live + 3/3 clean probe logins (scenario 11 decides). Notable finds along the way: ACE's inverted leash-start mapping, ACE's radians/degrees sled-constant bug (cos 10°), the housing-lockout prevalence catch (103,766 restricted cells), and the HouseOwner/Monarch PWD fields that were parsed-and-discarded.
Verification: every slice gated on the complete Release suite; final state 9,977 passed / 0 failed / 4 skipped (the D4 un-skip retired one permanent skip). Suite grew from 8,826 to 9,977 tests over the campaign (+1,151, all conformance/golden/pin coverage).
Final batched connected visual matrix (the ONE user gate)
- Burden >100%: run slows, jump shrinks; ~200%: barely moves/jumps.
- Repeated jumps drain stamina; low stamina → weak/refused jump; exhaustion behavior matches retail.
- Fresh vitae: movement penalty present.
- Walk off a cliff/roof edge: slides over like retail, no dead stop.
- Downhill jump landing: sled glide + bounce.
- Shallow-angle wall graze: lateral slide preserved.
- Packed crowd: spacing + shuffle-out unchanged (regression).
- 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).
- Locked/barred house: blocked at the threshold.
- Wading: slight retail sink-in.
- ~20 fresh logins: no run-on-the-spot.
- 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.