Commit graph

2188 commits

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

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

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

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

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

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

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

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

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

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

Suite 3,964 green.

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

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

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

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

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

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 17:00:03 +02:00
Erik
790938392f docs: R4 verify-session handoff - 5 live fixes shipped, next session enters at #161 (landing pose) with the queue model + evidence trails 2026-07-03 16:28:22 +02:00
Erik
3c866f95f7 docs: #161 landing-pose evidence trail from the live retest (seq still Falling at first post-landing UM; suspect the funnel's Falling dispatch writing interpreted fwd + HitGround re-apply) 2026-07-03 16:25:07 +02:00
Erik
6870857cc2 docs: close #160 (user-verified) - RemoteWeenie run-rate chain fix 41006e79 2026-07-03 16:20:12 +02:00
Erik
41006e795a fix(R4-V5): #160 remote run movetos in slow motion - remote interps had no weenie, so retail's run-rate chain never reached my_run_rate
User observation (acdream observing a retail player's server MoveTo):
walk-distance approaches correct; run-distance plays the run cycle AND
moves toward the target in slow motion.

Root cause, raw-verified (apply_run_to_command 0x00527be0, raw
305062-305076): retail's rate chain is
  weenie ? (InqRunRate() ?: my_run_rate) : 1.0
Every placed retail object HAS a weenie; a REMOTE's weenie fails
InqRunRate (no local skill data) and the chain lands on my_run_rate -
the exact field retail's mt-6/7 unpack writes with the wire's
MoveToRunRate (M13; observed 4.50). Our port had the branch verbatim
but remote interps carried NO weenie at all, taking the else-1.0
branch retail reserves for weenie-less detached objects: observer-side
run dispatches went out at speed 1.0, so the run cycle's pace AND
get_state_velocity's chase speed both crawled. Walk was unaffected -
walk speed is never rate-scaled (the discriminating symptom).

Fix: RemoteWeenie (Core) - the minimal stand-in for retail's
per-object ACCWeenieObject: InqRunRate fails (-> my_run_rate),
InqJumpVelocity fails, IsThePlayer default false (also ends the
fragile "null weenie counts as the player" reading of the A3 dual
dispatch for remotes), IsCreature default true (doors carry it too;
their force-asserted Contact keeps the creature ground-gate moot -
documented on the class). RemoteMotion's interp now constructs with
it.

Tests: RemoteWeenieRunRateTests pins both branches against the raw -
RemoteWeenie + MyRunRate=4.5 promotes WalkForward->RunForward @ 4.5;
weenie-less keeps the verbatim degenerate 1.0. Full suite green: 3,963.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:18:40 +02:00
Erik
ab35a78c1d docs: file #160-#163 (remote run-pace, landing pose retest, glide adjudication, temp-diag strip) + handoff postscript 2 2026-07-03 15:14:57 +02:00
Erik
350fb5e3a5 fix(R4-V5): remote transition links stripped by the detached-object guard - door swings snapped (adds register TS-40)
Root cause (pinned by the worktree investigation + verified against
source): CMotionInterp's dispatch tails strip link animations for
DETACHED objects only (retail `if (physics_obj->cell == 0)
RemoveLinkAnimations`, raw @305627, three ported sites:
DoInterpretedMotion / StopInterpretedMotion / StopCompletely). The R3
port proxied retail's cell pointer with CellPosition.ObjCellId == 0 -
but that #145 field is seeded ONLY by SnapToCell, whose single
production caller is the LOCAL PLAYER's SetPosition. Every REMOTE body
therefore read "detached" forever, and every dispatch stripped the
transition link the motion table had just appended: a used door's
swing link died the same tick (pose snapped closed<->open - the live
symptom), and remote walk<->run link poses have been silently eaten
since the guard landed (masked: locomotion cycles resemble each other;
a door's two poses don't). The dispatcher itself (GetObjectSequence
link path) was verified retail-verbatim end-to-end - not the bug.

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

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

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:10:16 +02:00
Erik
006cf6597a fix(R4-V5): remote-player movetos never ticked (the glide) + door UMs dropped since the funnel cutover
Two more live findings from the user's verify pass (the walk-up itself
works since 24569fd2):

GLIDE (a retail player using an object slides to it with no legs):
remote PLAYERS' MoveToManagers were armed by mt-6 but never TICKED -
the V4 UseTime slot lived only in the NPC/legacy per-tick branch,
gated !IsPlayerGuid (inherited from the deleted RemoteMoveToDriver's
NPC-only scope). Retail's UpdateObjectInternal has no entity-class
fork. The P4 tracker feed + UseTime is extracted into a shared
TickRemoteMoveTo(rm) and now also runs in the grounded player-remote
pipeline (L.3 M2): the manager's dispatches produce the locomotion
cycle through the funnel sink (the legs) while position stays
queue-chased per the M2 spec - the same composition an NPC gets.

DOORS (used doors flip ETHEREAL but never animate; a door opened by a
retail client does not animate either): static animated objects never
receive an UpdatePosition, so they never got a RemoteMotion (created
only in the UP handler) - and since the L.2g S2b funnel cutover the
UM motion apply is rm-only (the old direct SetCycle became the
funnel), so their On/Off forward-commands were parsed and dropped.
Confirmed live: door UMs arriving with cmd=0x000B/0x000C while the
door sequencer stayed frozen. Fix: create the rm on first UM (the
retail one-pipeline-for-every-entity-class shape), with a
LastServerPosTime>0 gate keeping UP-less entities OUT of the
dead-reckoning tick block (it force-asserts ground contact and
ground-resolves the body - position machinery for entities the server
moves; a wall-mounted door must not be terrain-snapped). Their cycles
play via the sequencer's own TickAnimations advance.

Remote jump-landing pose (third user report) not addressed here -
instrumentation next if it survives this build's retest.

Full suite green: 3,958.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:51:59 +02:00
Erik
24569fd2a1 fix(R4-V5): moveto stall #2 - login SetPosition ran before the sink bind, orphaning one immortal pending_motions node
The c2dc1a88 wedge fix paired every StopCompletely A9 node with a
completable motion-table type-5 entry via DefaultSink?.StopCompletely()
- but EnterPlayerModeNow called the initial SetPosition (whose teleport
idle is StopCompletely, R3-W6) BEFORE the sequencer/DefaultSink bind
block, so the login A9 node dispatched against a NULL sink and stayed
an orphan. Head-pop-any semantics mean a queue with one orphan never
reaches empty again (later completions just relabel the backlog), so
MotionsPending stayed true at every UseTime and the MoveToManager's
retail wait-for-anims gate (BeginTurnToHeading) never opened: every
server MoveTo armed (movingTo=True, Initialized=True, tracker fed,
node plan built) and the body never moved.

Pinned live by the [autowalk-gate] probe: type=MoveToObject init=True
contact=True motionsPending=True pm=[0x41000003] nodes=[TurnToHeading,
MoveToPosition] curCmd=0 - identical every half-second, forever.

Fix: the sequencer/sink bind block in EnterPlayerModeNow moves ABOVE
the initial physics Resolve + SetPosition (no data dependency - the
block only needs playerEntity/_animatedEntities). Teleport-arrival
SetPositions were already safe (sink long bound). Test rigs
(PlayerMoveToCutoverTests.MakeRig, W6EdgeDrivenMovementTests) mirror
the fixed order, and a new LoginQueue_DrainsToEmpty_UnderProductionFeed
test pins the invariant the probe caught: after login, pending_motions
must reach EMPTY under the production completion feed.

