Session handoff for the next pickup. #176 (purple seam flash) + #177
(stair-room light pop-in) are ONE bug, root-caused to the camera-nearest
MaxGlobalLights=128 snapshot cap evicting in-range lights of visible
cells (Hub = 366 fixtures). Fix deferred to A7 because uncapping exposes
unported per-cell light-reach (through-floor light) + the fixture
falloff-curve misassignment + an unattributed striped-floor artifact.
- New handoff: docs/research/2026-07-06-176-177-handoff-A7-lighting.md
(root cause, why-deferred, the A7 fix order, tooling inventory).
- Roadmap A7 updated: now owns #176/#177; the 'light visibility culling'
hypothesis layer is CONFIRMED (no per-cell insert_light registration);
fixture-curve layer added. Analysis pre-paid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The MaxGlobalLights 128->1024 fix (4d25e04d) was live-tested and made
the eviction pops stop — but with the full 366-fixture pool active,
three unported retail lighting semantics dominate the Facility Hub:
(a) lights reach THROUGH solid floors/walls: retail registers lights
per-CELL (insert_light 0x0054d1b0) so the under-room portals'
purple light never touches the corridor above; our flat
sphere-overlap selection has no reach/occlusion notion — rooms
washed magenta (user screenshot).
(b) stationary weenie fixtures ride the DYNAMIC 1/d falloff (~9x
retail's static 1/d3 bake curve at 3m) — the #143 isDynamic
assignment is wrong for ACE-served world fixtures.
(c) an unexplained striped z-fight-like artifact on lit floor regions
(user screenshot; no coincident dat geometry — the coplanar-pair
sweep came back empty; not a striped texture — all corridor
surfaces are plain Base1Image stone).
Reverted to 128. The cap is now documented as a LOAD-BEARING STOPGAP:
it accidentally approximates per-cell reach by keeping the pool local
to the camera. The #176/#177 root cause (cap eviction popping per-cell
light sets) stays CONFIRMED and fully documented; the real fix is the
A7 dungeon-lighting arc: per-cell light registration + the static
fixture curve + the stripe hunt, THEN uncap. The desired-end-state pin
is kept as Skip with the full pointer. Register row AP-85 rewritten to
match reality; ISSUES #176/#177 back to OPEN with the complete
mechanism story.
Suites: Core 2591+3skip / App 719 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The probe launch discriminated it: the user reproduced the purple floor
flash while [light] (ambient branch) and [pv-input] (portal flood) read
provably healthy — eliminating the last CPU-side theories and exposing
the one channel the probes could not see: per-cell 8-light set
composition.
BuildPointLightSnapshot kept the MaxGlobalLights=128 point lights
nearest THE CAMERA; the Facility Hub registers 366 fixtures, so 238
were evicted per frame by camera distance. SelectForObject (faithfully
camera-independent, and unit-pinned as such) could only choose from the
surviving 128 — an in-range torch of a visible cell that ranked past
the cap dropped out of that cell's 8-set, so per-cell Gouraud lighting
flipped as the chase boom swung the camera:
- #176: the flipping unit is a CELL -> discontinuity lines at exactly
cell-seam granularity; a torch-losing floor drops to dim blue-grey
stone at 0.2 ambient (the perceived purple), camera-angle dependent.
- #177: a stair room whose torches all ranked past the cap rendered at
bare 0.2 ambient (near-black = 'not visible'); approach re-admitted
them ('pops into existence'); the sweeping boundary dropped the
ramp's lights mid-descent ('disappears on the last step'). The
geometry never vanished - its lights did.
Retail's minimize_object_lighting (0x0054d480) has NO global
camera-nearest pool cap (lights register per cell, insert_light
0x0054d1b0). Fix: MaxGlobalLights 128 -> 1024, a non-biting safety
valve (GlobalLightPacker grows to fit; 64 B/light). Register row AP-85.
TDD pin: PointSnapshot_HubScaleLightCount_ObjectSelectionIsCameraInvariant
(RED at 128 with a Hub-scale 401-light layout, GREEN at 1024). The
pre-existing camera-independence pin covered the SELECTOR but not the
SNAPSHOT it selects from - the pop re-entered one stage upstream.
Suites: Core 2588 / App 719 / UI 425 / Net 385 green. Pending user gate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dungeon render pair (purple seam flash + stair pop-in) resisted
desk root-causing, but the investigation narrowed the space to two
live theories and shipped permanent apparatus:
- Issue176177DungeonSeamInspectionTests: dat truth — corridor floors
ARE textured drawn PortalSide portal polys; the reciprocal is NoPos;
the 'stairs' are 0x8A020182's ramp shell (vertical portals, zero
statics); CellBSP partitions exactly at portal planes; DXT1 textures
carry zero transparent-mode texels.
- Issue176177FacilityHubFloodReplayTests: production-matched flood
replays — approach/descent/gaze-sweep/walk all healthy with coherent
inputs; ScenarioE pins the flood's collapse-to-root sensitivity under
incoherent (root, eye) pairs.
- Issue176177SeamTransitLagTests: the resolver flips cells within one
tick-step of the portal plane — the 0.33-0.47m [cell-transit] 'lag'
in the gate logs is speed*tick quantization, not membership error.
Refuted (do NOT retry — ledger in the research doc): placeholder
texture, reciprocal z-fight, seal z-fight (seals only fire for
OtherCellId==0xFFFF), root/eye incoherence (production camera sweep is
mm-exact at planes), flood bistability, #119-class statics, undefined
DXT mips (both paths decode DXT->RGBA8; the compressed-array branch is
dead code), DXT1 alpha, fog mix (ramp ~538m), lightning leak (flash==0
in production), viewer-light pops (smooth (1-d/range) ramp).
Filed #178 (A8 double-sided shell stopgap still live) and #179
(lightning flash lacks an indoor gate — dormant). The purple can only
be the fog clear color (undrawn pixels) or the outdoor ambient+sun
tint; discrimination needs ONE probe launch (ACDREAM_PROBE_LIGHT +
ACDREAM_PROBE_PVINPUT + ACDREAM_PROBE_CELL) — protocol in
docs/research/2026-07-06-176-177-render-pair-investigation.md.
Suites: Core 2587 / App 719 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The corrected 1.835m collision capsule blocks the window alcove like
retail; doorways, seams, and stairs unaffected by the taller head.
Remaining #137 scope: the doors half (block/pass per open state, the
#175 pose fix's pending gate).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
#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>