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>
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>
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>
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>
Register:
- Verified retired: AD-8/AD-9/AP-8/AP-9 (V4), TS-36/AD-26 (V5) - all gone.
- AD-34 widened: MoveToManager.pending_actions joins the managed-LinkedList
row, with the MoveToNode rename note (retail MoveToManager::MovementNode,
renamed to avoid colliding with R2's MotionNode) - V2 owed this note.
- NEW TS-39: MoveToManager.StickTo/Unstick are unbound no-op seams - a
sticky MoveTo (wire bit 0x80) completes-and-stops instead of sticking;
PositionManager/StickyManager bodies are R5 scope (call shapes only in
the R4 extraction). Retires with R5.
- Re-anchored after the V5 controller deletion shifted lines: AD-25
(:1212->:874), AP-24 (:170->:176), AP-30 (:1503->:1110), TS-21
(:362->:311, stale-comment clause updated - V5 fixed the comment).
- AP-79 (widened in V5's commit) and the PlanFromVelocity row (V4)
verified present.
Roadmap: Phase R entry - R4 SHIPPED 2026-07-03 (V0-V6 trail), R2+R3+R4
share the ONE pending user visual pass; next code stage R5 (MovementManager
facade + Sticky/Constraint/TargetManager).
Plan of record: R4 stage entry filled in with the full V0-V6 commit trail
+ expected-diffs for the visual pass (retail cylinder-distance stop, real
turn cycles during corrections, CanCharge walk/run legs).
Handoff doc: postscript - R4 complete, V4 smoke log analyzed clean and
deleted; memory index updated (animation deep-dive map: MoveToManager gap
CLOSED, the "three approximations" pattern retired).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The local player now runs server MoveTos through the same verbatim
MoveToManager remotes got in V4. One commit, GameWindow + controller,
per the no-fan-out rule for coupled slices.
P1 gate (V0-pins.md P1, ported verbatim): CPhysics::SetObjectMovement
(0x00509690 @0050972e) drops any movement event whose wire autonomous
byte is set when the addressed object IsThePlayer - ACE's self-addressed
MoveToState reflection (MovementData.cs:162 IsAutonomous=1) never
reaches unpack_movement, which is what makes the retail unconditional
unpack-head interrupt safe. Gate placed AFTER the sequence gates
(retail order). The stale "ACE follows every mt=0x06 with an mt=0x00"
comment block dies with the code it excused (its causal story was
pre-#75; refuted in V0-pins P1).
Run-rate re-anchor (P1 contingency NOT needed - no AD row): the echo
tap ApplyServerRunRate is deleted outright. Both retail feeds already
exist: PlayerDescription skills via SetCharacterSkills (K-fix7) into
InqRunRate (preferred by apply_run_to_command/get_state_velocity), and
the mt-6/7 my_run_rate wire write (M13) now performed for the player by
the shared RouteServerMoveTo. The tap's InterpretedState.ForwardSpeed
overwrite was a pre-R3 mechanism that fought the ported machinery.
B.6 auto-walk deleted wholesale (~330 lines): fields, Begin/End/
DriveServerAutoWalk, IsServerAutoWalking, AutoWalkArrived, the
autoWalkConsumedMotion gates, the #69 turn-dir edge synthesizer, and
the relocated AutoWalkArrivalEpsilon/AutoWalkTurnRateFor constants
(AD-26 retired - the invented 5/30-degree bands are gone; arrival is
retail's distance predicate; turn-first is the TurnToHeading node).
TS-36 retired: Motion.InterruptCurrentMovement binds to
MoveTo.CancelMoveTo(ActionCancelled). Movement-key edges (ctor-default
params carry the 0x8000 CancelMoveTo bit), Shift (set_hold_run
interrupt:true), jump(), StopCompletely, and teleport all cancel a
running moveto through the retail chain - verified by controller-level
tests, not assumed.
MoveToComplete seam WIDENED to natural completion (Core): retail's
BeginNextNode empty-queue completion is inline CleanUp+StopCompletely
(raw @00529d47) and notifies nothing - the client-addition seam had to
fire there (both sticky and non-sticky exits) or AD-27's deferred
close-range Use/PickUp re-send never fires. Never fires on CancelMoveTo.
AD-27 re-anchored from the deleted AutoWalkArrived event.
InstallSpeculativeTurnToTarget rewired through the player's manager
(retail 9a/9b client-initiated shape): close-range -> TurnToObject,
far -> MoveToObject; AP-23 radius buckets + the #77 CanCharge
prediction survive as the params source (row re-anchored).
Per-tick: MoveTo.UseTime() at the old DriveServerAutoWalk slot
(provisional until R6, per the plan's placement decision); the P4
player-side TargetTracker twin (fields + pre-Update feed) mirrors the
remote adapter (AP-79 row widened). HitGround dual-call added on the
player landing edge AND the remote landing site - the latter closes a
V4 wiring-contract gap the adversarial review caught (retail 2d order:
minterp then moveto; without it a landing NPC's moveto never re-arms).
Also from the adversarial review: the mt-8 unresolvable-target degrade
now performs retail's params.desired_heading = wire_heading
substitution (decomp 2f case 8; invisible against ACE per P6, required
for the verbatim degrade); the V4 remote MoveToManager binding gets a
real curTime clock (the ctor stub advanced 1/30s per READ, skewing the
progress/fail-distance windows - note: the pending V4 NPC smoke ran on
the skewed clock); TS-33 row extended with the orientation-diff gap
(ApproxPositionEqual vs retail Frame::is_equal full-frame compare - a
stationary heading snap does not reach the wire; masked against ACE,
R7 outbound scope owns the fix).
MoveToMath gains HeadingFromYaw/YawFromHeading (the P5 scalar bridge
for yaw-authoritative bodies - the player's heading snap must write
Yaw, not the quaternion the controller re-derives every frame).
Tests: 3,956 green (+8). New: MoveToManagerCompletionSeamTests (arrival
fires once with None, no refire, cancel never fires, sticky handoff
order) + PlayerMoveToCutoverTests (EnterPlayerModeNow-shape rig: walks
to arrival with zero user input and zero MotionStateChanged frames -
the #75 invariant by construction; TurnToHeading rotates Yaw and snaps
exact; W-edge and jump cancel without firing complete). W6 edge suite
retargeted from the deleted echo tap to a direct apply pass (the
regression lives in ApplyInterpretedMovement, not the wire trigger).
Spec: docs/research/2026-07-03-r4-moveto/r4-port-plan.md section 3 V5.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P1 (BLOCKER -> RESOLVED, no adaptation needed): retail's
CPhysics::SetObjectMovement (0x00509690) drops any movement event whose
wire AUTONOMOUS byte is set when the addressed object IsThePlayer -
BEFORE unpack_movement's unconditional head-interrupt ever runs. ACE's
mt-0 MoveToState echo is IsAutonomous=1 HARDCODED (MovementData.cs:162)
and only exists when the client itself sends a MoveToState (the
2026-05-14 'echo kills auto-walk' trace was the pre-#75 build whose
overlay leaked outbound packets - post-#75 there is no echo at moveto
start). acdream parses isAutonomous (UpdateMotion.cs:129) and consumes
it NOWHERE; MotionSequenceGate cites 0x00509690 in its own doc but
omitted this final branch. V-commits port the missing gate tail and the
verbatim head-cancel lands cleanly; the informal GameWindow
no-cancel-on-non-MoveTo note retires.
P3 (Ghidra-confirmed live, textual verdict independently identical):
heading_diff 0x00528fb0 HAS the TurnLeft mirror - 360-diff whenever the
turn command != TurnRight; the extraction's 'arg unused' was a BN
x87-setcc artifact (the W6b class). P2 Ghidra-confirmed:
get_desired_heading = fwd-towards 0 / fwd-away 180 / back-towards
180 / back-away 0. ghidra-confirmations.md holds both raw decompiles.
P4-P7 pinned per V0-pins.md. RemoteMoveToDriver's stale chase/flee
class-doc claim corrected (retail and ACE AGREE on the predicates).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Workflow-produced R4 research (3 docs, 2,541 lines):
- r4-moveto-decomp.md: all 33 MoveToManager members verbatim
(0x00529010-0x0052a987) + the MovementManager type-6/7/8/9 relay +
MovementParameters::get_command. HandleUpdateTarget CONFIRMED on
MoveToManager (0x0052a7d0 — closes the R3 negative result; object
moves are DEFERRED until the first target-update callback). Walk-vs-
run = the CanCharge rule exactly (can_charge OR can_run && dist-gap >
walk_run_threshhold, riding hold_key_to_apply into DoInterpretedMotion
— MoveToManager never calls set_hold_run). Sticky handoff located
(empty queue + 0x80 → StickTo). fail_progress_count is WRITE-ONLY in
retail (no give-up threshold — do not invent one). 8-class BN
artifact ledger.
- r4-ace-moveto.md: 16 flagged ACE-isms, all retail-verified — incl. a
stale-member read (MoveToPosition checks the PREVIOUS move's
UseFinalHeading), a field transposition (InitializeLocalVars zeroes
DistanceToObject where retail zeroes the flags word), a dropped
BeginNextNode (ACE turns start one tick late), a UseTime gate
inversion, and the canceling CanCharge default+fast-path pair.
Blast radius: acdream has THREE independent approximations of this
one mechanism (DriveServerAutoWalk / RemoteMoveToDriver /
ServerControlledLocomotion) + an outdated chase/flee claim in
RemoteMoveToDriver's class doc.
- r4-port-plan.md: 7 pins (P1 blocker: retail interrupts current
movement on EVERY UM but ACE sends a companion mt-0 echo after each
MoveTo — needs a discriminator pin before V5; P3: heading_diff's
TurnLeft mirror needs one Ghidra decompile of 0x00528fb0; P4:
type-6/8 moves need a minimal TargetTracker adapter until R5),
17 gaps M1-M17, commits V0-V6. Retires AD-8/AD-9/AP-8/AP-9 + TS-36.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five discoveries beyond the plan text: ChargeJump never called by
production code (StandingLongJump has never armed — W6 must wire the
charge-start call); the DefaultSink→sequencer path already exists
end-to-end (W6 = bind one property + delete UpdatePlayerAnimation +
the edge detector); skipTransitionLink already gone (stale plan
bullet); the #45 sidestep factor + ACDREAM_ANIM_SPEED_SCALE have no
equivalent in the shared sink path (decision required); ApplyServerRunRate's
apply_current_movement goes live once DefaultSink binds (fast re-speed
absorbs it — retail's own mid-run speed-change mechanism).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Workflow-produced pin pass over the R3 decomp extraction (3 independent
raw re-readers + adversarial refuters on the two load-bearing pins +
synthesis). No pin was refuted; none blocks on cdb.
Headlines:
- A1: motion_allows_jump 0x005279e0 is a BLOCKLIST (0 = pass, 0x48 =
blocked) — the BN extraction's whitelist annotation was inverted
(corrected in-place in §3a + §10). Retail blocks FALLEN 0x40000008
and PASSES Falling 0x40000015; ACE mis-transcribed the exact-id term
as Falling (one-off slip — ACE's own charge gates use Fallen).
Definitive literal-uint blocklist table recorded.
- A3: the raw-vs-interpreted dual-dispatch gate is IsThePlayer (vtable
slot +0x14, bound via the ACCWeenieObject vftable dump @0x007e3ea0),
NOT ACE's IsCreature — in all four functions. Anti-artifact proof:
HitGround/LeaveGround nearby call the +0x2c IsCreature slot, so BN
distinguishes the slots locally. Copying ACE's gate would send
remotes down apply_raw_movement against an empty raw state.
- A4: MovementParameters absolute-mask table pinned from acclient.h's
own bitfield struct; retail ctor default 0x1EE0F has can_charge
CLEAR (ACE sets it true) and walk_run_threshold 15.0 (ACE 1.0).
- A5/A6: both jump-velocity epsilons are 0.000199999995f (acdream's
0.001 must change); get_leave_ground_velocity's fallback matrix is
GLOBAL→LOCAL (index-pattern match against Frame::globaltolocalvec).
- A10: definitive error-code table from an exhaustive return-site
sweep, incl. a second 0x24 site (DoInterpretedMotion action-class
contact block) absent from the plan row.
- Adjacent: move_to_interpreted_state's apply_current_movement arg2 is
a garbled allowJump = (motion_allows_jump(old fwd) == 0) — polarity
trap for W-commits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- r2-motiontable-decomp.md: 1,603-line verbatim extraction — full
CMotionTable (GetObjectSequence all 4 class branches, get_link,
is_allowed, re_modify, StopSequenceMotion, SetDefaultState, wrappers),
the free functions (add/combine/subtract_motion, change_cycle_speed,
same_sign), all 16 MotionTableManager members (pending_animations,
add_to_queue, remove_redundant_links with the 0xb0000000/0x70000000
block masks, truncate, AnimationDone vs CheckForCompletedMotions,
PerformMovement with the 0x41000003 stop sentinel), MotionState's
full modifier-stack/action-FIFO cast, verbatim struct layouts +
constants table. BN mistypings identified (SurfInfo lookups are
style_defaults/links hashes).
- r2-ace-motiontable.md: ACE cross-ref with the two-tracker headline
(MotionTableManager UPSTREAM of MotionInterp — never merged) + 5
flagged ACE oddities.
- r2-port-plan.md: 17 gaps (H1-H17), keep list, Q0-Q6 commit sequence,
the MotionDone->R3 boundary contract.
- Q0-pins.md: A1/A2 pinned to ACE's reading (three corroborations;
cdb confirmation folds into the next live session), A3 outTicks
decode, A4 ACE-oddity adjudications (the Action-branch double-count
is an ACE bug — do not copy), A5 Bitfield check at Q2.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pseudocode for the S2 port (unpack_movement case 0 / move_to_interpreted_state
/ apply_current_movement / apply_interpreted_movement / DoInterpretedMotion),
anchored on decomp lines + validated against a LIVE cdb trace of a retail
observer (per-UM DIM order confirmed: style -> forward -> sidestep-stop ->
turn-stop; empty UM = wholesale Ready stop).
Also settles the packer question: RawMotionState::Pack (0x0051ed10) is pure
static-default-difference — outbound L.2b port already verbatim; the
empty-vs-explicit walk variance between captures is driver-client state,
handled identically by the wholesale apply.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
S0 wire probe (live capture, retail actor via ACE) refuted the premise
the #39 machinery was built on: walk<->run Shift toggles arrive as
EXPLICIT UMs (0x0005 <-> 0x0007@runRate) because retail's default-
difference packing baselines forward_command against Ready — W-held is
always packed, and ACE re-emits it unconditionally with the holdKey
upgrade (MovementData.cs:104-119). The frequent flags=0 autonomous UMs
are genuine keys-released / heading-only states; retail applies them as
full stops (InterpretedMotionState ctor 0x0051e8d0 defaults Ready +
move_to_interpreted_state 0x005289c0 flat copy).
Retail has NO pace->animation adaptation anywhere in its inbound
pipeline (two decomp dives + ACE cross-check, deviation map DEV-2), and
the refinement layer's 0.2s-grace re-promotion after legitimate Ready
UMs was itself the observed Ready<->Run thrash / rubber-band component.
Deleted: ApplyPlayerLocomotionRefinement, UmGraceSeconds,
PlayerRunPromoteSpeed/PlayerRunDemoteSpeed, RemoteMotion.LastUMTime,
the synth-player refinement call in OnLivePositionUpdated. Player-remote
cycles are now UM-driven only, exactly like retail. NPC PlanFromVelocity
path untouched (S6 unifies).
S0 findings + S1 live validation (0 false UM_STALE drops across 280 UMs)
recorded in docs/research/2026-07-02-inbound-motion-deviation-map.md.
fix#39: closed — root-cause narrative corrected, machinery removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/investigate deliverable for the inbound (remote-entity) animation+position
retail-parity effort. 10 deviations (DEV-1..10) mapped and adversarially
verified against the named retail decomp + ACE port + current code (9
confirmed, 1 refuted-and-corrected).
Headline: the #39-era UP-pace->cycle inference layer's premise ('wire goes
silent on Shift toggle') is refuted at both oracles — retail sends a fresh
MoveToState on HoldRun toggle while moving (0x006b37a8) and ACE rebroadcasts
every MoveToState unconditionally (GameActionMoveToState.cs:36); retail has
NO pace->animation adaptation anywhere (position error is absorbed solely by
the InterpolationManager chase, already ported verbatim in L.3).
Registers sub-lane L.2g in the roadmap: port the CMotionInterp inbound funnel
verbatim for all remote entity classes, slices S0-S6.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fresh-session handoff for the INBOUND (remote-entity) motion arc. Captures the
symptom (walk<->run transition without stopping = interpreter reacts too slowly
-> animation+position desync compounding through turns; plus sliding + stop
position errors), the root-cause hypothesis (acdream lacks retail's CMotionInterp/
CSequence motion-transition state machine: pending_motions/MotionDone), the
pre-paid research (2026-06-04 sequencer deep-dive, 2026-06-26 audit D7-D12), the
decomp anchors, the inbound code map, the /investigate-then-port plan, the
live-capture setup, and the discipline reminders (report-only, surgical
sequencer integration, decomp-verbatim). Prior outbound/local arc shipped clean
through d34721fa.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The outbound MoveToState now sends the RAW forward_speed 1.0 (omitted by
default-difference packing) instead of the pre-computed runRate, matching what
retail's client sends.
Settled the open question with a live echo-test: acdream sends forward_speed=1.0,
ACE broadcasts back RunForward @ runRate (not 1.0), and a retail observer saw
+Acdream run at full pace. So ACE RECOMPUTES the broadcast run speed from the
character's run skill and auto-upgrades WalkForward+HoldKey.Run -> RunForward for
observers. The earlier PlayerMovementController comment citing ACE
MovementData.cs ("ACE relays the speed, so the wire must send run_rate") was
wrong; corrected.
Changes:
- PlayerMovementController section 6: forward wire command is WalkForward @ 1.0
(+ HoldKey.Run when running); LocalAnimationCommand still carries RunForward for
the local cycle. Backward already 1.0. The MotionStateChanged detection now
keys the walk<->run toggle off HoldKey + LocalAnimationCommand (forward_speed is
constant 1.0); the forward_speed comparison is retained but never fires.
- GameWindow: reverted the D6.2b echo-test one-liner; the wire RawMotionState
takes forward_speed from result (now 1.0).
The wire and the D6.2a local velocity are now both raw-1.0. Threading them onto
one shared RawMotionState (removing section-6's duplicate build) is a
behavior-neutral cleanup follow-up.
Full suite green (3255). Docs: roadmap + pseudocode doc updated with the
echo-test finding.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Understand-phase output for D6: verbatim decomp + faithful pseudocode for
adjust_motion (0x00528010), apply_run_to_command (0x00527be0),
get_state_velocity (0x00527d50), apply_raw_movement (0x005287e0), all
cross-checked against the retail decomp (ACE not checked out in this worktree;
its constants match the decomp). Includes the acdream integration map (the
hand-mirrored backward/strafe bypass D6 replaces), the retail-faithful behavior
changes (strafe ~20% faster + 3.0 clamp), and the locked design decisions:
full RawMotionState unification (wire+velocity+turn), forward_speed=1.0 on run
verified via smoke echo, turn ported to interpreted omega (feel unchanged,
AP-9 base rate stays).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UI Studio (preview panels through the production renderer), importer dat-fidelity (Fix A/B/C/4/5:
the importer carries dat font/justification/color; boundary look=importer / state=runtime), and the
Character window Attributes tab (reads as retail). 3062 tests green.
Handoff: docs/research/2026-06-26-mockup-stage-handoff.md.
# Conflicts:
# docs/ISSUES.md
# docs/architecture/retail-divergence-register.md
Covers: what's done (UI Studio, faithful importer Fix A/B/C/4/5, Character window), the
look-vs-runtime boundary, the mockup goal + approach (host the production UiHost live),
must-fix issues (#149 inventory, #150 GameWindow fonts), patterns/lessons, reference map,
and first concrete steps. For the next agent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Transcribed from user's retail screenshots during the character-window
polish session; documents name-white, large-gold-level, XP captions,
Infinity! cost, and the selected-row bar sprite — used as the acceptance
target for Fix A/B/C.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Element map for LayoutDesc 0x2100002E (the tabbed Attributes/Skills/Titles window) +
the importer-mount reality + the StringTable verdict + the follow-up list. Grounds the
CharacterStatController work in 0e644b5 and the remaining refinements.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rendered all 26 dump windows through the studio's --screenshot mode + a
System.Drawing contact-sheet montage. The generic dump LayoutSource renders
every panel with no crash; fidelity tracks static-chrome (in the dump) vs
runtime content (slots/stats/appraise, not in the dump). Static-rich panels
(vitals/toolbar/character/map/radar/effects/chat…) render great; runtime-only
windows (examine) are correctly blank; inventory shows backdrop+frame but its
nested sub-window chrome is sparse (next investigation). screenshots gitignored.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds Task 4b: a second load path for the UI Studio that reads the committed
retail UI layout dump (docs/research/2026-06-25-retail-ui-layout-dump.json)
and renders any of the 26 retail windows as a static sprite hierarchy.
New files:
- src/AcDream.App/Studio/UiDumpModel.cs — POCOs + System.Text.Json parse of
the dump (UiDump, DumpPanel, DumpNode, DumpRect, DumpStateSet, DumpImage,
DumpState + UiDumpModel static helpers: Parse, ListSlugs, PickImageId).
- src/AcDream.App/Studio/DumpLayout.cs — DumpLayout.Load(path, slug, resolve,
out err): parses the dump, finds the panel by slug, builds a UiElement tree.
Internal DumpSpriteElement draws its sprite via DrawSprite (not reusing
UiDatElement — avoids the ElementInfo/StateMedia dat-import dependency for
this static mockup). DumpGroupElement is a transparent container for Group
nodes. Rect basis is ABSOLUTE in the dump (verified: inventory root at
absolute x=500 and its children also start at x≈500 — child offset from
parent is 0–50px, not 500px); DumpLayout subtracts parent rect to produce
parent-relative Left/Top for each child.
- tests/AcDream.App.Tests/Studio/DumpLayoutTests.cs — 5 tests covering:
inventory load with 0x100001D5 check + >= 40 nodes, unknown slug → null+err,
root at origin, children are parent-relative, all 26 slugs smoke-load.
Modified files:
- StudioOptions: adds DumpSlug + DumpFile fields; --dump <slug> and
--dump-file <path> args; ResolveDumpFile() walks up to the solution root
to find the default dump JSON (mirrors ConformanceDats.SolutionRoot()).
--dump suppresses the default vitals layout so the two modes are exclusive.
- StudioWindow.OnLoad: when DumpSlug is set, loads via DumpLayout (no
FixtureProvider, no controllers — static structure only); else falls
through to the existing LayoutSource + FixtureProvider path.
Results: DumpLayoutTests 5/5 passed; full AcDream.App.Tests 609 passed, 2
skipped (same as before); dotnet build green, 0 warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Brings the 134 D.2b UI commits onto the physics/collision development line so
main can fast-forward. Today's #149 (BSP-less static collision) + #150 (open
doors fully passable) + the full collision/streaming/dense-town-FPS arc meet
the paperdoll/inventory work.
# Conflicts:
# docs/ISSUES.md
# docs/architecture/retail-divergence-register.md
Retail oracle greps confirmed:
- CSphere::intersects_sphere @ 0x00537ae4 (pc:321692): the ethereal branch
is `void __thiscall` — all paths return void (no COLLIDED). The function
performs a proximity check only; no blocking result is produced.
- CCylSphere::intersects_sphere @ 0x0053b4a0 (pc:324573): same void-return
pattern — ethereal branch calls collides_with_sphere (check only, no slide),
all returns are void = passable.
Change: added `if (sp.ObstructionEthereal) return TransitionState.OK` at the
top of SphereCollision and CylinderCollision in TransitionTypes.cs, mirroring
the void-return semantics of both retail functions. The existing per-object
clear at pc:276989 (line 2837) still fires after the early OK return.
Before this fix: an ethereal-alone NPC/ghost with a Cylinder or Sphere shadow
shape would BLOCK the player (regression introduced when Task 3 made ETHEREAL-
alone fall through ShouldSkip instead of instant-skipping). After: all three
shape types — BSP (via BSPQuery Path 1), Sphere, and Cylinder — correctly pass
through when obstruction_ethereal is set.
Tests: added 4 tests to ObstructionEtherealTests.cs verifying:
- Ethereal Cylinder → passable (sweep passes through, no CollisionNormalValid)
- Ethereal Sphere → passable (same)
- Non-ethereal Cylinder → still blocks (regression guard)
- Non-ethereal Sphere → still blocks (regression guard)
Full Core suite: 1584 pass, 0 fail, 2 skip (pre-existing dat skips).
Pseudocode doc updated with confirmed cyl/sphere ethereal contracts and the
complete set/clear/consume flow summary.
Retail refs:
- CSphere::intersects_sphere @ 0x00537ae4 / acclient_2013_pseudo_c.txt:321692
- CCylSphere::intersects_sphere @ 0x0053b4a0 / acclient_2013_pseudo_c.txt:324573
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retail CPhysicsObj::FindObjCollisions (0x0050f050) only instant-skips when
BOTH ETHEREAL_PS (0x4) AND IGNORE_COLLISIONS_PS (0x10) are set (pc:276782).
ETHEREAL-alone sets sphere_path.obstruction_ethereal=1 (pc:276806) and
continues to the shape dispatch. BSPTREE::find_collisions (0x0053a496) routes
Path 1 (sphere_intersects_solid) when the flag is set (pc:323742): the open
door has no solid leaf at the doorway, so the test returns OK → player passes
through. CEnvCell::find_env_collisions (0x0052c144) clears the flag first so
ENV walls are never weakened (pc:309580, "D5 clear").
Changes:
- CollisionExemption.ShouldSkip: require BOTH bits for Gate-1 early-out
(previously ETHEREAL alone returned true — the AD-7 shim). Divergence
register row AD-7 deleted.
- SpherePath: add ObstructionEthereal field (mirrors retail
SPHEREPATH.obstruction_ethereal).
- FindObjCollisionsInternal loop: set sp.ObstructionEthereal=(target&0x4)!=0
before shape dispatch; clear it after (per-object clear pc:276989).
Also clear at the null-BSP continue site to keep flag clean.
- FindEnvCollisions: clear sp.ObstructionEthereal=false at top (D5 clear
pc:309580) — ENV cell walls are always solid.
- BSPQuery.FindCollisions Path 1: change `obj.Ethereal` (ObjectInfo.Ethereal,
always false — dead code) to `path.ObstructionEthereal`. Gate now correctly
mirrors retail pc:323742: PLACEMENT_INSERT || obstruction_ethereal.
Consume site change (BSPQuery.cs before/after):
BEFORE: if (path.InsertType == InsertType.Placement || obj.Ethereal)
AFTER: if (path.InsertType == InsertType.Placement || path.ObstructionEthereal)
Mirrors retail pc:323742 exactly. obj.Ethereal was dead code (ObjectInfo.Ethereal
is never set true anywhere); the correct flag is SpherePath.ObstructionEthereal.
Tests: 1580 pass / 0 fail / 2 skip (was 1576/0/2 + 4 new in ObstructionEtherealTests.
CellarUp, CornerFlood, DoorCollision, HouseExitWalk all green — no wall regressions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Setup.Spheres were previously coerced to short cylinders (CylHeight=2*r),
which is geometrically wrong: a cylinder has flat caps; a sphere does not.
This ported CSphere::intersects_sphere (0x00537A80) so sphere-typed shadow
entries are tested as spheres — 3-D distance, no height clamping.
Changes:
- ShadowObjectRegistry.cs: added ShadowCollisionType.Sphere (enum value 2).
The BuildFloodSpheres anyCyl dedup at :232 is unaffected: only Cylinder
sets anyCyl=true; Sphere shapes fall through to the BSP-fallback path
(anyCyl=false → included), which is correct.
- ShadowShapeBuilder.cs: FromSetup now emits ShadowCollisionType.Sphere
(CylHeight=0) for Setup.Spheres instead of a short Cylinder.
- CollisionPrimitives.cs: added SweptSphereHitsSphere — quadratic swept
solve ported from ACE Sphere.cs::FindTimeOfCollision, which is a C# port
of retail's CSphere::intersects_sphere @ 0x00537A80. Sign convention
confirmed against the decomp: retail negates the root to produce a
forward t ∈ (0,1].
- TransitionTypes.cs: added Sphere narrow-phase branch between BSP and
Cylinder in FindObjCollisionsInCell; uses 3-D distance for overlap
(not XY-only). Added SphereCollision() method implementing the 3-D
wall-slide response. Updated diagnostic logging at :2734 to cover Sphere.
- Updated ShadowShapeBuilderTests for new Sphere type assertion.
- New SphereIntersectsSphereConformanceTests: 9 geometrically-anchored
cases (head-on, tangent, perpendicular-miss, lateral-near-miss,
sweep-away, beyond-step, degenerate-zero-sweep, already-overlapping,
vertical-sweep).
Retail oracle: CSphere::intersects_sphere @ 0x00537A80 (named-retail);
ACE Sphere.cs::FindTimeOfCollision (C# port, cross-confirmed).
Build: 0 errors, 10 warnings (pre-existing).
Tests: 1576 pass / 0 fail / 2 skip (1578 total).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
19-agent verified audit of how retail decides which objects collide vs
acdream's per-channel filters. Confirms the user's "1 fix for all collision"
intuition: acdream already has retail's two-layer shape (per-cell shadow
registration + query-time exemption); the divergences are now narrow and
enumerable, not a scattered filter mess.
Audit (docs/research/2026-06-24-collision-inclusion-audit.md): 6 confirmed
deviations (D1 mesh-AABB phantom HIGH, D2 ETHEREAL-alone, D3 no sphere
primitive, D4 entry-restrictions, D5 obstruction_ethereal absent, D8 cell-
transform stale cache) + 2 refuted by the adversarial pass (D6 placement-
insert present in BSPQuery; D7 terrain pass-through is the #135/#138
streaming-gap, retail does it too). Loader verified faithful: CacheGfxObj
reads PhysicsBSP gated on HasPhysics; the mesh-AABB is a pure additive
non-faithful layer.
Design (docs/superpowers/specs/2026-06-24-unified-collision-inclusion-design.md):
"1 fix" = one DAT-only shape authority (delete mesh-AABB), one query
predicate, four faithful channels kept distinct (retail keeps find_env/
find_building/find_obj separate), one per-apply rebase invariant. 5
independently-gated slices. Retires register rows AP-2 + AD-7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Next-session brief: build retail's collision-inclusion model (PhysicsState/
ETHEREAL/HAS_PHYSICS_BSP predicate + per-cell shadow-list registration + the
building-shell/terrain/EnvCell channels), map acdream's per-channel ad-hoc
filters against it, enumerate deviations, and design ONE unified retail-faithful
mechanism to replace them. Motivated by this session's #146/#147 — both were the
same shape (a per-channel filter diverging from retail). Brainstorm-gated,
report-first, no guess-patches. Includes the apparatus inventory + a paste-ready
prompt.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both observed 2026-06-24 after the FPS work; both are the known #135/#138 placed-
but-unstreamed streaming gap (NOT FPS-work regressions — those commits are render-
only). Handoff maps the symptoms to ISSUES, flags the delicate-area lessons (no
guess-patches, the reverted hold, fix-the-foundation, capture-first), and points
at the physics digest + streaming refs + capture apparatus.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Report: OUTCOME section (the two shipped fixes, the glFinish-artifact correction,
the deliberately-unpursued scenery-CPU/terrain-GPU headroom). ISSUES: recently-
closed entry with SHAs + pointers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cells batching shipped (29->75fps, 2.6x); remaining ~12ms is diffuse (particles
~3, punch/seal ~3, ~5.5 unattributed) + frame spikes. Next session: deep-dive
with RenderDoc to attribute + push higher. Distance-degrade theory is dead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>