The [autowalk-gate]/[autowalk-feed] diagnostics stay in for the live
verify pass (TEMPORARY-tagged; strip when #5 closes). Full suite
green: 3,958.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:35:44 +02:00
Erik
c2dc1a889c fix(R4-V5): moveto wedge - StopCompletely's missing animation dispatch orphaned pending_motions; + the P1 autonomous store family
Live bug (2026-07-03, user door test): a server MoveTo armed
(movingTo=True) but the local body never moved; the retail observer saw
ACE walk the server-side player to the door; the next local input
reasserted the stale position (rubber-band). Log evidence: the player's
pending_motions queue was non-empty on 92/94 MotionDone pops (remotes:
0/40) - MoveToManager's retail wait-for-anims gate
(BeginTurnToHeading's MotionsPending check) never opened.

ROOT CAUSE (found via a production-faithful repro test - the original
suite force-drained the queue, masking it): the R3 port MISIDENTIFIED
retail's StopCompletely_Internal body. It is NOT a physics velocity
zero - CPhysicsObj::StopCompletely_Internal (0x0050ead0) tailcalls
CPartArray::StopCompletelyInternal (0x00518890) which is
MotionTableManager::PerformMovement(type 5): an ANIMATION-side full
stop whose UNCONDITIONALLY-queued pending_animations entry is the
completable partner of the A9 pending_motions node
CMotionInterp::StopCompletely enqueues (raw @00527e90 between the
state writes and add_to_queue). Without the dispatch, every
StopCompletely (login/teleport SetPosition + every MoveToManager
PerformMovement head) left an orphan node with no completion - the
queue never drained at idle. (The R3 code comment claimed a register
row for the stand-in; none was ever added - porting the real mechanism
closes that violation without a row.)

Fix set (all retail-anchored):
- IInterpretedMotionSink gains StopCompletely() (default true = the
  null-sink posture); MotionTableDispatchSink routes it to the R2
  manager's already-ported type-5 op (StopObjectCompletely +
  Ready-sentinel entry) + TurnStopped for the ObservedOmega seam.
- MotionInterpreter.StopCompletely calls DefaultSink?.StopCompletely()
  at the exact retail slot (after the state writes, before
  add_to_queue); the immediate set_velocity(Zero) is kept as the
  documented physics-effect stand-in.
- PlayerMovementController ticks CheckForCompletedMotions after
  UseTime - retail's per-tick CPartArray::HandleMovement slot
  (0x00517d60 = MotionTableManager::UseTime = CheckForCompletedMotions
  tailcall, called every tick from UpdateObjectInternal @005159a4).
  NOTE: a first-cut per-tick apply_current_movement pump was tried and
  REVERTED - retail's apply callers are all event-driven (hold-key,
  HitGround/LeaveGround, ReportExhaustion), not per-tick; the remote
  block's per-tick apply is a pre-existing adaptation outside this
  fix's scope.
