acdream/memory/project_session_2026_04_18.md
Erik 7c3ba1e093 docs: session memory — evening animation investigation + agent dispatch
Captures the diagnosis chain (wrong-first-guess -> parser offset bug
-> handler rewrite -> ACE-confirmed observer-side inference), the
Agent A animation-overhaul merge (10 commits, dead-reckoning + speed
scaling + Commands list + sequence-wide velocity + soft-snap), the
pending Agent B sky/weather/lighting branch, and the orthogonal
wire-layer adds (SocialActions, InventoryActions, AppraiseInfo profile
blobs, 9 extra GameEvent parsers).

Current tree state: 684 tests passing (192 Core.Net + 492 Core),
build green, animation overhaul live on main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:52:24 +02:00

14 KiB
Raw Permalink Blame History

Session 2026-04-18 — Autonomous roadmap push (Phase E + F + H + G)

Context

User went AFK with direction:

  • "Work on the entire roadmap"
  • "FULL implementations, NO skeleton implementations"
  • "Fall back to decompile retail and holtburger"
  • "Implement full animation for monsters AND NPCs AND collision detection"
  • "Make proper commits so we can rollback later on"
  • "Don't stop! JUST GO"
  • Reminder: when connecting to the live ACE server, must log out of the previous session first; server is always up.

Delta: 30+ commits on main, 470 → 684 tests (+214), continuing

Status at the time of this update: continuing the session. The earlier wire-layer push (E.1-H.2 + AppraiseInfoParser) is stable. The evening was dominated by a remote-player animation bug investigation + deep-dive agent dispatch.

Investigation chain

User reported: "other players don't render properly now. Wrong clothes, just look like they are lagging forward with run animation. And when they stop, their running animation is still on."

Iterated on the diagnosis:

  1. First guess (wrong): today's AnimationSequencer rewrite caused it. User tested a d910d57 worktree baseline and confirmed "No its basically the same" — pre-existing bug, not a regression.
  2. Parser dump revealed: the UpdateMotion parser had a 2-byte offset error in its MovementData header skip. ACE's Align() pads based on absolute stream length (15 → 16, pad 1 byte, header total 6 bytes), not relative-to-block (which would pad 3 = header 8). The old parser assumed 8 → every subsequent field shifted by 2 → stance + command decoded as the packed-flags dword.
  3. Handler rewrite: OnLiveMotionUpdated now uses SetCycle directly instead of GetIdleCycle (which ignored command when stance==0); preserves current style/motion with "no-change" semantics; full MotionCommand reconstruction from 16-bit wire values.
  4. ACE source confirmed: Player_Tick.cs:368 — "the client will never send a 'client released forward' MoveToState in this scenario unfortunately. because of this, it's better to have the 'client blip forward' bug without it." → Server never broadcasts explicit Ready. Observer must infer stopped from position deltas.

Then dispatched two Opus-4.7 parallel agents with high-effort research

  • implementation mandates per user direction ("ultrathink", "super high effort"):

Agent A — retail animation overhaul (MERGED to main)

10 commits on worktree-agent-ae8eec87, +32 tests (628→660 before my orthogonal adds). Shipped:

  • MultiplyCyclicFramerate on AnimationSequencer — mid-cycle speed change without restart. Fixes "animation speed doesn't scale with run rate" for both the local player and remote observers.
  • Dead-reckoning per remote entity: EMA-smoothed velocity from consecutive UpdatePosition deltas, per-tick integration using the sequencer's current velocity rotated by entity orientation, clamp radius to prevent runaway on lost updates. Fixes "lagging forward" — remote chars now move smoothly between position updates.
  • Commands[] list parsing — the InterpretedMotionState tail list (MotionItem records) was completely unparsed. NPCs transmit waves, attacks, death throes via this list. Now routed through PlayAction (for Actions/Modifiers/Emotes) or SetCycle (SubStates).
  • MotionCommandResolver — reflects the DatReaderWriter.Enums.MotionCommand enum to reconstruct the full 32-bit command value from the 16-bit wire field. Prior ad-hoc OR of 0x40000000 sometimes produced invalid commands.
  • Sequence-wide Velocity/Omega — was per-node via lookup, broke during link transitions (cycle's velocity wasn't surfaced while a link played). Now matches ACE Sequence.Velocity (last-added-wins, retained across link/cycle boundary).
  • Omega integration to entity rotation — TurnRight/TurnLeft cycles now smoothly rotate the entity each tick between server updates.
  • Soft-snap residual — server UpdatePosition is authoritative but blended over ~300ms so corrections don't look like teleports.
  • Stop detection hardening — 300ms position-stale + velocity-dead double signal; dedup on respawn clears stale dead-reckon state.

Deferred by Agent A

  • PosFrames root motion drain (most AC locomotion uses MotionData.Velocity)
  • Full MotionState struct port (Modifier list, Action queue, substateMod)
  • AttackHook → combat damage frame sink
  • ReplaceObjectHook → weapon draw mesh swap
  • Stance transition double-link chain (HandCombat → SwordCombat multi-link)

Agent B — sky / weather / lighting (in progress)

Still running. When it completes, will ship:

  • SkyRenderer with retail GfxObj-based sky objects (not a textured sphere)
  • WeatherRenderer (rain/snow/fog via existing ParticleSystem)
  • Dynamic-lighting shader UBO (8-light UBO per R13 §12.3)
  • Torch/fireplace/spell-aura LightSource registration via SetLightHook

Additional orthogonal work this session (merged on main)

  • UpdateMotion parser fix — 6-byte header (not 8). Committed before agent dispatch.
  • GameWindow stop detection — 400ms position-stale → Ready (later superseded by Agent A's 300ms + velocity-dead version).
  • AppraiseInfoParser profile blobs — ArmorProfile / CreatureProfile / WeaponProfile / ArmorLevels / enchantment bitfields. +6 tests.
  • SocialActions — QueryHealth (0x01BF), PingRequest (0x01E9), FellowshipCreate/Quit/Dismiss/Recruit/Update, SetCharacterOptions, AddChannel/RemoveChannel. +10 tests.
  • InventoryActions — StackableMerge/Split×3, GiveObjectRequest, AddShortcut, RemoveShortcut, TeleToPoi. +8 tests.
  • 9 more GameEvent parsers — UseDone, PutObjectIn3D, InventoryServerSaveFailed, CloseGroundContainer, TradeFailure, AddToTrade, AcceptTrade, QueryItemManaResponse, CharacterConfirmationRequest.

Delta: 19 commits on main, 470 → 628 tests (+158)

# Commit Phase Insert What
1 4db0b2f E.1 582 Motion hook dispatch in AnimationSequencer + PosFrames + vel/omega
2 b04d393 E.1 319 IAnimationHookSink + AnimationHookRouter + GameWindow wiring
3 3517239 E.2 1,072 OpenAL engine + SoundTable cookbook + AudioHookSink
4 d3165f9 E.3 820 ParticleSystem + 13 integrators + ParticleHookSink + registry
5 d86fd08 F.1 656 GameEventEnvelope + GameEventType (94 values) + Dispatcher
6 2561f55 F.2 453 ItemRepository + AppraiseRequest + WieldObject parser
7 2e3f9d7 E.4 516 AttackTargetRequest + 7 combat notifications + CombatState
8 c95aedc E.5 396 CastSpellRequest (targeted + untargeted) + Spellbook + enchants
9 404cab5 H.1 568 Talk/Tell/ChatChannel outbound + HearSpeech inbound + ChatLog
10 6850d71 G.1 566 DerethDateTime + SkyKeyframe + WorldTimeService
11 a28a69a G.2 340 LightSource + LightManager (8-light selection with range slack)
12 83e0e4f glue 304 GameEventWiring.WireAll routes dispatcher → state classes
13 83e8d06 wire 30 GameWindow exposes Chat/Combat/Spells/Items + WireAll live session
14 3bea646 docs 191 Session memory + roadmap updates
15 62cf755 H.2 385 AllegianceRequests (swear/break) + AllegianceTree + passup math
16 68efb60 B.4 121 Use / UseWithTarget / TeleToLifestone outbound actions
17 d461279 char 139 RaiseAttribute/Vital/Skill, TrainSkill, ChangeCombatMode
18 e16f331 appr 438 AppraiseInfoParser — full PropertyBundle deserializer (6 tables)
19 4d96156 wire 14 Wire IdentifyObjectResponse → ItemRepository + Spellbook

Total ~7,700 lines of implementation + tests added today.

What's functionally alive now

Every event the server sends now mutates live client state. Before today: 0 of 94 GameEvents handled. After today: the priority-0 and priority-1 events are routed end-to-end (chat, combat, spells, inventory moves, health updates, system messages, popups).

Animation & sound (Phase E.1 / E.2 / E.3)

  • All 27 AnimationHookType values dispatched every frame an entity crosses an integer frame boundary.
  • Forward / Backward / Both direction semantics match ACE exactly.
  • PosFrames root motion accumulates per-tick for every animation that carries it.
  • CurrentVelocity / CurrentOmega on the sequencer surface the active MotionData's kinematics for downstream physics.
  • AnimationHookRouter fan-outs hooks to multiple sinks; throwing sink doesn't poison others.
  • OpenAlAudioEngine with 16-voice 3D pool + retail-faithful quieter-slot eviction algorithm (matches FUN_00550ad0 exactly). Inverse-square distance model, fails open when OpenAL unavailable.
  • SoundCookbook.Roll picks SoundEntry variants by probability with silence-tail handling.
  • AudioHookSink translates Sound / SoundTable / SoundTweaked hooks into OpenAL calls at the entity's world pos.
  • Entity SoundTable auto-registered from Setup.DefaultSoundTable at hydration time — NPCs and monsters get correct creature-specific footsteps / attack grunts out of the box.
  • ParticleSystem with 13 integrators (Still, LocalVelocity, GlobalVelocity, 7 Parabolic variants, Swarm, Explode, Implode), emit accumulator, overwrite-oldest eviction when pool saturated, linear fade of size/alpha/color over life.
  • ParticleHookSink wires CreateParticle / DestroyParticle / StopParticle / CreateBlockingParticle hooks into the system.

Network protocol (Phase F.1 / F.2 / E.4 / E.5 / H.1)

  • 0xF7B0 envelope parsed (playerGuid + sequence + eventType + payload slice).
  • GameEventDispatcher with per-type handlers + unhandled-counts diagnostic bag + exception isolation per handler.
  • 94 GameEventType enum values named per ACE conventions.
  • Parsers implemented for: ChannelBroadcast, Tell, TransientMessage, PopupString, WeenieError (+ WithString), UpdateHealth, PingResponse, MagicUpdateSpell, IdentifyObjectResponse (header only — full AppraiseInfo deferred), WieldObject, InventoryPutObjInContainer, VictimNotification, KillerNotification, AttackerNotification, DefenderNotification, EvasionAttacker/Defender, AttackDone, MagicUpdateEnchantment, MagicRemoveEnchantment, MagicDispelEnchantment.
  • Outbound actions: AppraiseRequest (0x00C8), AttackTargetRequest (0x0008), CastSpellRequest untargeted (0x0048) + targeted (0x004A), ChatRequests.BuildTalk (0x0015), BuildTell (0x005D), BuildChatChannel (0x0147).
  • Standalone GameMessage HearSpeech (0x02BB local + 0x02BC ranged) parser + dispatch.

Client state (all fed live now)

  • ItemRepository — add / move / remove / property merge, events fire to UI on every mutation.
  • CombatState — per-entity health cache + typed events (damage taken, dealt, evaded, missed, attack-done).
  • Spellbook — learned-spell set + active-enchantment layers, events on add/refresh/remove/purge.
  • ChatLog — unified ring buffer (500 lines) for every chat source; adapters for local / ranged / channel / tell / system / popup / self-echo.
  • WorldTimeService — server-synced PortalYear clock → DayFraction, CurrentHour, IsDaytime, CurrentSky (interpolated 4-keyframe), CurrentSunDirection.
  • LightManager — 8-light selection, slot 0 = Sun, slots 1..7 = nearest in-range point/spot, 10% range slack prevents pop.

Architecture shifts

  1. AcDream.Core.Net → AcDream.Core project reference added. GameEventWiring needs both (dispatcher lives in Net, state classes live in Core). Net now references Core upstream; Core still has zero Net dependency.
  2. DatReaderWriter 2.1.4 → 2.1.7. Upgrade was part of Phase E.3 work. 2.1.7 still doesn't expose ParticleEmitterInfo, so EmitterDescRegistry uses synthesized fallback emitters until the library catches up or we vendor the references/DatReaderWriter sources.

Deliberate deferrals (noted, not forgotten)

  • Particle GL renderer (E.3b): data layer is complete, integrators run, hooks route. The billboarded-quad instanced renderer in the translucent pass is still a todo. Particles are invisible today; their positions/colors/sizes are correct.
  • PlayerDescription (0x0013): dispatcher routes it but we don't yet deserialize the 10-flag-bitfield property-bundle body. Header is parsed (for diagnostic).
  • Full AppraiseInfo body: same story — header parsed, tables not yet walked. Both the PlayerDescription and AppraiseInfo parsers share the same packed-HashTable encoding; one parser lands, both unlock.
  • TurbineChat (0xF7DE): nested blob format, not parsed.
  • Phase G.3 dungeons: portal teleport handling exists; full dungeon streaming + pink-bubble loading + portal visibility BFS is the outstanding piece.
  • Phase H.2 Allegiance: swear/break outbound + tree rendering.
  • Phase H.3 Quest / dialog / emote VM: 122 EmoteType × 39 Trigger mini-VM deferred.
  • Phase H.4 Character creation: CharGen dat parser + appearance picker UI.

Pickup for next session

The master-synthesis "what unblocks most gameplay fastest" priorities after today:

  1. Particle GL renderer (E.3b) — makes all the particle data actually visible. Portal swirls, chimney smoke, fireplace flames, spell auras. Can follow WorldBuilder's ParticleBatcher pattern; billboarded quads in the Phase 9.1 translucent pass.
  2. Dynamic lighting shader (G.2 second commit) — UBO upload of LightManager.Active + shader fragment loop. Torches would cast actual light on their surroundings.
  3. AppraiseInfo / PlayerDescription full deserializer — unlocks attributes panel, paperdoll, spellbook UI, inventory detail view. ~400 lines of packed-htable code but it's pure data; the UI panels need it to show anything.
  4. Phase G.3 dungeon streaming — enter Foundry / any dungeon portal. Big user-visible win.
  5. Phase H.2 Allegiance — small, unblocks allegiance chat channel.
  6. Phase H.3/H.4 — quests + char creation, larger.

Known polish items (parked)

  • Walk-backward first-step jitter (small visual bug).
  • Walk/run-stop twitch at end of motion (missing SubState→Ready blend).
  • Remote-char legs-through-ground Z-offset (server-sent Z vs our terrain sample at observer XY — needs diagnostic dump).
  • Jump feel is ~95% retail-faithful, not 100%.

Baseline proven

  • dotnet build green at every commit boundary.
  • dotnet test green at every commit boundary.
  • 470 → 603 tests passed end of session.
  • 14 commits all to main; any one can be cherry-picked or reverted in isolation.