docs(issues): #8/#9/#11 filed; #10 wired (KillerNotification)
Files four new issues created by the 2026-04-25 PDB-discovery sprint:
#8 (DONE 2026-04-25) — pdb-extract tool, shipped 69d884a
#9 (OPEN) — function-map address-correction sweep
(Phase E will close)
#10 (DONE 2026-04-25) — wire KillerNotification (0x01AD); orphan
parser at GameEvents.ParseKillerNotification
existed but was never registered. This commit
adds CombatState.OnKillerNotification +
KillLanded event, registers the dispatcher
handler, and adds a regression test.
#11 (OPEN) — spell metadata loader (spells.csv → SpellTable)
(Phase F will close)
Code change is minimal — three lines of dispatch + a 12-line
CombatState method with a typed event for future killfeed UI.
818 tests passing (+1 KillerNotification).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0a429a980c
commit
567078803f
4 changed files with 113 additions and 0 deletions
|
|
@ -177,10 +177,73 @@ Copy this block when adding a new issue:
|
|||
|
||||
---
|
||||
|
||||
## #9 — Address-correction sweep on `acclient_function_map.md`
|
||||
|
||||
**Status:** OPEN
|
||||
**Severity:** LOW (per-developer convenience; gets us correct symbol→address mapping for ~71 hand-curated entries)
|
||||
**Filed:** 2026-04-25
|
||||
**Component:** docs / research
|
||||
|
||||
**Description:** The hand-curated function map at `docs/research/acclient_function_map.md` has ~71 entries with addresses derived from older Ghidra chunk inspection. The PDB-extracted `docs/research/named-retail/symbols.json` (Sept 2013 EoR build) is now the authoritative name-source. Several entries point at mid-function offsets rather than function starts. E.g. our `FUN_005111d0 = UpdatePhysicsInternal` — actual start in PDB is `0x510700`. Need a sweep that verifies + corrects all hand-curated rows.
|
||||
|
||||
**Root cause / status:** PDB built from a slightly different revision (~0xC00 byte delta on some functions); legacy Ghidra-derived addresses don't all line up. Match by name (PDB names are ground truth) and record the corrected address.
|
||||
|
||||
**Files:**
|
||||
- `docs/research/acclient_function_map.md` — corrections in-place.
|
||||
- `docs/research/named-retail/symbols.json` — name→address lookup source.
|
||||
|
||||
**Acceptance:** Spot-check 10 entries across all sections — each row's address matches `symbols.json` for the named function. Mismatches annotated `(corrected from FUN_xxx, was mid-body)`.
|
||||
|
||||
---
|
||||
|
||||
## #11 — Spell metadata loader (`spells.csv` → `SpellTable`)
|
||||
|
||||
**Status:** OPEN
|
||||
**Severity:** LOW (unblocks issue #6's stacking aggregation; also adds tooltip / icon / school metadata for future panels)
|
||||
**Filed:** 2026-04-25
|
||||
**Component:** core / spells
|
||||
|
||||
**Description:** `docs/research/data/spells.csv` (3,956 spells × 35 cols) has all the per-spell metadata the existing `Spellbook` lacks: `Name`, `School`, `Family` (buff stacking bucket), `IconId`, `Mana`, `Duration`, `IsDebuff`, `IsFellowship`, `Description`. Need a `SpellTable` loader that hydrates a `Dictionary<uint, SpellMetadata>` at startup so `Spellbook.TryGetMetadata(spellId, out)` works.
|
||||
|
||||
**Root cause / status:** Issue #6 (vital max ignores enchantment buffs) needs `Family` to do correct stacking aggregation (only one buff per family wins; highest generation). That field comes only from `spells.csv`.
|
||||
|
||||
**Files:**
|
||||
- `src/AcDream.Core/Spells/SpellMetadata.cs` (new record).
|
||||
- `src/AcDream.Core/Spells/SpellTable.cs` (new loader).
|
||||
- `src/AcDream.App/Rendering/GameWindow.cs` (load at OnLoad).
|
||||
- `src/AcDream.App/AcDream.App.csproj` (`<None Update>` to copy CSV to bin output).
|
||||
- `src/AcDream.Core/Spells/Spellbook.cs` (accept optional `SpellTable`, expose `TryGetMetadata`).
|
||||
|
||||
**Acceptance:** Launch with `ACDREAM_DEVTOOLS=1` shows console line `spells: loaded 3956 entries from spells.csv`. `Spellbook.TryGetMetadata(spellId, out)` returns valid record for active enchantment lookups.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
# Recently closed
|
||||
|
||||
## #10 — [DONE 2026-04-25] Wire `KillerNotification (0x01AD)`
|
||||
|
||||
**Closed:** 2026-04-25
|
||||
**Commit:** `docs(issues): #8/#9/#11 filed; #10 wired (KillerNotification)`
|
||||
**Resolution:** Orphan parser at `GameEvents.ParseKillerNotification` existed but was never registered for dispatch in `GameEventWiring.cs`. Added a `combat.OnKillerNotification(victimName, victimGuid)` method on `CombatState` that fires a new `KillLanded` event, then registered the handler. One-line dispatch + 12-line CombatState method + one regression test fixture in `GameEventWiringTests`.
|
||||
|
||||
---
|
||||
|
||||
## #8 — [DONE 2026-04-25] pdb-extract tool: PDB → symbols.json + types.json
|
||||
|
||||
**Closed:** 2026-04-25
|
||||
**Commit:** `tools(pdb-extract): #8 PDB -> symbols.json + types.json sidecar`
|
||||
**Resolution:** Pure-Python (no deps) MSF 7.00 PDB parser at `tools/pdb-extract/pdb_extract.py`. Reads `refs/acclient.pdb` (Sept 2013 EoR build), extracts S_PUB32 records from the symbol stream + named class/struct types from TPI, and writes JSON sidecars to `docs/research/named-retail/`:
|
||||
- `symbols.json` — 18,366 named functions (`address` + demangled `name` + raw `mangled`)
|
||||
- `types.json` — 5,371 named class/struct records (`name` + `size` + `kind`)
|
||||
|
||||
Best-effort MSVC C++ demangler handles the common `?Method@Class@@<sig>` patterns + ctors (`??0`) + dtors (`??1`); operator overloads and vtables left mangled. Spot-check verified: `CEnchantmentRegistry::EnchantAttribute` resolves to `0x00594570` exactly as the discovery agent reported. Runtime <1s.
|
||||
|
||||
Regen workflow: `py tools/pdb-extract/pdb_extract.py refs/acclient.pdb`. The committed JSON outputs are stable + ~3 MB combined; ripgrep/jq on them is faster than re-parsing.
|
||||
|
||||
---
|
||||
|
||||
## #5 — [DONE 2026-04-25] VitalsPanel stamina/mana bars always null
|
||||
|
||||
**Closed:** 2026-04-25
|
||||
|
|
|
|||
|
|
@ -107,6 +107,13 @@ public static class GameEventWiring
|
|||
var p = GameEvents.ParseAttackDone(e.Payload.Span);
|
||||
if (p is not null) combat.OnAttackDone(p.Value.AttackSequence, p.Value.WeenieError);
|
||||
});
|
||||
dispatcher.Register(GameEventType.KillerNotification, e =>
|
||||
{
|
||||
// ISSUES.md #10 — orphan parser, never registered before. The
|
||||
// server fires this after a player lands a killing blow.
|
||||
var p = GameEvents.ParseKillerNotification(e.Payload.Span);
|
||||
if (p is not null) combat.OnKillerNotification(p.Value.VictimName, p.Value.VictimGuid);
|
||||
});
|
||||
|
||||
// ── Spells ────────────────────────────────────────────────
|
||||
dispatcher.Register(GameEventType.MagicUpdateSpell, e =>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,14 @@ public sealed class CombatState
|
|||
/// <summary>An attack commit completed (0x01A7). WeenieError = 0 on success.</summary>
|
||||
public event Action<uint /*attackSeq*/, uint /*weenieError*/>? AttackDone;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when the server confirms the player landed a killing blow
|
||||
/// (GameEvent <c>KillerNotification (0x01AD)</c>). Event payload is
|
||||
/// the victim's display name + their server GUID. Used by killfeed UI
|
||||
/// (future panel) and any plugin scoring kill counts.
|
||||
/// </summary>
|
||||
public event Action<string /*victimName*/, uint /*victimGuid*/>? KillLanded;
|
||||
|
||||
public readonly record struct DamageIncoming(
|
||||
string AttackerName,
|
||||
uint AttackerGuid,
|
||||
|
|
@ -116,6 +124,16 @@ public sealed class CombatState
|
|||
public void OnEvasionAttackerNotification(string defenderName)
|
||||
=> MissedOutgoing?.Invoke(defenderName);
|
||||
|
||||
/// <summary>
|
||||
/// Server confirmation that the player landed a killing blow on a
|
||||
/// target. Wire source: GameEvent <c>KillerNotification (0x01AD)</c>
|
||||
/// — the parser at <c>GameEvents.ParseKillerNotification</c> shipped
|
||||
/// alongside victim/defender notifications but was never registered
|
||||
/// for dispatch until 2026-04-25 (per ISSUES.md #10).
|
||||
/// </summary>
|
||||
public void OnKillerNotification(string victimName, uint victimGuid)
|
||||
=> KillLanded?.Invoke(victimName, victimGuid);
|
||||
|
||||
public void OnEvasionDefenderNotification(string attackerName)
|
||||
=> EvadedIncoming?.Invoke(attackerName);
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,31 @@ public sealed class GameEventWiringTests
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(p), xp); p += 4;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WireAll_KillerNotification_FiresKillLandedOnCombatState()
|
||||
{
|
||||
// Issue #10 — orphan parser at GameEvents.ParseKillerNotification
|
||||
// existed but was never registered for dispatch until 2026-04-25.
|
||||
// Now wired: 0x01AD lands on CombatState.OnKillerNotification +
|
||||
// fires the KillLanded event.
|
||||
var (d, _, combat, _, _) = MakeAll();
|
||||
string? gotVictimName = null;
|
||||
uint gotVictimGuid = 0;
|
||||
combat.KillLanded += (name, guid) => { gotVictimName = name; gotVictimGuid = guid; };
|
||||
|
||||
// Wire shape: string16L victimName + u32 victimGuid
|
||||
byte[] nameBytes = MakeString16L("Drudge");
|
||||
byte[] payload = new byte[nameBytes.Length + 4];
|
||||
Array.Copy(nameBytes, payload, nameBytes.Length);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(nameBytes.Length), 0x80001234u);
|
||||
|
||||
var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.KillerNotification, payload));
|
||||
d.Dispatch(env!.Value);
|
||||
|
||||
Assert.Equal("Drudge", gotVictimName);
|
||||
Assert.Equal(0x80001234u, gotVictimGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WireAll_MagicPurgeEnchantments_CallsOnPurgeAll()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue