Commit graph

824 commits

Author SHA1 Message Date
Erik
5bd2b8bc8b fix(#171): R5-V3 — bind sticky melee (StickyManager live) + real arrival radii
Group-melee interpenetration + facing drift: the R5-V1-ported
StickyManager/PositionManager were Core-only — the StickTo/Unstick seams
were unbound and every arrival radius was 0, so ACE's Sticky|UseSpheres
melee chases closed ~one body-radius too deep and froze at stale arrival
poses until the next wire re-arm. Retires TS-39.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Suite 3,964 green.

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:10:16 +02:00
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
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
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
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
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
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
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
778744bf3e feat(R1-P2): verbatim CSequence container + list surgery
CSequence (Core/Physics/Motion): anim-node list with retail's exact
cursor semantics —
- append_animation (0x00525510): first_cyclic slides to the JUST-
  APPENDED node on EVERY call (the cyclic tail is always the last
  appended node); curr_anim seeds to head + get_starting_frame only
  when null; unresolvable anims discarded (G10);
- remove_cyclic_anims (0x00524e40): removed curr_anim snaps BACK to
  prev at get_ending_frame (or 0.0); first_cyclic = new tail;
- remove_link_animations/remove_all_link_animations (0x00524be0/
  0x00524ca0): removed curr_anim snaps FORWARD to first_cyclic at
  get_starting_frame (G11);
- apricot (0x00524b40, PDB-verified retail name): consumed-head trim
  bounded by curr_anim AND first_cyclic;
- clear (0x005255b0) resets placement fields too — raw body is
  authority over the gap map's G20 note;
- sequence-level velocity/omega with set/combine/subtract (G12);
- multiply_cyclic_animation_fr touches framerates ONLY (G13 —
  velocity rescale belongs to R2's change_cycle_speed composite);
- placement frame family + floored accessors (G14).

Register: AD-33 (double vs x87 long double frame_number, G15),
AD-34 (managed LinkedList vs intrusive DLList).

17 list-surgery state-table tests; 39 total R1 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 19:50:19 +02:00
Erik
1371c2a14c feat(R1-P0/P1): CSequence research base + verbatim AnimSequenceNode
P0 — research + pins: full CSequence-family verbatim extraction (1756
lines, per-function raw pseudo-C + cleaned flow, decomp line anchors),
ACE cross-reference (9 ranked divergences; headline: retail frame_number
is x87 long double — ACE's float is the worst case, our double the best
available; ACE's frame-boundary epsilon is an ACE fabrication, NOT
retail), current-sequencer map, and the R1 gap map (20 gaps, 13 keeps,
P0-P6 port order). Pinned the one decomp ambiguity (leftover-time carry
after advance_to_next_animation — ACE reading adopted; cdb confirmation
protocol recorded, non-blocking).

P1 — AnimSequenceNode verbatim (gap G1/G2/G16/G18):
- direction-aware BARE-INT boundary pair (get_starting_frame 0x00525c80 /
  get_ending_frame 0x00525cb0): reverse starts at high+1, ends at low —
  NO epsilon;
- multiply_framerate (0x00525be0) swaps low/high on negative factor;
- set_animation_id (0x00525d60) retail clamp order (high<0 -> num-1;
  low>=num -> num-1; high>=num -> num-1; low>high -> high=low);
- ctors with retail defaults (30f/-1/-1; AnimData copy + clamp);
- get_pos_frame null out-of-range (retail; ACE returns identity),
  floor double overload; get_part_frame same discipline;
- NO per-node IsLooping/Velocity/Omega — loop membership is list
  structure, physics accumulators live on the sequence (G16).

22 conformance tests (clamp table, boundary mirror table, swap
round-trip, bounds/floor semantics).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 19:45:56 +02:00
Erik
cae56afc82 docs(R): Phase R plan of record — retail motion+animation ground-up reconstruction
User mandate 2026-07-02: complete new movement + animation system,
verbatim retail equivalent, all entity classes, inbound + outbound, no
frozen code, no bandaids. Executed as a staged verbatim reconstruction
(R1 CSequence -> R8 cutover audit), each stage harness-gated with the
proven cdb-golden technique; legacy paths DELETED at each cutover.
Supersedes L.2g S3-S6; absorbs shipped S1/S2/S5 as components.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 19:21:28 +02:00
Erik
67506ce988 feat(L.2g-S2b): wire remote entities onto the CMotionInterp funnel (DEV-1 integration)
OnLiveMotionUpdated's remote branch (368 lines of bulk-copy + cycle
picker + per-axis DoInterpretedMotion + command-list router) collapses
to: build InboundInterpretedState from the wire (retail UnPack defaults;
MoveTo packets feed the PlanMoveToStart seed as the forward command) ->
MotionInterpreter.MoveToInterpretedState(ims, RemoteMotionSink) ->
sink.Commit().

RemoteMotionSink (new, App): receives the funnel's gate-passed
dispatches in retail order; the axis-priority pick (fwd > side > turn),
Run->Walk->Ready missing-cycle fallback, overlay routing, ObservedOmega
seeding, and diag lines are MOVED VERBATIM from the pre-S2 block.
Register row AP-73 documents the single-cycle composition approximation
(retail blends modifiers via re_modify — DEV-9, retires with S3/S6).

Retail-verbatim behavior changes:
- Absent stance now defaults to NonCombat 0x8000003D (retail UnPack
  default, S0-trace-verified) instead of keep-current.
- Remote command lists flow through the funnel's 15-bit action-stamp
  gate — retail's actual mechanism for ACE's re-bundled stale entries;
  the old skip-SubState router workaround is now local-player-only.
- Airborne cycle preservation is the funnel's contact_allows_move gate
  (K-fix17's guard semantics, now from the retail mechanism).
- Stops ride the same path: empty UM -> flat copy -> Ready dispatch ->
  get_state_velocity 0 (DEV-3 core; the 300ms stop-detection fallback
  stays until S6 verifies NPC unification).

Full suite green (3290). Live smoke next.

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