Commit graph

850 commits

Author SHA1 Message Date
Erik
aa96d7ad77 fix #137 (window climb): the player's collision capsule topped out at 1.2m — head sphere 0.63m too low
The 'run into the last corridor window and pop up through its roof'
report: the live callers passed sphereHeight: 1.2f into
SpherePath.InitPath, whose head-sphere formula (height - radius) put the
head sphere center at 0.72 - the capsule top at 1.2m. The top 0.63m of a
1.83m character had NO collision, so at the corridor-end window alcove
(0x8A020179 -> 0x8A02017E: 0.70m sill face, 1.3m opening, sloped funnel
behind) the step-up's placement never saw the head overlapping the
lintel solids and let the player climb in head-through-roof.

Dat truth (HumanSetup_CollisionSpheres_DatTruth): Setup 0x02000001
spheres = (0,0,0.475) r=0.48 and (0,0,1.350) r=0.48 - capsule top 1.83 =
Setup.Height 1.835. Retail collides with that sphere list verbatim
(CPhysicsObj::transition 0x00512dc0 -> init_sphere(GetNumSphere,
GetSphere, m_scale)).

Fix: PlayerMovementController + the GameWindow remote resolve now pass
sphereHeight: 1.835f (capsule top; head center 1.355 vs dat 1.350).
InitPath unchanged - captured-input replay fixtures (recorded 1.2
inputs) stay byte-identical. Register TS-46: the (radius, capsule-top)
scalar approximation of the Setup sphere list (5mm foot/head offsets;
remotes use human dims) with the retire path (plumb the sphere list).

Pins: WindowOpening_HeadCannotFit_EntryBlocked (22-frame walked approach
wall-slides at the sill, never enters 0x8A02017E) +
WindowAlcove_RaisedPlacement_HeadInLintelSolid_Collides (Path-1
placement rejects the raised head in the lintel solids) + the
WindowShaft_FullPolyDump / HumanSetup dat inspections.

Suites: Core 2562 / App 713 / UI 425 / Net 385, 0 failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:57:11 +02:00
Erik
1494f10cb5 docs #137: corridor gate PASSED; file #176 (purple seam flash) + #177 (stairs pop-in) + the window-climb repro
User gate 2026-07-06 evening: 'not collision anymore. Good.' — the
corridor-phantom arc (stub sliding-normal leak, slide_sphere opposing
branch, CheckOtherCells stale footCenter) is user-verified fixed.

Same gate surfaced three follow-ups:
- #137 remaining scope: the window/opening climb — running into the last
  corridor window steps the player up into the opening, head+camera
  through its roof. Log anchor: transit 0x8A020179 -> 0x8A02017E at
  (90.209,-41.774,-5.209) — sill ~0.8m > stepUpHeight 0.6, climbed anyway.
- #176 (NEW, render): purple floor flashing at cell seams, camera-angle
  dependent — survives the physics fix, so render-side; the seam floors
  ARE portal polygons to the under-rooms (placeholder-surface suspicion).
- #177 (NEW, render): stairs between levels pop in/out — invisible from
  the corridor, appear on entering the room, vanish on the last step
  down. The #119 visibility class, dungeon edition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:32:48 +02:00
