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>
289 lines
14 KiB
Markdown
289 lines
14 KiB
Markdown
# 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.
|