diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index 5b82491..c94df04 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -33,6 +33,18 @@ | A.3 | Background net receive thread — dedicated daemon thread buffers UDP into Channel, render thread drains | Visual ✓ | | B.3 | Physics collision engine — TerrainSurface (heightmap Z), CellSurface (indoor floor polygon projection), PhysicsEngine (resolver with step-height + cell transitions). Populated from streaming pipeline. | Tests ✓ | | B.2 | Player movement mode — Tab-toggled WASD ground walking, walk/run/idle animations, third-person chase camera, MoveToState + AutonomousPosition outbound, portal entry. Outdoor-only MVP. | Live ✓ | +| D.1 | 2D ortho overlay + font rendering (StbTrueTypeSharp atlas + TextRenderer + DebugOverlay) | Visual ✓ | +| E.1 | Motion-hook expansion — AnimationSequencer fires all 27 hook types per crossed frame; PosFrames root motion + vel/omega exposure; IAnimationHookSink + AnimationHookRouter fan-out | Tests ✓ | +| E.2 | Audio engine — OpenAL 16-voice 3D pool with retail-faithful quieter-slot eviction, SoundTable cookbook (probability-weighted variant picking), Wave PCM decoder, AudioHookSink wiring | Tests ✓ | +| E.3 | Particle system (data layer) — ParticleSystem with 13 motion integrators, EmitterDescRegistry, ParticleHookSink wiring all CreateParticle / DestroyParticle / StopParticle hooks | Tests ✓ | +| E.4 | Combat notifications + outbound — AttackTargetRequest (0x0008), 7 combat notification parsers (Victim/Defender/Attacker/Evasion/AttackDone/UpdateHealth), CombatState per-entity health tracker | Tests ✓ | +| E.5 | Spell cast wire — CastSpellRequest targeted (0x004A) + untargeted (0x0048), Spellbook (learned spells + active-enchantment layers), 5 enchantment GameEvent parsers | Tests ✓ | +| F.1 | GameEvent (0xF7B0) envelope dispatcher — all 94 sub-opcodes routed, exception-isolated handler registry, unhandled-count diagnostic bag; 18 event-payload parsers | Tests ✓ | +| F.2 | Item model + Appraise — ItemRepository with move/equip/property events, AppraiseRequest (0x00C8), IdentifyObjectResponse header, WieldObject + InventoryPutObjInContainer | Tests ✓ | +| G.1 | Sky + day/night — DerethDateTime (retail-exact 7620-tick calendar + 16-hour names + PY year), SkyStateProvider (4-keyframe default with angular-wrap lerp), WorldTimeService (server-synced clock with real-time advance) | Tests ✓ | +| G.2 | Dynamic lighting (selection) — LightSource + LightManager with retail 8-light cap, range-squared with 1.1× slack, slot 0 reserved for Sun, OwnerId-keyed unregister | Tests ✓ | +| H.1 | Chat wire layer — Talk (0x0015) / Tell (0x005D) / ChatChannel (0x0147) outbound, HearSpeech (0x02BB local + 0x02BC ranged) inbound, ChatLog ring buffer with adapters for every chat source | Tests ✓ | +| Glue | GameEventWiring.WireAll — single-call registration mapping parsed GameEvents → Core state classes (ChatLog, CombatState, Spellbook, ItemRepository); GameWindow exposes state classes + wires them to live session | Tests ✓ | Plus polish that doesn't get its own phase number: - FlyCamera default speed lowered + Shift-to-boost diff --git a/memory/project_session_2026_04_18.md b/memory/project_session_2026_04_18.md new file mode 100644 index 0000000..a3af5f3 --- /dev/null +++ b/memory/project_session_2026_04_18.md @@ -0,0 +1,179 @@ +# 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: 14 commits on main, 470 → 603 tests (+133) + +| # | 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 | + +Total **~6,600 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.