- The P1 autonomous-store family, completing the pin's port shape
  (the gate landed in V5; the STORE side was missing):
  (a) the unpack store (00509730: last_move_was_autonomous = wire
  byte) in the GameWindow player branch - local player only for now
  (remote interps have no WeenieObj, which A3 reads as IsThePlayer;
  storing a remote's autonomous byte would mis-route their per-tick
  apply onto the raw branch);
  (b) input edges store =1 at the controller edge block - retail's
  CPhysicsObj::DoMotion @00510030 / StopMotion @005100e0 /
  TakeControlFromServer @006b32f4 input-boundary stores;
  (c) section 2's per-frame velocity write now PRESERVES the flag
  (V5's first cut stamped it per frame, the wrong altitude - retail
  stores it at event boundaries only);
  (d) InstallSpeculativeTurnToTarget stores false (models the wire
  mt-6's unpack store; part of the AP-23 adaptation) so the
  event-driven applies (e.g. HitGround mid-moveto) route interpreted
  and cannot clobber the manager's dispatched motions with idle raw
  state.

Tests: new ServerMoveToPosition_ProductionCompletionFeed_WalksToArrival
drives the controller under the PRODUCTION completion feed
(sequencer.Advance -> MotionDoneTarget) instead of force-draining -
this is the test that reproduced the live wedge (queue stuck at the
two A9 Ready orphans) and now proves the drain. Existing cutover tests
mirror the wire-path autonomous store before PerformMovement. Full
suite green: 3,957.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:15:36 +02:00
Erik
b87726dc2c docs(R4-V6): register sweep + roadmap + plan trail - R4 SHIPPED
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>
2026-07-03 13:29:14 +02:00
Erik
b3decdfac6 feat(R4-V5): LOCAL PLAYER cutover - B.6 auto-walk DELETED; P1 autonomous gate ported; TS-36 bound (closes M1-local, M9, M10, M17; retires TS-36, AD-26; re-anchors AD-27, AP-23)
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>
2026-07-03 13:24:22 +02:00
Erik
e4553a0262 docs: Phase R session handoff — fresh session picks up at V4 smoke verdict → R4-V5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:19:16 +02:00
Erik
7016b26ce7 feat(R4-V4): REMOTE cutover - per-remote MoveToManager; RemoteMoveToDriver + PlanMoveToStart DELETED (closes M1-remote, M4/M5/M6/M8-remote; retires AD-8, AD-9, AP-8, AP-9)
Every remote's server-directed movement now runs the verbatim retail
MoveToManager. EnsureRemoteMotionBindings constructs it per remote with
the full V2 seam set (position/heading/velocity providers, the P4
TargetTracker adapter via setTarget/clearTarget storing the tracked
guid on RemoteMotion) and binds InterruptCurrentMovement ->
CancelMoveTo(0x36) - the retail interrupt chain (TS-36 remote side).

UM routing is retail's unpack_movement dispatch (0x00524440): the
head-interrupt + unstick fire for EVERY movement type, then mt 6/7
build MovementParameters.FromWire(raw bitfield - now surfaced by the
parser) + a MovementStruct (mt 6 resolves the target guid against the
entity table, degrading to MoveToPosition at the wire origin per the
plan's 2f; my_run_rate written from MoveToRunRate per @300603) ->
MoveTo.PerformMovement; mt 8/9 likewise via FromWireTurnTo; ONLY mt 0
flows through the interpreted funnel (the PlanMoveToStart seed is
DELETED - retail never routes MoveTo types through the interpreted
state copy).

Per tick: the P4 tracker feeds HandleUpdateTarget(Ok/ExitWorld) from
the live entity table, then UseTime runs the manager's steering/
arrival/fail handlers, then apply_current_movement recomputes velocity
- the same shape ordinary locomotion uses. The legacy driver branches,
the stale-destination timer, and the ClampApproachVelocity band-aid
are gone; arrival now uses retail cylinder distance (watch melee-range
stop distance in the visual pass).

DELETED: RemoteMoveToDriver.cs + its tests (OriginToWorld relocated
verbatim to MoveToMath; the B.6 auto-walk's two surviving constants
inlined into PlayerMovementController, dying with it in V5);
ServerControlledLocomotion.PlanMoveToStart + tests (PlanFromVelocity
survives - new AP-80 row); the RemoteMotion MoveTo capture fields.
Registers: AD-8/AD-9/AP-8/AP-9 retired; AP-79 (TargetTracker adapter,
retire R5) + AP-80 added.

Full suite green: 3,948 passed. Live smoke launched - NPC
chase/wander behavior pending user verification (recorded in the
session handoff).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:18:05 +02:00
Erik
a144e87318 feat(R4-V3): wire completion - mt 8/9 parsing + full params exposure + the mt-0 sticky trailer (closes M7, M13, M14-wire-note)
UpdateMotion now parses TurnToObject (mt 8: guid, standalone wire
heading, 3-dword UnPackNet) and TurnToHeading (mt 9: 3-dword UnPackNet)
into a new TurnToPathData sibling record (the two wire forms genuinely
diverge - 7-dword move UnPackNet with Origin head vs 3-dword turn
UnPackNet with guid+heading head; every consumer switches on
MovementType first, so no polymorphic shape was invented). mt 6/7
exposure widened additively so ALL UnPackNet fields reach
MovementParameters.FromWire. The mt=0 motionFlags sticky-guid trailer
(bit 0x1) is parsed for cursor honesty and carried unconsumed until R5
- scoped to mt=0 ONLY per both ACE's writer (MovementInvalid.Write) and
the decomp's case-0 read, tighter than the plan sketched; the
StandingLongJump bit (0x2) doc-noted as the R5 unpack_movement item.
MoveToRunRate doc-pointered as the V4/V5 MyRunRate write.

11 new golden-byte tests hand-assembled from ACE's writers
(MovementData/TurnToObject/TurnToParameters/TurnToHeading/
MoveToParameters/MovementInvalid) incl. flag-permutation round-trips
and the trailer cursor-honesty case; the existing 12 mt 6/7 fixtures
pass unchanged. Full suite: 3,972 passed.

Implemented by a dedicated agent against the V0-pinned spec (P6 order
confirmed exactly); scope + suite independently verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:53:53 +02:00
Erik
addc8e97a8 feat(R4-V2): MoveToManager verbatim - all 33 members + conformance harness (closes M1/M3/M4/M5/M6/M10/M14-core)
The retail server-directed-movement brain (0x00529010-0x0052a987),
Core-only with every App dependency as a ctor/property seam for the
V4/V5 cutovers: node-plan builders for all four movement types
(TurnToObject's desired-heading clobber quirk VERBATIM; TurnToHeading's
immediate BeginNextNode - ACE's one-tick-late gap not copied),
PerformMovement (cancel 0x36 + unstick first), BeginNextNode with the
sticky handoff order (radius/height/tlid read BEFORE CleanUp),
BeginMoveForward (GetCommand walk/run cascade + stored-params
write-back + progress-clock seed), HandleMoveToPosition (chase arrival
dist <= distance_to_object per the adjudicated BN inversion; fail
distance -> 0x3D; progress >= 0.25 units/s over >= 1 s, incremental AND
overall; fail_progress_count write-only - retail has NO give-up
threshold and none was invented), HandleTurnToHeading (20/340 aux
deadband; the Ghidra-confirmed heading_diff mirror), HandleUpdateTarget
0x0052a7d0 (deferred-start: object moves wait for the first Ok
callback; retargets reset the progress clock without requeueing),
UseTime's initialized gate, InitializeLocalVariables per retail (flags
word + context_id zeroed, floats stale, FLT_MAX resets - not ACE's
transpositions).

TDD catch: default(Quaternion) is the ZERO quaternion, not identity -
a fresh manager's heading computations would silently read 90 degrees;
explicit IdentityPosition resets match the decomp's identity-Frame
semantics. Also pinned: retail's explicit double adjust_motion in
_DoMotion/_StopMotion; entry points never drain pending_actions (only
PerformMovement's cancel does) - re-issues must route through
PerformMovement, documented + tested.

101 new conformance tests incl. three end-to-end scripted drives
(chase turn->run->walk-demote->arrive; flee; frozen-heading
TurnToObject through retarget). Full suite: 3,961 passed.

Implemented by a dedicated agent against the V0-pinned spec; scope +
suite independently verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:43:50 +02:00
Erik
e0d2492cbb feat(R4-V1): command-selection family + state widening (closes M2-mechanics, M11, M12, M15)
MovementParameters gains the verbatim selection family:
GetCommand 0x0052aa00 (the walk-vs-run cascade INCLUDING the CanCharge
0x10 fast-path ACE dropped - retail's default can_charge=false + the
fast-path present, the A13+A15 canceling-pair trap avoided; inclusive
threshold edge per the raw), TowardsAndAway, GetDesiredHeading per the
live Ghidra decompile (fwd-towards 0 / fwd-away 180 / back-towards 180
/ back-away 0), FromWire/FromWireTurnTo (UnPackNet semantics, all 18 A4
masks round-tripped).

New MoveToMath: HeadingDiff per the live Ghidra decompile of 0x00528fb0
(the 360-diff NOT-TurnRight mirror + F_EPSILON 0.000199999995f - the
BN "arg unused" artifact corrected), HeadingGreater (the visible
TurnRight idiom), PositionHeading/Get/SetHeading reusing the codebase's
single yaw-heading convention (P5), CylinderDistance (PDB arg order;
planar-minus-radii shape documented as the interpretation - the raw's
x87 body is garbled; seam noted).

MovementType gains Invalid + retail 6/7/8/9; MovementStruct widened
(ObjectId/TopLevelId/Pos/Radius/Height/Params, additive); WeenieError
+= 0x0B/0x36/0x37/0x38/0x3D with retail-meaning doc comments.

148 new conformance tests. Full suite: 3,860 passed.

Implemented by a dedicated agent against the V0-pinned spec; scope +
suite independently verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:13:15 +02:00
Erik
386b1ce550 docs(R4-V0): pins P1-P7 resolved — the autonomous-byte gate IS retail's echo discriminator; heading_diff mirror Ghidra-confirmed
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>
2026-07-03 11:02:13 +02:00
Erik
988304e13b docs(R4-V-1): MoveToManager research base — decomp extraction + ACE cross-ref + port work-list
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>
2026-07-03 10:31:12 +02:00
Erik
30115d96aa fix(R3-W6b): entry-cache the apply pass's axis reads — the 'press W and stop instantly' regression
User live report (the R3 visual pass): pressing W stopped the local
player instantly (retail observers saw run + rubber-band back — our AP
never moved); S/strafe appeared to work. The edge tracer + a
harness-with-echo repro converged on the funnel:

ApplyInterpretedMovement live-read InterpretedState.ForwardCommand
AFTER the style dispatch. The style dispatch SUCCEEDS against the real
MotionTableDispatchSink (GetObjectSequence Branch 1 style==target →
success — verified in the raw @298636), which gates the
ModifyInterpretedState write → InterpretedMotionState::ApplyMotion's
style branch resets forward_command to Ready UNCONDITIONALLY (raw
0051ea6c — our port is verbatim). Retail SELF-HEALS: its compiled
apply pass reads the axis fields into registers BEFORE the style call,
so the fwd dispatch re-applies the pre-reset command and the field
recovers within the pass — proven by our own 183-case live-retail
observer trace (the fwd dispatch carries the wire's RunForward after
the style dispatch on the same UM). The BN pseudo-C's apparent
post-style field reads at 0x528687 are decompiler rendering of hoisted
registers — the same artifact class as the A1 polarity inversion.
Under the live-field read, every apply pass (the ~10Hz ACE UM echo via
ApplyServerRunRate included) dispatched Ready and left the field
permanently Ready; the controller's per-frame get_state_velocity then
zeroed the body. The 183-case suite could not catch it: its
RecordingSink's return value doesn't mirror the real sink's
style-success, so the resetting state-write never ran under test.

Fix: entry-cache fwd/sidestep/turn commands+speeds (and the my_run_rate
read) before the style dispatch — restoring the S2a funnel's original
semantics through the W5 merge.

3 new regression tests bind the REAL sink over a real sequencer
(the masked condition): a full apply pass self-heals forward; 2 seconds
of held-W with echo cadence covers meters not centimeters; Shift
walk↔run toggling survives mid-transition echoes. Full suite: 3,734
passed. Temp diagnostics removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 09:52:23 +02:00
Erik
778a2b5385 docs(R3-W7): register/roadmap/plan/memory sweep — R3 SHIPPED pending the shared visual pass
AP-78 retired (its condition — App seam bindings + K-fix18 deletion —
landed in W4); TS-21/TS-23/AP-30 anchors re-pinned to the post-W6
controller lines; roadmap + plan record the full W0-W7 trail; the
memory index notes the 2026-06-04 sequencer-deep-dive divergence map
CLOSED by Phase R1-R3. R2+R3 share ONE pending user visual pass; R4
(MoveToManager) is next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 09:21:30 +02:00
Erik
fb7beb706b feat(R3-W6): LOCAL PLAYER UNIFICATION — edge-driven retail input; UpdatePlayerAnimation + the synthesis layer DELETED (closes J15)
The local player now drives the SAME pipeline remotes use. The
controller's per-frame RawMotionState rebuild (level-triggered D6.2) is
replaced by retail's CommandInterpreter altitude: key EDGES fire
DoMotion/StopMotion (0x00528d20/0x00528530) and the Shift edge fires
set_hold_run (0x00528b70, caller-0x006b33ca shape) — the interpreter's
own RawState mutates via ApplyMotion/RemoveMotion and every dispatch
routes the funnel + DefaultSink into the sequencer. GameWindow's
UpdatePlayerAnimation (169 lines) is DELETED with both
LocalAnimationCommand/LocalAnimationSpeed MovementResult fields and all
three synthesis sites (K-fix5 pacing, Walk→Run cycle selection, the
auto-walk overrides): run pacing now comes from apply_run_to_command's
my_run_rate promotion — the same source remotes use; airborne-Falling
falls out of contact_allows_move (the funnel's second dispatch).
Auto-walk's #69 turn-phase legs re-expressed as DoMotion(Turn*) edges;
teleport idle = StopCompletely (retail's full stop) + an edge-tracker
reset so held keys walk straight out of a portal.

Map discovery R1 fixed: ChargeJump() now fires at charge START (the
0x0056afac input-boundary site) — StandingLongJump had NEVER armed in
production despite the W3 port.

TWO root-cause fixes the cutover surfaced (each would have been
invisible under the old synthesis layer):
- CurrentHoldKey was a shadow FIELD synced only on the raw dispatch
  branch; set_hold_run writes RawState.CurrentHoldKey, so an edge-driven
  Shift toggle followed by DoMotion read a stale None and lost the run
  promotion. Now an alias of RawState.CurrentHoldKey (retail's
  adjust_motion reads raw_state.current_holdkey directly).
- The controller's grounded velocity + jump-launch writes used
  set_local_velocity's default autonomous:false, silently clearing
  LastMoveWasAutonomous and flipping the A3 dual dispatch to the
  interpreted branch for the local player. Both marked autonomous.

EXPECTED-DIFFS (map §6, for the R3 visual pass): #45's 1.248x sidestep
factor + ACDREAM_ANIM_SPEED_SCALE died with UpdatePlayerAnimation —
local sidestep pacing now matches how remotes have always played;
auto-walk-at-run plays walk-pace legs until R4; ApplyServerRunRate's
apply_current_movement goes live (Branch-2 fast re-speed absorbs the
echo — retail's own mid-run speed-change mechanism).

Wire: outbound values stay input-derived (the L.2b-verified byte
stream untouched; RawState-sourcing needs the map §2b cdb TODO and R7
owns outbound). MotionStateChanged = the old comparison minus the dead
localAnimCmd leg, OR any edge fired.

Full suite green: 3,731 passed. Live smoke: in-world, user-driven
input through the new path (walk/turn/strafe), 12k entities, zero
exceptions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 09:19:56 +02:00
Erik
fc5a2cda28 docs(R3-W6a): local-player cutover map — re-anchored edit list + edge table + risk decisions
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>
2026-07-03 09:06:42 +02:00
Erik
df7b096d6f feat(R3-W5): DoMotion family verbatim + the ONE DoInterpretedMotion + per-op zero-tick flush (closes J3, J4, J9, J14)
The two parallel DoInterpretedMotion implementations MERGE into one
verbatim pair (0x00528360/@305639): contact gate, StandingLongJump
state-only branch, Dead → RemoveLinkAnimations, the jump_error_code
double-check WITH the DisableJumpDuringLink 0x20000 leg (W2's TODO(W5)
resolved — MovementParameters now flows), ModifyInterpretedState
gating, CurCell-null tail, AddToQueue with the real ContextId;
StopInterpretedMotion's post-stop Ready node + raw-mirror RemoveMotion.
Legacy overloads + ApplyMotionToInterpretedState DELETED. The funnel's
public surface unchanged (183-case suite compiles + passes as-is).

DoMotion 0x00528d20 verbatim: cancel_moveto interrupt; SetHoldKey(key,
cancelMoveToBit) BEFORE adjust_motion; params re-default for the
interpreted call; combat-stance gates on the ORIGINAL id (Crouch/Sit/
Sleep → 0x3f/0x40/0x41; & 0x2000000 → 0x42 outside NonCombat); action
depth cap ≥6 → 0x45; raw mirror ORIGINAL id. StopMotion 0x00528530
mirror shape. StopCompletely 0x00527e40 with the A9 snapshot quirk
(motion_allows_jump on the OLD forward command, stashed in the queued
node) + the J9 fix (sidestep/turn SPEEDS untouched — the 1.0 resets
were a divergence). PerformMovement flushes zero-tick completions after
every dispatched op via the new CheckForCompletedMotions seam
(0x0050fe30) — bound App-side per entity (remotes via
EnsureRemoteMotionBindings, player at the sequencer bind site).

PORT DISCOVERY (not in the W0 pins): retail's DoInterpretedMotion
RESULT gates both add_to_queue AND the state write — a void sink let
the style dispatch's apply-only failure clobber ForwardCommand before
the forward axis read it, regressing 74/183 live-trace cases.
IInterpretedMotionSink.ApplyMotion/StopMotion now return bool
(documented on the interface with the raw anchors); one funnel
assertion corrected with raw-line citations (airborne ForwardCommand
ends at Falling under the fully-wired verbatim algorithm).

62 new gate-table tests. Full suite: 3,729 passed.

Implemented by a dedicated agent against the W0-pinned spec; seam
bound + suite independently re-verified by the orchestrator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 08:57:33 +02:00
Erik
e214acdf23 feat(R3-W4): ground transitions + lifecycle verbatim; K-fix18 DELETED (closes J8, J10, J11-shape, J12, J13, J18, J19)
Core (dedicated agent, independently reviewed): HitGround 0x00528ac0 /
LeaveGround 0x00528b00 verbatim (creature+gravity gates, the
RemoveLinkAnimations seam — K-fix18's retail mechanism — velocity via
GetLeaveGroundVelocity with the autonomous flag, jump-state resets,
apply_current_movement re-sync); enter_default_state 0x00528c80 per A8
(fresh states, InitializeMotionTables seam, sentinel APPENDED without
draining pending_motions — pinned, Initted=1, LeaveGround tail);
Initted gates; the A3 IsThePlayer dual dispatch in
apply_current_movement / ReportExhaustion / SetWeenieObject /
SetPhysicsObject (a remote player routes INTERPRETED — the
ACE-divergence pin); set_hold_run 0x00528b70 + SetHoldKey 0x00528bb0
(XOR guard, None-only-from-Run); adjust_motion creature guard wired
(TS-34 retired); PhysicsBody.LastMoveWasAutonomous +
set_local_velocity(autonomous). Port discovery: retail's
apply_raw_movement 0x005287e0 / apply_interpreted_movement 0x00528600
ARE the already-shipped D6.2a/funnel functions — the dual dispatch
composes them instead of duplicating.

App cutover (orchestrator): the skipTransitionLink flag + both K-fix18
call sites DELETED (AP-74 retired). MotionInterpreter.DefaultSink routes
apply_current_movement's interpreted branch through the REAL funnel
dispatch when a sink is bound — so a remote's LeaveGround engages
Falling via the contact-gated funnel, replacing the forced SetCycle
(J19); the per-remote MotionTableDispatchSink is now PERSISTENT
(EnsureRemoteMotionBindings: DefaultSink + RemoveLinkAnimations +
InitializeMotionTables seams, idempotent from both the UM and
VectorUpdate paths; wire velocity re-applied after LeaveGround so it
stays authoritative). Player: seams bound to the player sequencer; the
controller's grounded→airborne EDGE now fires LeaveGround (jump()
clears OnWalkable and the same frame's transition detection fires it —
retail's order; walk-off-a-ledge gets the momentum fallback + link
strip it never had); the manual jump-block LeaveGround deleted;
LastMoveWasAutonomous set at the controller chokepoint (W6 refines).

Trace S8 re-expressed as the retail mechanism (Falling dispatch +
RemoveAllLinkAnimations = same final state the flag produced). 43 new
lifecycle tests. Registers: TS-34 + AP-74 retired; TS-38, AP-77, AP-78
added. Full suite: 3,665 passed. Live smoke: in-world clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:01:31 +02:00
Erik
af4764443f feat(R3-W3): verbatim jump family — charge gates, jump_is_allowed chain, epsilon fixes; StandingLongJump misattribution retired (closes J5, J6, J7-interp-side, J16-epsilons)
JumpChargeIsAllowed 0x00527a50 (CanJump→0x49 before posture; Fallen +
Crouch..Sleeping→0x48), ChargeJump 0x005281c0 — now the ONLY place
standing_longjump arms (grounded Contact+OnWalkable + forward Ready +
no sidestep/turn, raw @305453-305466); the S2a-flagged side effect
inside contact_allows_move is DELETED (J6) with regression pins that
contact checks never touch the flag (the funnel's StandingLongJump
branch is local-player scope; the controller wires ChargeJump in W4/W6
— remotes never charge client-side, no interim regression).

jump_is_allowed 0x005282b0 full chain replaces the 15-line
approximation: IsFullyConstrained→0x47 (new PhysicsBody stub, TS-35)
BEFORE the pending-head peek (A2 ordering, pinned); head peek fires
whenever the queue is non-empty (no ACE Count>1 gate; nonzero
jump_error_code short-circuits the whole chain — test proves
JumpStaminaCost is never consulted); retail entry shape verbatim
(non-creature-weenie and gravity-off skip the ground gate; null
physics obj → 0x24 NOT 8 per A10); charge → MotionAllowsJump(fwd)
double-check → JumpStaminaCost gate (new IWeenieObject member,
always-affordable PlayerWeenie stub — TS-5 extended).

GetJumpVZ/GetLeaveGroundVelocity: epsilon corrected to retail's
0.000199999995f (was 0.001 — regression-pinned at extent 0.0005);
A5 shape (clamp 1.0, weenie-null 10.0f); A6 momentum fallback fires
only when ALL THREE components are sub-epsilon and overwrites all
three with global→local(m_velocityVector).

Jump 0x00528780: InterruptCurrentMovement no-op seam (TS-36, →R4
cancel_moveto), fires unconditionally; standing_longjump cleared ONLY
on failure (success keeps it — pinned). IWeenieObject.IsThePlayer()
(PlayerWeenie true) lands for W4's A3 dispatch.

51 new tests + 1 re-pin; superseded pre-W3 jump tests removed. Full
suite: 3,623 passed. Registers: TS-5 extended, TS-35/36/37 added.

Implemented by a dedicated agent against the W0-pinned spec; arming
gates + entry shape verified against the quoted raw text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:37:07 +02:00
Erik
371679915e feat(R3-W2): pending_motions lifecycle + the MotionDone consumer — the R2 seam lands (closes J1, J17)
MotionInterpreter implements IMotionDoneSink: pending_motions
(LinkedList<MotionNode>, AD-34 wording) + add_to_queue 0x00527b80 +
motions_pending 0x00527fe0 + MotionDone 0x00527ec0 verbatim (action-class
head → UnstickFromObject no-op seam (→R5 StickyManager) + BOTH states'
action-FIFO pops; head popped UNCONDITIONALLY — A7: never match-by-id,
params unread in this build) + HandleExitWorld 0x00527f30 (the raw
decompile's null-physics-obj branch would infinite-loop if translated
literally — adjudicated as retail dead code, documented on the method) +
is_standing_still 0x00527fa0 + MotionAllowsJump 0x005279e0 with the
A1-pinned literal blocklist (28-case boundary table test: Fallen blocks,
Falling passes — ACE's transposition NOT copied).

Queue producers wired into the funnel per the decomp anchors
(re-verified from the raw text): DispatchInterpretedMotion's success
path computes jump_error_code via retail's double-check shape
(motion_allows_jump(motion), then forward_command when non-action;
the DisableJumpDuringLink params leg is TODO(W5) — no MovementParameters
threaded yet) + add_to_queue; apply_interpreted_movement's turn-stop
re-queues the Ready node (@305766-305785 — the plan's producer #2 and #3
are the SAME site; StopInterpretedMotion's internal add_to_queue @305657
is W5 scope). ContextId=0 with TODO(W5): retail's own compiled code
reads an UNINITIALIZED local for it at this call site.

GameWindow: MotionDoneTarget now binds the entity's REAL consumer —
player via PlayerMovementController.Motion (new internal property),
remotes via RemoteMotion.Motion — resolved AT FIRE TIME (a remote's
RemoteMotion can be created after its first anim tick; an eager capture
would drop completions forever). Despawn runs BOTH layers' exit-world
drains (manager then interp) per §4. AD-36 narrowed (consumed for
creature-class; doors/statics recorder-only until R5).

51 new conformance cases incl. the end-to-end chain test:
MotionTableManager.AnimationDone → sink → interp pending head pops in
step. 183-case observer-trace + funnel suites green unchanged (queue
side effects additive). Full suite: 3,582 passed.

Implemented by a dedicated agent (MotionInterpreter side) against the
W0-pinned spec; producer placements + MotionAllowsJump branch algebra
independently reviewed against the raw decomp; GameWindow wiring by the
orchestrator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:24:56 +02:00
Erik
8664959152 feat(R3-W1): retail state completion — action FIFOs, MovementParameters, MotionNode, WeenieError renumber (closes J2, J16-codes)
InterpretedMotionState + the unified RawMotionState (LegacyRawMotionState
folded away) gain retail's action FIFO + ApplyMotion/RemoveMotion, ported
verbatim from the raw named decomp (0x0051e790/0x0051eb60 region, pc
293252-293703) including two genuine retail quirks pinned by tests and
independently re-verified against the raw text before commit:
- RawMotionState::ApplyMotion's RunForward (0x44000007) dead branch — a
  cycle-class apply of literal RunForward writes NOTHING (retail encodes
  run as WalkForward + HoldKey_Run on the raw state; same family as the
  D6 apply_run_to_command early-return).
- InterpretedMotionState::RemoveMotion's exact-match-only TurnRight/
  SideStepRight handling (left variants fall through to the style/
  forward branches).

MovementParameters verbatim (A4 pin): 18 named flags per the absolute
mask table, ctor = 0x1EE0F expansion + distance_to_object 0.6 /
fail_distance FLT_MAX / speed 1 / walk_run_threshhold 15 / hold_key
Invalid — with the two ACE-divergence traps ported RETAIL-side
(can_charge FALSE where ACE defaults true; threshold 15.0 not 1.0).
MotionNode {ContextId, Motion, JumpErrorCode} (acclient.h:53293) — the
pending_motions node W2 consumes.

WeenieError renumbered to retail's numeric semantics (A10 table):
NotGrounded=0x24, GeneralMovementFailure 0x24→0x47, new 0x3f/0x40/0x41
combat-stance rejects + 0x42 chat-emote + 0x45 action-depth; the
airborne jump/action returns corrected 0x48→0x24 per the A10 sweep
(incl. jump_is_allowed's null-physics-obj 0x24-not-8 divergence from
ACE). Codes are local-only — wire untouched.

Outbound packer proof: RawMotionStatePacker unmodified; all 6
golden-byte RawMotionStatePackTests pass unmodified. TS-24 register row
updated (capability landed; outbound wiring still open).

Implemented by a dedicated agent against the W0-pinned spec; quirks,
scope, and suite independently verified. Full suite green: 3,531
(374+425+713+2015+4 pre-existing skips).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:12:33 +02:00
Erik
220927d350 docs(R2-Q6): register/roadmap/plan sweep — R2 shipped pending the stage visual pass
Dead-reference sweep clean (no live IsLocomotionCycleLowByte / HasCycle /
RemoteMotionSink / SCFAST-SCFULL code refs — remaining mentions are
historical doc comments). Register reconciled incrementally through
Q3-Q5 (AP-73 + stale IA-4 deleted; AP-74/75/76 + AD-35/36 added; AD-34
extended). Roadmap Phase R entry records R1+R2 shipped + R3 prep;
memory index notes the 2026-06-04 sequencer-deep-dive divergences now
mostly closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:56:46 +02:00
Erik
d82f07d4e5 feat(R2-Q5): RemoteMotionSink DELETED — funnel dispatches straight into PerformMovement; AP-73 retired
The funnel's retail-ordered dispatches now go directly into the entity's
motion-table stack via Motion/MotionTableDispatchSink (Core):
ApplyMotion → PerformMovement(InterpretedCommand), StopMotion →
PerformMovement(StopInterpretedCommand). No axis collection, no
single-cycle priority pick, no Commit pass, no HasCycle probe, no
Run→Walk→Ready fallback chain — GetObjectSequence 0x00522860 +
is_allowed decide (closes H4, H5-callers, H11-callers, H15, H17-carry).
Run-while-turning now blends for real: the turn is a Branch-4 modifier
combined over the untouched run substate (AP-73 DELETED from the
register).

AnimationSequencer gains the public PerformMovement passthrough (lazy
initialize_state + locomotion velocity synthesis on success — AP-75;
remote body translation via PositionManager.ComputeOffset depends on
CurrentVelocity) and InitializeState(); HasCycle DELETED (the miss
hazard is structurally gone — GetObjectSequence checks the cycle before
any surgery; new conformance test pins sequence+state untouched on a
miss).

GameWindow: sink swap with the TurnApplied/TurnStopped ObservedOmega
callbacks (H17 carried verbatim — register AP-76, retire R6); spawn +
door sites run retail's enter-world order (initialize_state installs
the table default, the wire's initial motion dispatches unguarded — the
L.1c fallback chains deleted); despawn drains the pending queue via
HandleExitWorld (MotionDone success:false per entry, 0x0051bda0).

5 new MotionTableDispatchSink conformance tests (lazy init, Branch-4
turn blend + callback, Case-B stop unwind, Case-A stop re-drive,
miss no-op). Full suite green: 3,462 passed.

Live smoke vs ACE: in-world, NPC emote/Ready UMs dispatching through
the new path, [MOTIONDONE] completions firing across entities — first
live proof of the Q3→Q4→Q5 chain. Zero exceptions.

Registers: AP-73 deleted; AP-76 added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:55:13 +02:00
Erik
c072b73686 docs(R2): record Q3+Q4 shipped + R3-W0 prep in the Phase R plan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:42:02 +02:00
Erik
3b9d9bb6be feat(R2-Q4): adapter cutover — SetCycle/PlayAction rehosted on PerformMovement; Fix B / fast-path / stop-anim fallback / G17 gate DELETED
AnimationSequencer becomes a thin shim over the verbatim R2 stack:
SetCycle dispatches the style change (Branch 1) then the motion (Branch
2/3/4) through MotionTableManager.PerformMovement; PlayAction is the
same dispatch (actions rebuild via Branch 3; modifiers are Branch 4
physics-only combine_motion — the AP-73 turn-blend mechanism now runs
for real). CurrentStyle/CurrentMotion/CurrentSpeedMod are read-only
mirrors of MotionState (post-adjust_motion, signed mod). Lazy
initialize_state on first drive (retail lazy-create analog) keeps
undriven sequencers do-nothing.

DELETED legacy inventions (net -648 lines): the adapter fast-path
(replaced by Branch-2 fast re-speed: change_cycle_speed +
subtract/combine_motion), Fix B + IsLocomotionCycleLowByte (replaced by
remove_redundant_links on the pending queue — the retail mechanism the
2026-05-03 cdb trace pointed at), the stop-anim low-byte fallback
(replaced by get_link's reversed-key double-hop — retail's actual
backward-walk settle plays the windup link REVERSED), GetLink/BuildNode/
EnqueueMotionData + the G17 HasVelocity/HasOmega gate (add_motion sets
unconditionally), MultiplyCyclicFramerate + the G13 composite,
PlayAction's insert-before-tail machinery, SCFAST/SCFULL diagnostics.
KEPT at the adapter (register rows): the boundary adjust_motion remap +
locomotion velocity/omega synthesis (AP-75, retire R3-W6/R6), K-fix18
skip-link as post-dispatch RemoveAllLinkAnimations (AP-74, retire
R3-W4 when LeaveGround fires it).

GameWindow queue-drain wiring (the §4 G6 seam): each drained AnimDone
hook → manager.AnimationDone(true) (retail AnimDoneHook::Execute
0x00526c20 → Hook_AnimDone 0x0050fda0 chain), UseTime once per tick
(0x00517d57/0x00517d67); IMotionDoneSink bound to an
ACDREAM_DUMP_MOTION recorder (AD-36 — R3 rebinds to
MotionInterpreter.MotionDone).

Conformance: the 11-scenario pre-cutover trace suite (a6235a36) replays
green with six EXPECTED-DIFF annotations (double-hop walk-run routing,
reversed settle links, Branch-4 physics-only modifiers, Branch-1
style-change links, post-adjust mirrors) — everything unannotated is
byte-identical. Legacy suites repaired by a dedicated agent (fixtures
gain the retail-mandatory StyleDefaults + class-bit-tagged ids;
reflection state-poking replaced with real dispatch; zero category-D
regressions — the suspected cursor bug was a fixture class-bit gap),
plus two vacuously-passing tests fixed. Register: stale IA-4 deleted
(R1-P1 ported the negative-factor swap).

Full suite green: 3,458 passed (374+425+713+1946).

Closes H6, H7, H8, H9, H10-adapter, H16-wiring (r2-port-plan.md §3 Q4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:41:27 +02:00
Erik
cd0289bea2 docs(R3-W0): ambiguity pins A1-A10 — all textually resolved, adversarially verified
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>
2026-07-02 21:24:46 +02:00
Erik
a6235a36f5 test(R2-Q4a): pre-cutover adapter trace goldens
11 scripted scenarios (spawn idle, idle→walk link, walk→run cyclic-to-
cyclic, fast re-speed, backward-walk remap, stop-settle fallback, emote
action, K-fix18 skip-link, turn modifier, style change, link-drain
sentinel count) snapshotting the CSequence core's list state after every
SetCycle/PlayAction against the LEGACY adapter (fast-path + Fix B +
stop-anim fallback + G17 gate). These are the parity bar for the Q4
PerformMovement cutover — intentional post-cutover changes get
EXPECTED-DIFF annotations, everything else must stay byte-identical.

Notable pre-cutover behavior captured: the S6 link-before-link stacking
(old reversed link kept mid-drain while the settle link appends behind).

Adds the internal AnimationSequencer.Core test seam.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:12:15 +02:00
Erik
aa65990a1d feat(R2-Q3): verbatim MotionTableManager — the pending-animation queue (closes H3, H15-core)
Ports retail's MotionTableManager (0x0051bxxx; r2-motiontable-decomp.md
§11) above the Q2 CMotionTable: add_to_queue 0x0051bfe0 (append +
immediate collapse), remove_redundant_links 0x0051bf20 (TAIL-ANCHORED
single scan, NOT ACE's restructured loop — cycle-tail block mask
0xb0000000 = STYLE|MODIFIER|ACTION, style-tail 0x70000000 =
CYCLE|MODIFIER|ACTION; the decomp's prose gloss of those masks was
imprecise, the literal constants are ported), truncate_animation_list
0x0051bca0 (zero NumAnims IN PLACE from the tail through the matched
node's successor + CSequence.RemoveLinkAnimations), AnimationDone
0x0051bce0 (counter-driven countdown chain: one call can pop MULTIPLE
entries; action-class pops MotionState's action FIFO; drained-list
counter reset), CheckForCompletedMotions 0x0051be00 / UseTime (zero-tick
sweep, success hardcoded true), initialize_state 0x0051c030 (0x41000003
sentinel), HandleEnterWorld/HandleExitWorld drains (MotionDone
success:false; enter also strips link anims), PerformMovement 0x0051c0b0
(error codes 7 / 0x43 / 0; StopCompletely queues the Ready sentinel
UNCONDITIONALLY).

IMotionDoneSink is the R2 seam standing in for CPhysicsObj::MotionDone —
R3 binds MotionInterpreter.MotionDone (r2-port-plan.md §4 contract).

This queue + remove_redundant_links IS the retail mechanism our old
'Fix B' rapid-motion collapse approximated — Q4 deletes Fix B and routes
SetCycle/PlayAction through PerformMovement.

47 conformance tests: countdown-chain tables, truncate blocked/allowed
matrices for both masks, zero-tick vs counter sweep, world drains, the
walk-run-walk-run collapse trace, the 2026-05-03 walk-to-run golden
(both nodes queued, truncate not firing), PerformMovement error matrix.

Register: AD-34 extended (pending_animations managed LinkedList);
AD-35 added (NotHandled sentinel vs retail's dead-code pointer-leak
default case).

Implemented by a dedicated agent against the committed Q3 spec; diff
scope, mask semantics, truncation range, counter reset, and
PerformMovement paths independently verified against the decomp raw
text before commit. Build + full suite green (3,447 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:01:50 +02:00
Erik
8eff397801 docs(R3-W-1): CMotionInterp-completion research base — decomp extraction + ACE cross-ref + port work-list
Workflow-produced R3 research (3 docs, 3,061 lines):
- r3-motioninterp-decomp.md: verbatim pseudo-C + anchors for the full R3
  scope — pending_motions lifecycle (add_to_queue 0x00527b80, MotionDone
  0x00527ec0), DoMotion 0x00528d20 + PerformMovement 0x00528e80, the whole
  jump family, HitGround 0x00528ac0 / LeaveGround 0x00528b00 (stale
  0x00529710 doc-comment corrected), enter_default_state, MovementManager
  relay surface, struct anchors, constants inventory. Negative results
  (IsAnimating / HandleUpdateTarget / CMotionInterp::HandleEnterWorld NOT
  in retail) explicitly recorded.
- r3-ace-motioninterp.md: ACE MotionInterp/MovementManager/MotionNode map
  with flagged ACE-isms (jump_is_allowed L747 NPE typo, Falling-vs-Fallen
  boundary discrepancy).
- r3-port-plan.md: 10 pinned ambiguities (headline: motion_allows_jump
  0x48 polarity INVERTED in the BN annotation — ranges are a BLOCKLIST;
  apply_current_movement dispatch gate IsThePlayer vs IsCreature), 19
  itemized gaps J1-J19, keep-list, 7-commit sequence W0-W7 ending in the
  local-player unification, exact IMotionDoneSink wiring spec vs R2 §4.

Precondition paragraph updated at vaulting: Q2 committed 98f58db9.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:56:16 +02:00
Erik
615cd4dd74 docs(R2): record Q0-Q2 progress in the Phase R plan
Q0 pins dc54a3e4, Q1 MotionState 2345da30, Q2 CMotionTable 98f58db9.
Remaining R2 work: Q3 MotionTableManager, Q4 adapter cutover, Q5
RemoteMotionSink deletion, Q6 register sweep.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:52:48 +02:00
Erik
98f58db913 feat(R2-Q2): verbatim CMotionTable — the GetObjectSequence dispatcher
CMotionTable (Core/Physics/Motion, 692 lines) wrapping the dat
MotionTable, per r2-motiontable-decomp.md + Q0 pins:

- GetObjectSequence (0x00522860): entry guards; modifier-class no-op
  fast path; Branch 1 style-change (exit link + style link +
  default_style double-hop + re_modify); Branch 2 cycle (UNCONDITIONAL
  default-style retry per label_522ae6, is_allowed gate, same-substate
  re-speed fast path = ChangeCycleSpeed + SubtractMotion(old) +
  CombineMotion(new), clear-modifiers bit0, direct-link vs !SameSign
  double-hop, A2 signedSpeed, outgoing-modifier re-registration,
  re_modify, outTicks per A3); Branch 3 action (direct link OR the
  4-layer out-and-back with the base cycle re-added at the OLD
  substate_mod; outTicks WITHOUT ACE's double-count, A4-#1); Branch 4
  modifier (PHYSICS-ONLY CombineMotion + AddModifier stop-then-re-add —
  the AP-73 retirement mechanism).
- get_link (A1 pin: either-negative -> swapped keys — the adapter's
  field-validated port re-homed), is_allowed (Bitfield & 2; A5
  CONFIRMED on DatReaderWriter 2.1.7), re_modify (deep-copy snapshot
  termination bound), StopSequenceMotion, SetDefaultState,
  DoObjectMotion/StopObjectMotion/StopObjectCompletely (A4-#4 return).
- Free functions: AddMotion (UNCONDITIONAL velocity/omega set — the
  G17 core), CombineMotion/SubtractMotion (physics-only),
  ChangeCycleSpeed (verbatim incl. the A4-#2 retail gap), SameSign.

44 conformance tests pinning H1/H4/H5/H7/H8/H10-H14 + all Q0 pins,
incl. the run-while-turning gated-cycle -> Branch-4 physics-only test
(retail's actual turn-blend mechanism) and missing-cycle ->
sequence-untouched (retires the HasCycle fallback rationale).

Implemented by a dedicated agent against the Q2 spec; diff + key
branches reviewed, suite re-verified (3934 green) before commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:48:26 +02:00
Erik
2345da30e9 feat(R2-Q1): verbatim MotionState (gap H2)
Style/Substate/SubstateMod + the two independent MotionList chains with
retail's exact disciplines: modifier PUSH-FRONT stack
(add_modifier_no_check 0x00525ff0; add_modifier 0x00526340 refusing
duplicates AND the current base substate; remove_modifier by node;
clear_modifiers) and action TAIL-APPEND FIFO (add_action 0x005260a0;
remove_action_head 0x00526120 returning the popped motion, 0 when
empty; clear_actions). Deep-copy ctor clones both chains (Q0-pins
A4-#5: re_modify's snapshot is a termination bound, never shared).

11 discipline-table tests. Next: Q2 (CMotionTable + free functions —
the GetObjectSequence dispatcher).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:31:16 +02:00
Erik
dc54a3e41f docs(R2-Q0): motion-table research base + ambiguity pins
- 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>
2026-07-02 20:29:26 +02:00
Erik
a987cad182 feat(R1-P6): root-motion Frame seam + dead-API removal — R1 COMPLETE
- Advance(dt, Frame? rootMotionFrame) overload: retail's actual root-
  motion contract (CSequence::update(quantum, Frame*) 0x00525b80) —
  every crossed frame's pos_frame combines into the caller's Frame plus
  the sequence velocity/omega via apply_physics. This is the seam R6's
  retail per-tick order (CPartArray.Update -> adjust_offset ->
  Frame.combine) consumes.
- DELETED: ConsumeRootMotionDelta + the dead adapter accumulator fields
  (zero external callers; gap-map API-migration table).
- Root-motion test now asserts REAL accumulation through the wired
  Frame path (replaces the P5 inert-stub pin).
- Phase R plan: R1 stage marked SHIPPED with its commit trail.

Full suite green (3346). R1 done: P0 research/pins -> P1 node -> P2
container -> P3 physics -> P4 advance core -> P5 adapter cutover ->
P6 wiring. Next: R2 (GetObjectSequence + MotionTableManager; extraction
workflow already running).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:24:01 +02:00
Erik
9147344a6f feat(R1-P5): AnimationSequencer rehosted on the verbatim CSequence core
The adapter keeps its full public surface (every consumer compiles
unchanged) while the internals move to Motion.CSequence:

DELETED (legacy invented mechanisms, per the R1 gap map + Phase R
mandate): the AnimNode class + adapter queue/_currNode/_firstCyclic/
_framePosition, the ACE-fabricated FrameEpsilon boundary math, the
safety=64 advance cap, ClearCyclicTail surgery, the stale-head
_currNode relocation block, per-node IsLooping/Velocity/Omega, the
adapter-side AdvanceToNextAnimation/ApplyPosFrame/ExecuteHooks, the
per-node !IsLooping AnimationDone gate (now the core's list-structure
head != first_cyclic gate, G5).

REHOSTED: SetCycle rebuild = RemoveCyclicAnims (+ClearAnimations for
K-fix18) + retail add_motion appends (framerate-only AnimData scaling,
G10 loop membership: the LAST appended node is the cyclic tail) + Fix B
via RemoveAllLinkAnimations (the core's snap-forward IS Fix B's
behavior); Advance = core.Update(dt, null) with hooks flowing through
an IAnimHookQueue adapter into the existing ConsumePendingHooks drain;
fast path = core.MultiplyCyclicAnimationFramerate + the adapter-level
velocity rescale (R2's change_cycle_speed composite stand-in, G13).
Kept byte-identical: adjust_motion remap, GetLink + stop-anim fallback,
K-fix18, velocity synthesis, SlerpRetailClient, the #61 render-side
clamp, SCFAST/SCFULL diagnostics.

Tests: 2 updated with decomp citations — the reverse-start boundary now
pins retail's bare-int HighFrame+1 (0x00525c80, no epsilon, G1); the
root-motion accumulation test pins the inert-stub state pending P6's
Frame wiring (G7). All 44 sequencer tests + the 56 core tests + full
suite green (3346).

Implemented by a dedicated agent against the P5 spec; diff + tests
reviewed, suite re-verified before commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:20:58 +02:00
Erik
658b91d8aa feat(R1-P4): verbatim update_internal / update / advance_to_next_animation
The CSequence frame-advance core (gaps G3/G4/G5/G6/G8/G9/G19),
ACE-verified skeleton + retail constants + the P0-pinned leftover carry:

- update_internal (0x005255d0): frame_number += framerate*dt; overshoot
  clamps to the RAW high/low frame with leftover-time computation +
  animDone; the pose/physics/hook triple fires for EVERY crossed integer
  frame (ascending fwd / descending rev, strict > < boundaries, NO
  epsilon, NO safety cap); AnimDone is a LIST-STRUCTURE gate (head !=
  first_cyclic) queuing the global AnimDoneHook; iterative loop carries
  the leftover into the next node (P0 pin — a lag spike fast-forwards
  through multiple queued nodes in one tick).
- advance_to_next_animation (0x005252b0): four pose ops per transition
  (subtract1 outgoing @ current frame + residual physics; step — fwd
  wraps to first_cyclic, REVERSE wraps to the LIST TAIL, asymmetric by
  design; reseed from the incoming direction-aware boundary; combine
  incoming + physics). Pose ops run in BOTH directions — ACE's
  framerate-sign gates are ACE-isms, decomp is unconditional modulo the
  degenerate-framerate guard.
- update (0x00525b80): non-empty -> update_internal + apricot; empty +
  frame -> accumulated-physics free motion (G8).
- execute_hooks (0x00524830): direction filter (Both or match) QUEUING
  into the IAnimHookQueue host seam (stands in for CPhysicsObj.anim_hooks
  + the AnimDoneHook singleton; drain placement moves in R6). Null part
  frame guarded (documented safe divergence vs retail's latent deref).
- FrameOps.Combine/Subtract1: the AFrame pose composition (verified
  math from the pre-R1 port, now against DatReaderWriter Frame).

10 conformance tests: single-tick goldens, exact-integer boundary
sit (the old #61 flash class), cyclic wrap without AnimDone,
link->cycle fast-forward proving hook order (link hooks -> ANIMDONE ->
cycle hooks) AND the carry, reverse descending hooks with the OOB
high+1 start, reverse tail-wrap, zero-framerate physics-only,
empty-list free motion, pos-frame root motion into the caller Frame,
apricot trim via update. Full suite 3346 green.

R1 remaining: P5 (adapter cutover — AnimationSequencer rehosted on the
core, legacy epsilon/stale-head/safety-cap DELETED) + P6 (root-motion
wiring + API narrowing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:05:04 +02:00