Erik
d4869154d2 fix #137 (seam shake): CheckOtherCells queried remaining cells at a stale pre-climb center
The P2 cellar-lip lesson one loop deeper. CheckOtherCells takes footCenter
by value and used it for every cell in the loop — but a mid-loop query can
MOVE the sphere: at the Facility Hub cell boundaries the neighbor's ramp
floor full-hits, step_sphere_up climbs the foot +0.6mm and returns OK, and
the loop continued querying the REMAINING cells (including the under-room,
portal-ring-3) at the pre-climb height — 0.4mm inside the double-faced
floor slab, grazing its underside (the under-room's ceiling) within the
near-miss window. That dispatched a neg-poly step-up with a DOWNWARD
normal, whose failure funneled into slide_sphere's opposing branch ->
synthetic reversed-movement collision -> Collided -> revert, every frame:
the seam shake (and, pre-mechanism-2-fix, the original absorbing wedge's
entry).

Retail check_other_cells reads the LIVE sphere_path.global_sphere for
every cell (each cell's find_collisions receives the transition itself,
pc:272717+). Fix: re-read footCenter = sp.GlobalSphere[0].Origin at the
top of each loop iteration.

All three Issue137CorridorSeamReplayTests repros un-skipped: the
snapshot-exact west-boundary crossing (capture tick 4101), the east
deep-straddle, and the clean-run lifecycle all GREEN. Full suites: Core
2556 / App 713 / UI 425 / Net 385, 0 failures.

Visual gate pending: corridor run + the purple seam flashing re-check
(expected to be the render exposing the same per-frame oscillation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:15:05 +02:00
Erik
dbddad7a5e test #137: the seam shake reproduced offline — third mechanism characterized
The corridor gate FAILED with a changed symptom: shaking at cell seams
(+ purple floor flashing there) instead of the dead stop. Deep-probe
session (step-walk/push-back/indoor-bsp + full resolve capture) traced
the complete chain; docs/ISSUES.md #137 carries it. Short form:

- Corridor floors are double-faced portal slabs over under-rooms; the
  resting foot sphere lives within half a millimeter of three hit/straddle
  thresholds there.
- Crossing a boundary, the foot penetrates the neighbor ramp slab by
  ~0.4mm, steps up onto it successfully (+0.6mm lift, stepped=True) —
  and the lifted check position is then LOST: the following pass runs at
  the unlifted height (the P2 stale-snapshot class; retail step_up
  0x0050b6cc restores only on FAILURE).
- The unlifted re-test grazes the under-room's ceiling (the slab
  underside) within the near-miss window, dispatches a neg-poly step-up
  with a DOWNWARD normal, whose nested step-down finds no walkable at
  exact tangency -> StepUpSlide -> slide_sphere opposing branch ->
  reversed-movement collision -> Collided -> revert. Every frame = shake.

Apparatus committed:
- Issue137CorridorSeamReplayTests: 3 deterministic offline repros
  (snapshot-exact west-boundary from the capture, east deep-straddle,
  the clean-run pin), currently Skip='#137 seam shake' pending the fix.
  Key: THREE portal-ring hydration (the under-room 0x8A020166 is ring-3;
  with fewer rings the flood can't add it and the bug vanishes) + live
  Setup step heights (0.6/1.5) + probe-buffer capture for line-diffing
  offline vs live traces.
- Issue137CorridorSeamInspectionTests: portal-poly world spans (exposed
  the visual-vs-physics polygon-id conflation and the floor-portal
  topology), physics-BSP leaf membership walk, hit-normal candidate
  sweep (|align| both windings), downward-poly sweep.

NEXT: read TransitionalInsert's attempt loop against retail 0x0050b6f0,
find the restore that clobbers the successful step-up position, fix,
un-skip. The purple seam flashing is expected to be the render exposing
the same per-frame oscillation - re-check after the physics fix.

Suites: Core 2553 / App 713 / UI 425 / Net 385, 0 failures (5 skips =
2 pre-existing + the 3 parked repros).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:05:19 +02:00
Erik
e8651b3819 fix #137 (corridor phantom resolved): slide_sphere opposing branch returns Collided; the 'wall' was synthetic
The mechanism-1 theory (PortalSide portal polys solid in our physics set)
is REFUTED for the corridor repro, and the remaining half of the phantom
is fixed — no cdb session needed:

- The live hit normal (-1.00,0.03,-0.03) matches NO dat polygon: a
  world-space sweep of both seam cells + every portal-adjacent neighbor
  (CorridorSeam_FindPolygonMatchingLiveHit) returns zero candidates. The
  normal is the negated movement direction — the SYNTHETIC value
  slide_sphere's opposing-normals branch records (reversed = -gDelta).
- Cell 0x8A02016E has IDENTITY rotation (the prior session's 'rotation
  maps the portal planes into the -X wall' was a misattribution). The
  PortalSide polys to 0x011E are +-Y planes 1.4 m beside the player's
  track, perpendicular to the +X run — pos_hits_sphere's directional
  cull rejects them for that movement. They ARE referenced by the dat's
  physics-BSP leaves (CorridorCell_PhysicsBspLeafMembership), so retail
  tests them too when approached into their plane; the dat's
  keep-PortalSide / strip-ExactMatch asymmetry reads as intentional
  (solid window/grate-class portals). No portal-poly filter — exactly
  the blanket-skip the pickup warned against.
- Port fix: CSphere::slide_sphere's opposing-normals branch
  (0x005375d7-0x0053762c) records the reversed displacement and returns
  COLLIDED_TS; our port returned OK ('retail returns OK here' was a
  decomp misread), letting the step complete as-is with the synthetic
  collision normal that validate's epilogue then persisted as the
  sliding normal the wedge absorbed on. TransitionTypes opposing branch
  now returns Collided; pinned by
  SlideSphere_OpposingNormals_ReturnsCollided_WithReversedDisplacementNormal
  (RED->GREEN).
- Dat-backed replay (Issue137CorridorSeamReplayTests) reproduces the
  live hit frame verbatim (same in/out to the millimeter, same 016E->017A
  transit, same +8mm settle) and runs the corridor CLEAN: hit=no, no
  sliding normal persisted, six further forward frames advance freely.
- Inspection tests extended: physics-BSP leaf membership walk +
  hit-normal candidate sweep + downward-poly sweep (all report-style,
  dat-gated). Pickup prompt banner'd SUPERSEDED; ISSUES #137 updated
  (door half stays open); audit doc extended with the resolution.

Suites: Core 2551 / App 713 / UI 425 / Net 385, 0 failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:27:40 +02:00
Erik
a11df5b8d3 fix #137 (mechanism 2): BSP full-hit stubs leaked sliding normals — the corridor absorbing wedge
The Facility Hub corridor dead-stop's second half: after one seam hit,
every forward resolve returned ok=False hit=no with zero advance. The
body-persisted SlidingNormal (-1,0,0) projected the exactly-anti-parallel
corridor push to zero in AdjustOffset and the step loop aborted at step 0
before any collision test could refresh the state.

Audit (docs/research/2026-07-06-137-sliding-normal-lifecycle-audit.md):
retail's only in-transition sliding-normal writer is validate_transition
(0x0050ac21); the whole sphere/BSP layer writes NONE (grep-verified), and
the body persistence (SetPositionInternal 0x005154c2, SLIDING_TS bit sync
0x005154e1) runs only on transition success. Our BSPQuery Contact-branch
full-hit responses were stubs (SetCollisionNormal + SetSlidingNormal +
return Slid) where retail dispatches the real slide_sphere — so the seam
hit (a SUCCESSFUL full-advance resolve per the live log) persisted the
phantom wall's normal, which retail's lifecycle structurally cannot do.

- BSPQuery Contact foot full-hit fallback + head full-hit now route
  through Transition.SlideSphereInternal (CSphere::slide_sphere
  0x00537440 — in-frame slide, no sliding-normal write; ACE
  BSPTree.cs:202,310-316). The dead stub is rewritten as the faithful
  BSPTREE::slide_sphere wrapper.
- PhysicsEngine sliding writeback gated on ok (retail success-only
  placement; behaviorally latent, removes the failed-frame leak class).
- Register: TS-4 amended (Path-6 steep-tangent sites still write the
  normal — now documented), TS-45 added (SphereCollision's write — same
  leak class, left for a follow-up out of #171's blast radius).
- Pins: Issue137SlidingNormalLifecycleTests — both site pins RED->GREEN,
  plus the retail persist/absorb/clear wall lifecycle (validate-write
  persistence, faithful absorbed anti-parallel frame, oblique escape
  clears the bit). BSPQueryTests full-hit pin updated to the real slide.

Mechanism 1 (PortalSide portal polys solid in the physics set) stays
OPEN - #137 not closed; the corridor re-test rides that session.

Suites: Core 2545 / App 713 / UI 425 / Net 385, 0 failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:08:29 +02:00
Erik
e73e45da54 docs: #137 corridor-phantom pickup prompt for the next session
Both mechanisms + evidence pointers + the done-already oracle greps,
ordered mechanism-2-first (the sliding-normal wedge, pure acdream-side)
then the mechanism-1 retail cdb trace (needs the user running retail
at the Facility Hub corridor).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:26:32 +02:00
Erik
59705cf647 docs #137: record the portal-poly oracle greps (question stays open)
CCellStruct::UnPack loads physics polys + BSP verbatim (no portal
stripping); the CPolygon test chain is pure geometry; CCellPortal
carries portal_side/exact_match but nothing in the BSP tests consults
it. Retail's PortalSide passability is therefore not a load filter or
poly flag — remaining candidates are the transit/membership test
order or a stippling/sidedness interaction. Next: cdb-attach retail
at the 0x8A02016E corridor per the step -1 protocol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:22:05 +02:00
Erik
17ce23ce38 docs #137: corridor phantom characterized — PortalSide polys solid + sliding-normal wedge
Facility Hub corridor repro (probe + dat evidence, both mechanisms
pinned):

1. PortalSide portal polygons live IN CellStruct.PhysicsPolygons and
   acdream collides with them as plain solid geometry. Cell
   0x8A02016E's portals to 0x011E (polys 1/3/5, flags=PortalSide) are
   present in the physics set while every ExactMatch portal in the
   same cell is absent — the cell's rotation maps those portal planes
   to the world -X wall the player hit mid-corridor
   (launch-175-verify2.log:42858, n=(-1,0,0)). Retail must honor the
   portal side; oracle grep required before fixing.

2. After the single seam hit, the body-persisted SlidingNormal
   projects every subsequent forward offset to exactly zero in
   AdjustOffset - the step aborts BEFORE any collision test can
   update state (ok=False hit=no, zero advance), an absorbing wedge
   escaped only by strafing ("push through on the side"). The #116
   slide-response family: retail re-derives slide state per frame
   (get_object_info pc:279992).

Inspection fixture: Issue137CorridorSeamInspectionTests (dumps
physics-vs-portal polygon membership for the two corridor cells).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:20:05 +02:00
Erik
355e389d4c fix #175: door BSP collision poses at the motion-table closed pose
The Facility Hub double door (Setup 0x02000C9D) embeds the player into
the visual panel from one side and blocks with a phantom slab on the
other: its Setup PLACEMENT frames pose the two panels AJAR (yaw
-150/-30 deg, 0.44 m behind the doorway plane — dat-confirmed by the
Issue175 inspection) while the rendered door poses them CLOSED from
the wire-supplied motion table via the sequencer. ShadowShapeBuilder
read placement frames, so the 1.66x0.29x2.95 m physics slabs
registered at the ajar pose. Retail tests each part's LIVE
CPhysicsPart pose — for an idle door, the motion table's default
(closed) state.

Fix: ShadowShapeBuilder.FromSetup gains partPoseOverride (BSP part
shapes only); RegisterServerEntityCollision derives it from the spawn
MotionTableId via GameWindow.MotionTableDefaultPose (default style ->
first cycle -> LowFrame part frames). Null/short poses fall back
per-part to placement frames — table-less entities and landblock
statics unchanged. One-shot snapshot vs retail's per-frame live pose
is register row AP-84 (equivalent for the door lifecycle: closed ==
default pose; open == ETHEREAL bypasses collision, #150).

Pins: FromSetup_PartPoseOverride_ReplacesPlacementFrames /
_NoOverride_KeepsPlacementFrames / _ShortOverride_FallsBackPerPart.
Suites: Core 2539 / App 713 / UI 425 / Net 385 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:00:06 +02:00
Erik
2312259a93 docs: file #175 — door collision uses placement pose, not the closed pose (dat-confirmed)
User report at the Facility Hub double door (Setup 0x02000C9D): embed
into the visual panel from one side, phantom wall on the other. Dat
inspection (Issue175HubDoorPoseInspectionTests, kept as the evidence
fixture) confirms the mechanism: the Setup's Default PLACEMENT frames
pose the two panels AJAR (yaw -150/-30, origin (+-0.88, -0.44, 1.37))
while the rendered door poses them CLOSED from the wire-supplied
motion table via the sequencer. ShadowShapeBuilder reads placement
frames, so the 1.66x0.29x2.95 m physics slabs register at the ajar
pose — displaced behind the visual door. Retail tests each part's
LIVE pose (closed == motion-table default; the open swing is ETHEREAL,
#150). Fix shape filed: BSP shadow shapes for sequencer-bearing
entities must use the sequencer's part transforms, placement frames
only as the no-animation fallback. Holtburg's single door never
surfaced this because its placement pose ~= closed pose.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 16:52:43 +02:00
Erik
b54555da62 fix #174: RemoveLinkAnimations seam is HandleEnterWorld (strip + drain)
Retail CPhysicsObj::RemoveLinkAnimations (0x0050fe20) is a tailcall to
CPartArray::HandleEnterWorld (0x00517d70) -> MotionTableManager::
HandleEnterWorld (0x0051bdd0): remove_all_link_animations PLUS a full
pending_animations drain (while (head) AnimationDone(0)), each pop
relaying MotionDone so CMotionInterp pops its pending_motions node in
lockstep. acdream bound the seam to the bare sequence strip, so every
jump's LeaveGround removed the animations that queued manager nodes
were counting down on — orphaning them (NumAnims>0, anims gone) and
permanently damming BOTH queues. MotionsPending() then never drained
(probe round: last player pending=False at the first MovementJump
press; old jump motions still completing at rest minutes later) and
BeginTurnToHeading/BeginMoveForward's verbatim motions_pending gates
starved every armed moveto: ACE's mt-6 walk-to-door armed but the body
never walked (wire-proven, seqs 98-101); the close-range use turn
never completed so the deferred action was silently eaten. Doors only
worked on a fresh session (shallow queue).

Rebind both production sites (remote EnsureRemoteMotionBindings +
the player's EnterPlayerModeNow block) to Manager.HandleEnterWorld();
the sequencer wrapper was a pure passthrough so the manager call is a
strict superset. All six interp seam sites (LeaveGround, HitGround,
Dead, and the detached-object guards) are the same retail chain.
Harness mirrors updated; pins: Issue174LinkStripDrainTests (the seam
drains both queues; fresh motions queue and complete after). Suites:
Core 2535 / App 713 / UI 425 / Net 385 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 16:33:41 +02:00
Erik
62b6fa52e8 docs #174: root mechanism found — player motion-queue backlog starves movetos
Probe round (ACDREAM_PROBE_AUTOWALK) discriminated the candidates: the
local player's pending-motion queue drains at ~1 node/sec and backs up
minutes deep during active play (last pending=False at the first
MovementJump press; old jump motions still completing at rest minutes
later). MotionsPending() then starves BeginTurnToHeading/
BeginMoveForward (verbatim retail gates): far-range Uses ARE sent and
ACE replies mt-6 walk-to-door but the body never walks; close-range
Uses park on a turn that never completes — both faces of "door does
not work", while a fresh session (shallow queue) works perfectly.
Retail's queue stays shallow (the #170 cdb drain trace: adds == dones)
— this is the #170 pending_motions family, local-player DRAIN-rate
edition. Fix path: decomp CheckForCompletedMotions (0x0051bfd0) pop
semantics + queue-dump probe first; no MotionsPending bypass, no
deferral-skipping band-aids.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 16:17:43 +02:00
Erik
6811fc53d9 docs: file #174 — close-range door Use silently swallowed (wire-proven)
Facility Hub door investigation evidence: retail's byte-identical
UseItem works on the same ACE (door F74C/F74B captured, broadcast to
both clients; acdream renders the observed swing), while acdream's
later attempts never reach the wire — the AP-23 close-range deferral
parks the action on MoveToComplete(None) and the player's speculative
TurnToObject never completes after the first use of the session.
Candidates: per-attempt user-input cancel vs MotionsPending starvation
(the #170 class, local edition). Also noted: GameEventType.UseDone is
parsed nowhere, so ACE rejections are invisible. Captures:
door-use3/4.pcapng (session scratchpad). Next: ACDREAM_PROBE_AUTOWALK
round, then fix per the retail client use flow (§9a/§9b) — no
deferral-skipping band-aid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 16:10:34 +02:00
Erik
012d5c7bf7 fix #173: remote jump arcs get the retail collision-velocity response
An observed character jumping into a dungeon ceiling hovered at the
roof until its ballistic arc decayed, landing visibly late (user
report, 0x0007). The remote DR tick sweeps collision (position pinned
at the ceiling — no clip-through) but retail's post-transition velocity
response, CPhysicsObj::handle_all_collisions (pc:282699-282715:
v -= (1+elasticity)*dot(v,n)*n), was only ported for the LOCAL player
(L.3a). The remote body kept its +Z launch velocity and re-integrated
it into the roof every tick — the position was clamped but the
timeline was pure ballistics.

Retail runs handle_all_collisions after every SetPositionInternal for
every physics object, remotes included. Mirror the local reflection
block in the remote sweep's post-resolve path: same formula, same
AD-25 airborne-before-AND-after suppression (corridor slides and
landings don't reflect; the landing snap's Velocity.Z <= 0 gate stays
intact), same Inelastic zero-out for future missiles. AD-25 register
row extended to cover both sites.

Suites green: Core 2533 / App 713 / UI 425 / Net 385.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 15:34:25 +02:00
Erik
6ab269894a fix #172: port the retail CCylSphere collision family (platform step-up)
The Holtburg town-network portal platform (stab 0xC0A9B465, Setup
0x020019E3, CylSphere r=2.597m h=0.256m) blocked the player with an
endless rim slide instead of retail's step-up-onto-top — gating the
whole #137 dungeon repro. Surfaced when #149 started registering
BSP-less stab CylSpheres: the collision SHAPE became right while the
RESPONSE was still the hand-rolled AP-6 approximation (step-up gate +
radial wall-slide only).

Root cause: no cylinder-TOP support anywhere. DoStepUp's internal
step-down probe needs retail's step_sphere_down (0x0053a9b0) to land on
the flat top — a cylinder has no polygons for the walkable search — so
every step-up onto a wide cylinder failed into StepUpSlide and the
player orbited the rim (probe-confirmed: [cyl-test] result=Slid with
horizontal rim normals, launch-137-repro.log).

Port the full family verbatim: dispatcher intersects_sphere 0x0053b440
(placement/ethereal detection, step-down cap landing, walkable probe,
grounded step_sphere_up 0x0053b310, PathClipped collide_with_point
0x0053acb0, airborne land_on_cylinder 0x0053b3d0, Collide-flag
exact-TOI cap rest) + collides_with_sphere 0x0053a880 +
normal_of_collision 0x0053ab50 + slide_sphere 0x0053b2a0. Pseudocode +
settled BN x87 ambiguities (via ACE cross-ref) + two ACE bugs found and
NOT copied (head-slide foot-disp; see doc §8):
docs/research/2026-07-05-ccylsphere-collision-family-pseudocode.md

Ethereal cylinders now flow through retail's Layer-2 override
(pc:276961) instead of the early-OK consume — same net #150 behavior,
plus retail placement-blocked-by-cylinder semantics. SlideSphere gains
a sphereNum param (retail slides the head sphere by its own
displacement, 0x0053b843).

Register: AP-6 retired; AP-83 added (PerfectClip TOI tail decoded per
ACE, dead code until missiles). Tests: CylSphereFamilyTests (grounded
step-up onto the exact platform shape, tall-cylinder block, airborne
top landing, ethereal guard); the #42 self-shadow control assertion
updated to the retail observable (denied movement — the old ~1m radial
self-push was the approximation's artifact, not retail). Suites: Core
2533 / App 713 / UI 425 / Net 385 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 14:52:28 +02:00
Erik
67c3357246 docs: M1.5 dungeon-track pickup prompt for the next session
Work order: #137 collision (oracle-first, repro before fix) -> #153
apparatus-first (hold shape needs explicit user approval per the
no-holds feedback) -> #138 acceptance run -> A7 lighting closer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 14:02:35 +02:00
Erik
aa734f3409 docs: R5 arc close-out — V5 facade shipped, arc DONE; M1.5 critical path next
- r5-wiring-handoff.md: arc close-out banner (V1-V5 trail, relay/anchor
  inventory, carried follow-ons #167 / R6 TS-42 / TS-43)
- roadmap: R5 ledger entry completed through V5 (incl. the #170/#171/V4
  gate history that had only lived in memory/handoffs)
- milestones: the interleaved parity-track note — R5 arc DONE 2026-07-05
- CLAUDE.md Current state: R5 arc DONE; next work = M1.5 critical path
  (#137 dungeon collision, #138 teleport-OUT, A7 dungeon lighting)
- pickup prompt marked DONE

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 11:12:37 +02:00
Erik
dccd700991 feat(physics): R5-V5 — MovementManager facade owns each entity's interp+moveto pair
Structural capstone of the R5 movement-manager arc; zero behavior change.

Retail MovementManager (acclient.h /* 3463 */, 16 bytes / four pointers)
gives every CPhysicsObj ONE owner for its motion_interpreter +
moveto_manager. acdream carried them as loose per-entity objects wired by
hand at three sites. This slice:

- New src/AcDream.Core/Physics/Motion/MovementManager.cs — owns
  MotionInterpreter + lazy MoveToManager (MakeMoveToManager 0x00524000 via
  a MoveToFactory closure, the acdream stand-in for the physics_obj/
  weenie_obj backpointers) + the relays with retail call shapes:
  PerformMovement 0x005240d0 (types 1-5 -> minterp, 6-9 ->
  MakeMoveToManager + moveto, (type-1)>8 -> 0x47), UseTime 0x005242f0
  (moveto only), HitGround 0x00524300 (minterp FIRST then moveto),
  HandleExitWorld 0x00524350 (minterp only), CancelMoveTo 0x005241b0,
  HandleUpdateTarget 0x00524790, IsMovingTo 0x00524260.
- RemoteMotion.Movement + PlayerMovementController.Movement hold the ONE
  facade; Motion/MoveTo become child views so the comment-dense call sites
  read unchanged. The three wiring sites (EnsureRemoteMotionBindings,
  EnterPlayerModeNow, the chase harness — same commit per the mirror rule)
  construct through MoveToFactory + MakeMoveToManager(), preserving the
  pre-facade eager timing (side-effect-free ctor = unobservable either way).
- Relay call sites repointed: both remote landing HitGround pairs + the
  player landing pair, despawn HandleExitWorld, TickRemoteMoveTo + the
  player Update UseTime, RouteServerMoveTo (takes the facade; routes via
  the retail PerformMovement dispatch), InstallSpeculativeTurnToTarget,
  host HandleUpdateTarget/InterruptCurrentMovement closures (retail
  CPhysicsObj::HandleUpdateTarget @0x00512bf0 fan head + the TS-36
  interrupt chain, now the literal facade relays).
- NOT absorbed per the slice spec: unpack_movement stays App
  (RouteServerMoveTo + UM heads; Core.Net types stay out of Core.Physics);
  TS-42 per-tick order untouched (R6); #170/#171 gate-passed machinery
  untouched. PerformMovement's set_active(1) head not re-asserted (spawn
  asserts Active; status quo — no new register row).
- Register: TS-41/TS-42 source wording freshened to the facade shape;
  AD-36 retire note corrected (facade half closed; residue = entities
  that never get a RemoteMotion). No new rows.
- Conformance: 15 new MovementManagerTests pin the dispatch table, lazy
  create, relay targets/order, null tolerance. Suite 4052 green; the
  183-case/funnel/moveto/chase/sticky suites UNMODIFIED (harness
  construction mirrors production, test bodies untouched).

Decomp: docs/research/2026-07-03-r5-managers/r5-movementmanager-decomp.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 11:12:19 +02:00
Erik
6feaab91e3 docs: R5-V5 facade pickup prompt for the next session
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:32:52 +02:00
Erik
6fd60b4e66 docs: R5-V4 behavioral slice gate PASSED ("looks good")
Handoff status updated. The R5 arc's remaining work is the structural
MovementManager facade only (own slice, fresh session — pickup at
r5-wiring-handoff §V4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:30:29 +02:00
Erik
f423884bd1 feat(physics): R5-V4 behavioral slice — head stance dispatch (all mt) + #164 autonomy bit + mt-0 wire flags
Three unpack_movement parity items (facade deferred per the handoff's
own optional clause — see r5-wiring-handoff §V4 status):

1. HEAD style-on-change (0x00524440 @00524502-0052452c): both GameWindow
   routing heads (remote + player) now dispatch DoMotion(style, ctor
   defaults) when the UM's stance differs from the interp's current
   style, BEFORE the movement-type routing — for EVERY type. Previously
   style applied only through the mt-0 funnel copy, so a chase/turn UM
   (mt 6-9) carrying a stance change started the move in the OLD stance
   (a monster charged in NonCombat posture until the next mt-0). The
   RetailObserverTraceConformanceTests exclusion note updated — the
   trace filter stays (head calls can't appear in a
   MoveToInterpretedState replay) but the production gap it pointed at
   is closed.

2. #164 (closes): the action-replay loop threads each action's autonomy
   into the dispatch params (Autonomous = the 0x1000 splice, raw
   305982) — replayed actions enter the interpreted actions list with
   their real autonomy instead of ctor-default false.

3. mt-0 wire flags consumed (UpdateMotion parsed them since R4-V3):
   0x1 StickToObject → CPhysicsObj::stick_to_object port (0x005127e0:
   resolve target, PartArray radii — 0 when shapeless, guid-as-is for
   acdream's flat entity table — → PositionManager.StickTo; unresolvable
   target → no stick), at BOTH case-0 tails in retail order
   (@00524583-0052458e: funnel apply → stick → longjump flag);
   0x2 StandingLongJump → Motion.StandingLongJump, UNCONDITIONAL write
   (absent flag clears — retail @0052458e). ServerMotionState gains the
   StandingLongJump field.

Conformance: ChaseArm_WithStanceChange_AppliesStanceBeforeTheChase
(harness mirrors the routing-head dispatch) +
Actions_ReplayCarriesAutonomyIntoTheInterpretedList. Suite 4041 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:18:26 +02:00
Erik
530453067a docs: reconcile the Current-state banner + milestones (M1.5 active; D.2b UI + R5 arc = interleaved parity tracks)
Discharges the 2026-06-22 banner-reconcile note: M1.5 stays the single
active milestone (remaining: A7 dungeon lighting, #137 dungeon collision,
#138 teleport-OUT). The D.2b retail-UI track and the R5 movement-manager
arc are recorded in the milestones doc as user-report-driven,
issue-level parity tracks subordinate to M1.5 — not milestones. Banner
rewritten within the 6-line budget; R5-V3/#170/#171 closes and the
R5-V4 pickup pointer included.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:02:04 +02:00
Erik
0b998db713 docs(#171): close the issue — user visual gate PASSED ("Looks good, ship it")
Three slices: 5bd2b8bc (R5-V3 sticky binding + real radii) + 7a823176
(NPC UP-snap suppression while stuck, TS-44) + 69966950 (deep-overlap
back-off sign pin, AP-82). ISSUES entry marked DONE; r5-wiring-handoff
V3 status = gate passed; ACDREAM_PROBE_STICKY documented in CLAUDE.md's
diagnostic env-var list (kept as permanent gated diagnostics — the
PhysicsDiagnostics probe-family pattern, off by default).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 09:55:35 +02:00
Erik
699669502c fix(#171): sticky deep-overlap back-off sign pin — the runaway-to-center (gate-2 residual)
Gate 2: pack behavior good except "sometimes the monster is in the
character / too close vs retail". The ACDREAM_PROBE_STICKY capture
nailed it frame-by-frame: 1661 deep-overlap AdjustOffset ticks, ALL
steering inward (dist -0.65 -> -0.78 -> ... -> -1.9 at +0.13/tick),
monsters converging to centerDist~0 — while the suppressed-snap lines
show ACE's authoritative positions stayed properly OUTSIDE (drift up to
7.7 m). The radii were correct (tgtR=0.68, ownR=0.59-0.98).

Root cause: ACE's literal decode of StickyManager::adjust_offset
(`if (delta >= |dist|) delta = dist;`) leaves delta POSITIVE when the
overlap exceeds one tick's step — steering TOWARD the target center, a
runaway whose equilibrium is centers-coincident. ACE servers virtually
never reach that branch (quantum >=1/30 -> threshold ~1 m); at
render-rate quanta the threshold is ~0.13 m and pack jostle trips it
constantly. The BN mush (0x00555554-0x00555597) is unreadable on
exactly this compare; the retail oracle (side-by-side on the same ACE:
monsters separate) refutes the ACE-literal reading.

Pin: sign-correct clamp — `else if (dist < 0) delta = -delta` (back off
rate-limited). Identical to ACE-literal in every shallow/outside case.
Register row AP-82 (same commit) with the cdb verification note.
Conformance: StickyManagerTests.AdjustOffset_DeepOverlap_BacksOff_
RateLimited. Full suite 4039 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 09:51:24 +02:00
Erik
7a82317614 fix(#171): gate NPC UP hard-snaps while stuck — the sticky/snap tug-of-war (gate-1 residuals)
Gate 1 (2026-07-04): "better in general" + three residuals — monsters
pushed INTO the player, attacks with stale facing, position
"flashing/flapping instead of gliding". All three are ONE mechanism:
the legacy NPC UpdatePosition handler hard-snaps position, orientation,
and velocity/cycle UNCONDITIONALLY, fighting the armed sticky every UP
(ACE's authoritative rest pose sits ~0.6 m out and its server-side
facing lags the strafing target; the client stick holds 0.3 m + live
facing — oscillation at UP cadence).

Retail is immune by architecture, not by tuning: UP corrections flow
through the InterpolationManager into the SAME per-tick
PositionManager::adjust_offset chain where StickyManager::adjust_offset
OVERWRITES them while armed (0x00555190 chain order; 0x00555430 assigns
m_fOrigin). A server correction can never fight an armed stick
frame-by-frame. The remote-player branch already has exactly that
(queue -> combiner -> sticky overwrite); the legacy NPC path snaps
outside the chain.

Fix: suppress the NPC UP position/orientation/velocity-adoption snaps
while the entity is stuck (PositionManager.GetStickyObjectId() != 0) —
the retail chain semantics translated to the snap architecture.
LastServerPos/Time + cell bookkeeping still record; server truth
reasserts on the first UP after unstick, bounded by the 1 s sticky
lease. Register row TS-44 (same commit); retires with the S6/R6
interp-queue unification of the NPC path.

Apparatus: ACDREAM_PROBE_STICKY=1 — per-guid [sticky] lifecycle lines
(STICK / UNSTICK / LEASE-EXPIRE / TARGET-status teardown), per-armed-
tick steer lines (signed gap dist, applied delta, heading delta, live
resolve), and [sticky-snap-skip] at the suppressed-snap site.
PhysicsDiagnostics.ProbeStickyEnabled owns the flag (rule #5).

Full suite 4038 green. Awaiting gate 2 (pack melee vs retail).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 09:34:25 +02:00
Erik
5bd2b8bc8b fix(#171): R5-V3 — bind sticky melee (StickyManager live) + real arrival radii
Group-melee interpenetration + facing drift: the R5-V1-ported
StickyManager/PositionManager were Core-only — the StickTo/Unstick seams
were unbound and every arrival radius was 0, so ACE's Sticky|UseSpheres
melee chases closed ~one body-radius too deep and froze at stale arrival
poses until the next wire re-arm. Retires TS-39.

Wiring (anchors re-verified against the named decomp this session):
- EntityPhysicsHost owns a PositionManager; HandleUpdateTarget fans
  MoveToManager then PositionManager (CPhysicsObj::HandleUpdateTarget
  0x00512bc0 order).
- Seams bound remote + player: MoveToManager.StickTo (BeginNextNode
  sticky arrival @0x00529d3a), Unstick (PerformMovement head), and
  MotionInterpreter.UnstickFromObject (UM funnel head, 0x0050eaea).
- AdjustOffset at the retail UpdatePositionInternal slot (@0x00512d0e):
  NPC branch composes pre-sweep (steer is swept by ResolveWithTransition);
  remote-player branch chains the combiner offset through the shared
  delta frame (the interp stage) so sticky OVERWRITES when armed
  (0x00555430 assigns m_fOrigin, not accumulates); player inside the
  30 Hz physics quantum before UpdatePhysicsInternal.
- UseTime (the 1 s lease watchdog) at the UpdateObjectInternal tail
  (@0x005159b3): unconditional per remote; player gated on the physics
  tick (retail's MinQuantum gate skips UseTime too).
- Real setup cylsphere radii (CPartArray::GetRadius/GetHeight
  0x005180a0/0x005180b0 = setup radius/height x ObjScale from the spawn
  record): own via EnsureRemoteMotionBindings + player wiring; target via
  RouteServerMoveTo AND the speculative use-walk install (retail resolves
  the target PartArray at EVERY MoveToObject site — ACE PhysicsObj.cs:951).
- Teardown parity: exit_world (0x00514e60) UnStick + ClearTarget before
  the ExitWorld notify; player teleport fires teleport_hook's tail
  (UnStick in SetPosition + EntityPhysicsHost.NotifyTeleported =
  ClearTarget + NotifyVoyeurOfEvent(Teleported) @0x00514f1b) so mobs
  stuck to the player drop their sticks on a recall.
- SERVERVEL arbitration also yields to a stuck entity (same starvation
  class as the #170 fix — sticky owns the between-snap translation).
- StickyManager.UseTime aligned to retail's strict > deadline
  (0x00555626; ACE >): two V1 tests had pinned the >= edge — corrected.

Register: TS-39 deleted; TS-41 narrowed (stickyArmed gate); TS-43 added
(remote teleport_hook gap — self-corrects within the 1 s lease); AP-23
narrowed (real radii at the speculative site; only the use-radius
buckets remain invented).

Conformance: 2 new full-stack sticky scenarios in
RemoteChaseEndToEndHarnessTests (arrive -> stick -> strafing-target
gap+facing track -> lease expiry; unstick-on-rearm -> re-stick).
Full suite 4038 green.

Pre-commit adversarial diff review (3 lenses + per-finding refuters)
confirmed and fixed 4 findings: ObjScale-dead radius read, player
UseTime order inversion, missing teleport voyeur notify, speculative-
site radius asymmetry.

Awaiting the user visual gate: pack melee side-by-side vs retail
(attackers reshuffle + keep facing; some overlap is ACE-server-side).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 23:46:17 +02:00
Erik
d413ac2a29 docs(#171): file the group-melee interpenetration issue + R5-V3 handoff/pickup
User report during the #170 gate: pack-melee monsters partly inside each
other with slightly stale facings vs retail on the same ACE. Investigation
(report-only) pinned two code-grounded causes, both already-tracked R5-V3
scope, fix APPROVED by the user:

1. TS-39 — sticky melee is a no-op: ACE arms melee chases with Sticky
   (Monster_Navigation.cs:416); retail's arrival hands off to
   PositionManager::StickTo -> StickyManager::adjust_offset (0x00555430,
   per-tick 0.3m edge gap + facing tracking); acdream's StickTo/Unstick
   seams are unbound so attackers freeze at stale arrival poses.
2. Arrival radii are zero: getOwnRadius () => 0f (the R5-V3 pin) and
   RouteServerMoveTo never sets MovementStruct.Radius/Height — retail/ACE
   arrive edge-to-edge with setup-derived radii (ACE PhysicsObj.MoveToObject
   reads the TARGET's PartArray radius/height).

Ruled out: the between-snap collision sweep (remotes run full
ResolveWithTransition; CollisionExemption keeps creatures collidable).
Caveat recorded: ACE has no server-side monster avoidance — acceptance is
retail PARITY, not zero overlap.

ISSUES #171 filed; handoff docs/research/2026-07-04-171-sticky-melee-handoff.md
+ pickup docs/research/2026-07-04-171-pickup-prompt.md; memory index updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 22:38:58 +02:00
Erik
4cad626ff5 chore(#170): strip the temp probes, close the issue (user visual gate PASSED)
Gate result: user side-by-side vs retail — "looks good, as close to retail
now as I can see". Telemetry from the gate session (launch-170-gate2.log):
BeginMoveForward ~1:1 with MoveToObject arms for every chasing creature
(pre-fix capture was 16:1), ZERO "SERVERVEL skips UseTime while armed"
occurrences, pending_motions depth 1 at last sample.

Stripped per the #170 close-out plan: s_mvtoDiag + all [mvto] prints
(MoveToManager), s_drainDiag + [drain]/[drainq] (MotionInterpreter), the
[npc-tick] branch prints and the "UM actions" inbound-action dump
(GameWindow). The durable ACDREAM_DUMP_MOTION family stays. The
RemoteChaseEndToEndHarnessTests + RemoteChaseDrainBisectTests conformance
suites stay as the permanent regression net for this pipeline.

ISSUES: #170 -> DONE + Recently-closed entry (fix SHAs 427332ac, d2ccc80e,
1051fc83). Memory topic + index flipped to CLOSED.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 22:31:00 +02:00
Erik
1051fc83c6 fix(#170): armed moveto always ticks UseTime — the SERVERVEL branch starved the chase
The "sustain the run" residual. The handoff's "Ready stop-node backlog
drains a beat slower than retail" framing was DISPROVEN: a new full-stack
offline harness (RemoteChaseEndToEndHarnessTests — real MoveToManager +
MotionInterpreter + AnimationSequencer + MotionTableDispatchSink + the
manual omega integration, wired field-for-field like
EnsureRemoteMotionBindings and ticked in TickAnimations' exact phase
order) proves the Core turn/run/drain pipeline healthy: the chase turn
completes in <1 s both directions, BeginMoveForward installs per arm, the
run sustains across re-arms and attack swings, and pending_motions fully
empties (retail cdb invariant add_to_queue == MotionDone).

The real mechanism (launch-drainq.log, corrected per-guid attribution —
the previous session's timeline mis-attributed [mvto] lines that fire in
the network phase): funnel per chasing scamp was 16 mt-6 arms -> 11
dispatched turns -> ONE BeginMoveForward. Any NPC receiving
UpdatePositions gets HasServerVelocity=true (synthesized from position
deltas even when the wire carries no velocity), and the grounded per-tick
branch routed those to the SERVERVEL leg, which SKIPS
MoveToManager.UseTime — [npc-tick] literally logged
"branch=SERVERVEL (skips UseTime) mtState=MoveToObject". The armed
moveto was starved for exactly the duration of the server-side chase:
legs stayed in Ready while the body glided on synthesized velocity (the
#170 slide); the manager only woke in UP-silent gaps (creature stopped
server-side) and its stale-heading turn was interrupted by the next UM
before reaching BeginMoveForward.

Retail runs MovementManager::UseTime UNCONDITIONALLY every tick
(CPhysicsObj::UpdateObjectInternal 0x005156b0, call @0x00515998) and has
no wire-velocity leg-driver anywhere; between UPs a moveto-driven body
translates from the motion state (get_state_velocity) with UP hard-snaps
correcting drift. Fix: an armed moveto (MovementTypeState != Invalid)
always takes the MOVETO leg; SERVERVEL remains only as the legacy
fallback for entities without a moveto (scripted paths / missiles).

Register: TS-41 (the narrowed SERVERVEL stopgap), TS-42 (drain-order
divergence also pinned this session: acdream drains AnimDone->MotionDone
AFTER HandleTargetting/MoveTo.UseTime; retail's process_hooks
@0x00512d3d runs BEFORE TargetManager/MovementManager in
UpdateObjectInternal — one frame of extra latency, R6 scope).

New conformance: RemoteChaseEndToEndHarnessTests (3 scenarios + theory)
+ RemoteChaseDrainBisectTests (the drain-chain pin; its first run also
demonstrated the TS-40 InWorld=false link-strip wedge shape — harness
bodies must replicate the live RemoteMotion construction).

ISSUES #170 updated (awaiting user visual gate; probes stay until then);
handoff doc superseded-note added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 21:59:59 +02:00
Erik
e744192361 docs(#170): session handoff + pickup prompt for the "sustain the run" residual
Detailed handoff (docs/research/2026-07-04-170-creature-run-handoff.md) + paste
prompt (2026-07-04-170-pickup-prompt.md) for continuing #170 in a fresh session.
Records the full root-cause chain (retail cdb + acdream probes), the landed partial
fix (427332ac: per-frame apply_current_movement flood deleted → pending_motions
1.3M→~1, run installs, stuck-attack gone), the one residual (Ready-stop drain lag →
run not fully sustained), the exact next steps (cdb the retail Ready-stop drain), the
apparatus (env-gated probes + cdb scripts), the file:line map, and the DO-NOT-RETRY
list (superseded MovementManager-coexistence hypothesis; keep the velocity fix; don't
patch the retail-faithful motions_pending guard). ISSUES #170 updated to PARTIAL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:31:40 +02:00
Erik
eb423fb749 docs: #170 — record code-grounded root cause (MovementManager coexistence, R5-V4)
Traced the live remote path: the mt-6 chase runs through the verbatim
MoveToManager, but every inbound mt-0 attack UM fires the unpack_movement head
interrupt (bound to MoveTo.CancelMoveTo) AND installs the UM's ForwardCommand
(Ready/idle for an attack-only UM), so the chase MoveTo is cancelled and the run
legs are replaced by idle while the body keeps dead-reckoning -> glide + repeated
idle/attack. The 15-bit ServerActionStamp gate is faithful (uniform-anim is not a
stamp bug). This chase+attack coexistence is R5/MovementManager scope, so #170
sub-bugs 2/3 are downstream of the incomplete MovementManager port (R5-V4), not
#159. Recorded as a hypothesis pending a live ACDREAM_DUMP_MOTION capture + retail
side-by-side (no guessing a fix in this revert-prone path).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 09:17:24 +02:00
Erik
5eb902ffe7 docs: #159 DONE; refine #170 sub-bug 1 (planner unwired, Mite Scamp in correct block)
Mark ISSUES #159 done (2de5a011) and record the key finding for #170: the
CombatAnimationPlanner numbering fix is latent (the planner isn't wired into
the live dispatch) and the Mite Scamp's 0x62-0x64 attacks were always in the
correct block — so #170's visible symptom is sub-bug 2 (the pending_motions
MOTIONDONE loop), not command misclassification. Roadmap L.1b note updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 09:08:58 +02:00
Erik
e4155cd962 docs: session handoff (R5-V1/V2 + #168/#169 fixes) + roadmap; entry for next session
Next-session entry point: 2026-07-03-session-handoff.md. Two ready streams —
#170 (creature chase/attack animation, well-scoped) or R5-V3 (sticky/TS-39).
Roadmap Phase R updated: R5-V2 shipped, streaming fixes landed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 08:51:54 +02:00
Erik
64f83d7cf1 docs: file #170 — remote creature chase+attack renders wrong vs retail
User retail side-by-side during R5-V2 visual gate: chasing+attacking monster
glides, over-frequency, uniform attack anims (retail correct on same ACE →
client-side). Decomposes into #159 (attack cmd numbering) + pending_motions
loop + dead-reckon-vs-locomotion glide. NOT V2 (chase tracking works).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 08:48:55 +02:00
Erik
7c5bc97c74 docs: file #168 (pending-bucket trap) + #169 (cold-spawn streaming hole) as DONE
Both root-caused live + fixed this session (315af02f, 9b06a9b8). NOT R5 —
pre-existing streaming bugs surfaced while visual-gating R5-V2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 23:43:41 +02:00
Erik
fffe90b30a feat(physics): R5-V2 — wire TargetManager voyeur system per-entity, retire AP-79
Replace the AP-79 P4 TargetTracker poll adapter with the ported retail
TargetManager voyeur subscription system, wired per entity. Behaviorally a
faithful no-op refactor for the common cases (server-directed creature chase
+ auto-walk-to-object), now driven by the retail mechanism instead of the
GameWindow poll.

New: EntityPhysicsHost (App) — the per-entity IPhysicsObjHost (retail
CPhysicsObj stand-in), owning a TargetManager. Delegate-injected accessors so
it stays free of GameWindow internals (code-structure rule #1).

Wiring (GameWindow):
- _physicsHosts registry (guid → host) = retail CObjectMaint::GetObjectA,
  backing the voyeur round-trip's cross-entity delivery.
- ResolvePhysicsHost lazily creates a minimal position-only host for ANY known
  entity — retail lets every CPhysicsObj host a target_manager, so a STATIC
  object (chest/corpse) still answers add_voyeur; without this, auto-walk to a
  never-animated object would arm the moveto but never receive the immediate
  target snapshot and never start.
- MoveToManager set_target/clear_target/quantum seams repointed at the host's
  TargetManager (remote + player).
- HandleTargetting ticked unconditionally per entity BEFORE UseTime (retail
  UpdateObjectInternal order): per-remote loop for remotes, pre-Update block for
  the player. The player's tick is load-bearing for creature-chase — the player
  as a watched target pushes its position to the chasing NPCs' HandleUpdateTarget
  each frame, ahead of their UseTime.
- Despawn (RemoveLiveEntityByServerGuid): NotifyVoyeurOfEvent(ExitWorld) to the
  entity's watchers before pruning the host — the only cleanup for a watcher
  whose target already sent an Ok (past Undefined, the 10s staleness never fires).

Deleted: RemoteMotion.TrackedTarget* fields + _playerMoveToTarget* GameWindow
fields + the two manual poll blocks. AP-79 register row retired same commit
(AP section 73→72).

Delivery is now synchronous-on-set_target (retail: set_target is last in
MoveToObject, so the immediate snapshot lands with all moveto state in place)
vs AP-79's next-frame poll — more faithful. Full suite 4006 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 19:59:00 +02:00
Erik
2b5e8a6738 docs: R5-V1 landed — wiring handoff, register TS-35 correction, #167, roadmap
Housekeeping around the R5-V1 core port (3d89446d):
- r5-wiring-handoff.md: the V2/V3/V4 GameWindow-wiring plan (per-entity
  IPhysicsObjHost, AP-79→voyeur behavioral-equivalence anchor, TS-39 sticky
  integration, mt-0 flags, #164, facade). These are the visual-gated slices.
- ISSUES #167: ConstraintManager leash unported — arming (SmartBox) + two
  x87 distance constants BN elided; deferred (needs cdb/Ghidra). TS-35 stays.
- Register TS-35 corrected: R5 recon proved the "write side" is the
  ConstraintManager server-position rubber-band leash, NOT the earlier
  "per-cell contact-plane / doorway-jamming" guess. Oracle addresses fixed.
- Roadmap Phase R: R5-V1 shipped, R2-R4 visual pass PASSED, next = wiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 19:42:04 +02:00
Erik
3d89446d98 feat(physics): R5-V1 — port PositionManager/Sticky/Constraint + TargetManager (Core, unwired)
The retail movement-manager family the R4 MoveToManager port left as
do-not-invent seams (decomp §9f/§9g). Faithful C# ports of retail's
PositionManager facade + StickyManager + ConstraintManager + the
TargetManager voyeur system, with full conformance tests. NO wiring yet
— purely additive, no behavior change. Wiring (retiring TS-39 sticky +
AP-79 target adapter) is R5-V2/V3.

New Core classes (src/AcDream.Core/Physics/Motion/):
- StickyManager (0x00555400): follow-a-target steering. adjust_offset's
  dense x87 mush decoded via ACE (StickyRadius 0.3, StickyTime 1.0,
  follow speed ×5 / fallback 15) — speed-clamped signed-distance steer +
  bounded turn-to-face; 1 s watchdog; Ok→initialized / non-Ok→teardown.
- ConstraintManager (0x00556090): the server-position rubber-band leash.
  90% IsFullyConstrained jump gate + grounded linear brake taper.
  Structural only — acdream never ARMS it (retail arms from
  SmartBox::HandleReceivedPosition, which acdream lacks, with two x87
  constants BN elided). IsFullyConstrained stays false = TS-35 behavior;
  leash-arming + the unknown constants are a deferred issue.
- PositionManager facade (0x00555160): lazy Sticky/Constraint + fan-out.
- TargetManager (0x0051a370) + TargettedVoyeurInfo: the peer-to-peer
  voyeur subscription system (0.5 s throttle, 10 s staleness,
  send-on-drift-past-radius, dead-reckon GetInterpolatedPosition). A
  faithful superset of the AP-79 adapter — SetTarget subscribes ON the
  target; the target's HandleTargetting pushes updates back.
- IPhysicsObjHost: the CPhysicsObj back-pointer seam (position/velocity/
  radius/contact/GetObjectA + target-tracking fan-out) the App wires per
  entity in V2/V3. MotionDeltaFrame: mutable retail-Frame delta accumulator.

Supporting:
- TargetInfo extended to the full retail 10-field struct (additive
  defaults keep the R4 4-arg call sites compiling).
- MoveToMath: signed CylinderDistanceNoZ, NormalizeCheckSmall,
  GlobalToLocalVec.
- Rename: the misnamed AcDream.Core.Physics.PositionManager (a remote
  anim+interp per-frame combiner, NOT the retail facade) → RemoteMotion
  Combiner, freeing the name and removing the ambiguity that breaks every
  file importing both Physics + Physics.Motion (GameWindow will in V2/V3).

Tests: 42 new conformance cases (Sticky/Constraint/Position facade +
TargetManager incl. the full cross-entity voyeur round-trip). Full suite
4006 green (+2 skipped), no regressions.

Decomp + ACE cross-ref + port plan: docs/research/2026-07-03-r5-managers/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 19:34:49 +02:00
Erik
517bbfdae4 docs: R5 entry handoff - fresh session enters here (R2-R4 arc closed, function inventory + verification items + lessons)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 18:12:33 +02:00
Erik
304327b0a4 docs: R2-R4 visual pass PASSED (user-verified) - file #165 wall-swallow + #166 slope-landing sled; next entry R5
All four visual-pass items confirmed by the user's eyes (jump/ledge
momentum, run-in-circles blend, stop settle, retail-observer view of
+Acdream). Two observations filed from the pass:

- #165: retail movers observed from acdream penetrate walls a bit
  ("swallowed") instead of stopping flush - remote-DR collision class,
  PROBE_RESOLVE capture is the first investigative step.
- #166: downhill-jump landing glide+bounce (retail sled) absent - the
  register predicted this exact gap (AD-25 risk column) - composite of
  AD-25 + AP-7 + TS-4, retire together post-R6 per the user's
  "polish later" call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 17:59:45 +02:00
Erik
b152321b43 docs: move #163 to Recently closed (5ebe2be3)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 17:39:36 +02:00
Erik
5ebe2be38e chore: #163 strip R4-V5 temp diagnostics; docs: close #162 (no adaptation)
#163: remove [autowalk-gate] (+ _lastAutowalkGateLogTime throttle clock,
PlayerMovementController), [autowalk-feed] (GameWindow player tracker
feed), and the short-lived [MOVETO-CANCEL] #162 capture probe. The
durable ProbeAutoWalk family (PhysicsDiagnostics owner + DebugPanel
toggle + the permanent [autowalk] probe sites) stays.

#162 closed WITHOUT adaptation: user A/B says retail does not glide, and
post-#161 acdream matches (user-verified walk/run-by-distance movetos +
clean mid-chain key takeover). Head-interrupt + CancelMoveTo re-audited
retail-verbatim against the raw (unpack_movement 0x00524440,
interrupt_current_movement 0x005101f0, MoveToManager::CancelMoveTo
0x00529930 armed-gate); the launch-162 capture shows ACE's mt-0
reflections carry the mover's real locomotion, so cancels never strand
the legs. The old glide was killed by the R4-V5 stack + #161's
apply-pass params fix. Evidence trail in ISSUES Recently-closed #162.

Suite 3,964 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 17:38:41 +02:00
Erik
584ad0a8f6 docs: close #161 (user-verified) - landing-pose fix b1cf0102; next up #162 adjudication + #163 diag strip
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 17:11:16 +02:00
Erik
b1cf01029a fix(R4): #161 remote landing stuck in falling pose - apply-pass params decode
Retail's apply_interpreted_movement (0x00528600) does NOT dispatch with
ctor-default MovementParameters: the BN decomp smears the bitfield store
into the mush expression at raw 305778 - (word & 0x37ff) | cancelMoveTo<<15
| disableJump<<17 - which CLEARS SetHoldKey/ModifyInterpretedState/
CancelMoveTo. ACE MotionInterp.cs:444-449 confirms independently. Three
legs fixed, all retail decode, no adaptations added:

1. ApplyInterpretedMovement now builds the pass params retail actually
   uses (ModifyInterpretedState=false is load-bearing): no dispatch in the
   pass writes InterpretedState, so the airborne Falling substitution
   PRESERVES the wire's forward command and HitGround's re-apply
   dispatches it - the motion table plays the Falling->X landing link.
   The W6 entry-cache (built on the wrong "retail self-heals via hoisted
   registers" theory) is deleted; live reads are retail semantics. Raw
   arg3 decoded as DisableJumpDuringLink -> every (N,0) caller means
   allowJump=true; all 9 caller polarities fixed. copy_movement_from's
   current_style copy (raw 0051e757) added to the UM flat-copy.

2. Both GameWindow landing blocks cleared the Gravity state bit BEFORE
   Motion.HitGround(), whose verbatim state&0x400 gate then no-opped the
   whole retail re-apply; the UP-driven landing block never called
   HitGround at all. Both now run HitGround (minterp then moveto,
   MovementManager::HitGround 0x00524300 order) with Gravity still set,
   then do the DR bookkeeping clear (register row AP-81 added for the
   remaining flag dance, retire in R6).

3. K-fix17's forced SetCycle (both copies) deleted: it executed every
   landing but read the leg-1-clobbered ForwardCommand (0x40000015) and
   re-set the very Falling cycle it meant to clear.

Tests: new HitGround_AfterFall_RedispatchesPreservedForward_ExitsFalling
lifecycle pin; AirborneBody state assertion flipped (it had pinned the
bug value). Suite 3,964 green incl. the 183-case retail-observer trace.
Filed #164 (action-replay Autonomous bit, no current consumer).

Awaiting live verify: stand-still landing must exit the falling pose with
zero wire input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 17:00:03 +02:00
Erik
790938392f docs: R4 verify-session handoff - 5 live fixes shipped, next session enters at #161 (landing pose) with the queue model + evidence trails 2026-07-03 16:28:22 +02:00
Erik
3c866f95f7 docs: #161 landing-pose evidence trail from the live retest (seq still Falling at first post-landing UM; suspect the funnel's Falling dispatch writing interpreted fwd + HitGround re-apply) 2026-07-03 16:25:07 +02:00
Erik
6870857cc2 docs: close #160 (user-verified) - RemoteWeenie run-rate chain fix 41006e79 2026-07-03 16:20:12 +02:00
Erik
ab35a78c1d docs: file #160-#163 (remote run-pace, landing pose retest, glide adjudication, temp-diag strip) + handoff postscript 2 2026-07-03 15:14:57 +02:00
Erik
350fb5e3a5 fix(R4-V5): remote transition links stripped by the detached-object guard - door swings snapped (adds register TS-40)
Root cause (pinned by the worktree investigation + verified against
source): CMotionInterp's dispatch tails strip link animations for
DETACHED objects only (retail `if (physics_obj->cell == 0)
RemoveLinkAnimations`, raw @305627, three ported sites:
DoInterpretedMotion / StopInterpretedMotion / StopCompletely). The R3
port proxied retail's cell pointer with CellPosition.ObjCellId == 0 -
but that #145 field is seeded ONLY by SnapToCell, whose single
production caller is the LOCAL PLAYER's SetPosition. Every REMOTE body
therefore read "detached" forever, and every dispatch stripped the
transition link the motion table had just appended: a used door's
swing link died the same tick (pose snapped closed<->open - the live
symptom), and remote walk<->run link poses have been silently eaten
since the guard landed (masked: locomotion cycles resemble each other;
a door's two poses don't). The dispatcher itself (GetObjectSequence
link path) was verified retail-verbatim end-to-end - not the bug.

Fix: PhysicsBody.InWorld - an explicit "placed in the world" flag
carrying exactly the guard's retail meaning (register row TS-40,
replacing the previously UNREGISTERED ObjCellId proxy). Set by
SnapToCell (retail: enter_world/set_cell assigns the cell) and by
RemoteMotion construction (a RemoteMotion exists only for a world
entity). All three guard sites now test !InWorld. Default false =
retail's pre-enter_world detached state, preserving the guard for
genuinely unplaced bodies (bare-harness interps unchanged).

Tests: InWorldLinkGuardTests pins the polarity at all guard classes
(in-world dispatch keeps links; detached strips; StopCompletely same).
Full suite green: 3,961.

If the door still snaps after this, the open question is DATA (does
the door's table author an On<->Off link at all) - next step per the
investigation: dump the real table's Links/StyleDefaults via a
DoorSetupGfxObjInspectionTests extension.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:10:16 +02:00