File Details
v0.30.5
- R
- May 5, 2026
- 350.74 KB
- 16
- 12.0.5
- Retail
File Name
GuildBankLedger-v0.30.5.zip
Supported Versions
- 12.0.5
GuildBankLedger
v0.30.5 (2026-05-05)
Full Changelog Previous Releases
- Docs: append Codex P2/P3 follow-up bullets to v0.30.5 changelog
Doc-only follow-up to PR #13. CHANGELOG.md and UI/ChangelogView.lua CHANGELOG_DATA v0.30.5 entries gain two parallel bullets describing the Codex P2/P3 follow-up fixes that landed in code in commits a4ed3b0 + dd3d633. No code changes, no version bump. - Docs: append Codex P2/P3 follow-up bullets to v0.30.5 changelog
The v0.30.5 PR (#13) merged with the Codex P2 (Core.lua
MigrateRecoverPeerRealms realm normalization) and Codex P3 (Sync.lua
InitSync seed recency check) fixes in code (commits a4ed3b0 and dd3d633
within #13), but the CHANGELOG.md and UI/ChangelogView.lua CHANGELOG_DATA
v0.30.5 entries only described the original 8-commit work and the
Codex P1 follow-ups. Adding two parallel bullets to each so the
user-facing changelog matches the shipped code before the v0.30.5 tag
goes out.
Doc-only. No code changes. Tests 1135 / 0 / 0 / 0. Lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - Sync: realm-aware peer keys + record realm normalization (v0.30.5)
Replaces broken Ambiguate(name, "none") at 16 sites with GBL:CanonicalPeerKey, which uses custom local-realm-only strip via _isLocalRealm so two distinct same-name characters across connected realms keep distinct peer keys. Adds schema migrations 8 -> 9 (realm-aware peer-key collapse), 9 -> 10 (normalize stored realm strings + rebuild record.id and seenTxHashes), and 10 -> 11 (best-effort recovery for users on the intermediate strip-everything build). BuildRosterCache tracks bare-name ambiguity via a false sentinel. RepairCorruptedPlayerRealms trims hyphen-corrupted realm strings. ConsolidatePeerKeys runtime sweep on GUILD_ROSTER_UPDATE. ResolvePlayerName cross-guild fallback removed. Codex P1/P2/P3 review findings all addressed.
Tests 1135 / 0 / 0 / 0. Lint clean. - Sync: prose cleanup, fix em dashes on PR-added lines
Replace em dashes with periods, semicolons, or colons on every PR-added
line in CHANGELOG.md, CLAUDE.md, and docs/PROMPT-quality-audit.md, per
the global style rule. Pre-existing em dashes on lines this PR did not
touch are left as-is (the rule only applies to lines being written or
edited).
Also: rename "highest-leverage" to "highest-impact" in
docs/PROMPT-quality-audit.md (the "leverage" term is on the AI-vocab
banlist).
No code changes. Tests + lint unaffected.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - Sync: address Codex P2 + P3 follow-up
Two fixes for the second Codex review pass on this PR.
F3 (Codex P2): MigrateRecoverPeerRealms (schema 10 -> 11) was building
its bare-name -> realm lookup from GetGuildRosterInfo without normalizing
the realm portion. GetGuildRosterInfo can return raw spaced realm names
("Aerie Peak") for cross-realm guildmates depending on the realm topology.
The unnormalized realm then flowed into CanonicalPeerKey at the rewrite
site, which preserved the cross-realm form as-is. Result: recovered keys
like "Alice-Aerie Peak" while every other call site of CanonicalPeerKey
produced "Alice-AeriePeak", silently splitting the same peer across two
keys after the recovery migration. This is exactly the duplicate-peer-key
scenario the recovery exists to fix, surviving for users on multi-word
realms (Aerie Peak, Burning Blade, Argent Dawn, etc.).
Fix: normalize realm once after the no-base fallback at Core.lua:1442,
before storing in lookup or the rewrite call.
F4 (Codex P3): InitSync's knownPeers seed loop wrote to
syncState.peers[clean] unconditionally per pairs() iteration. When
knownPeers contains both legacy bare and canonical qualified forms of
the same peer (the upgrade scenario this PR addresses), both raw keys
canonicalize to the same clean key, and pairs() iteration order is
undefined, so an older snapshot can nondeterministically overwrite a
newer one in the runtime cache. The knownPeers consolidation right
below already had a recency check; the syncState.peers write did not.
Fix: wrap the syncState.peers write in the same recency check, mirroring
the knownPeers pattern.
Tests: 1 new for F3 (raw spaced realm in roster recovers to normalized
canonical key) + 2 new for F4 (recency wins in both directions: bare
older / qualified newer, and bare newer / qualified older). Full suite
at 1135 / 0 / 0 / 0. Lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - Sync: ConsolidatePeerKeys + early corruption repair fix cold-startup race
In-game smoke after the last commit revealed peers still keyed bare even
though CanonicalPeerKey worked correctly when invoked directly. Root cause:
the InitSync seed loop runs in OnEnable BEFORE GUILD_ROSTER_UPDATE fires,
so on first reload after a corruption-bearing session, playerRealms was
still corrupt at seed time. CanonicalPeerKey rejected the hyphen-bearing
realm and fell through to bare passthrough. Bare entries got written to
syncState.peers and knownPeers; subsequent /reload after the corruption
was repaired-on-disk still seeded from already-bare knownPeers (the seed
loop's clean == name short-circuit didn't trigger consolidation).
Two-part fix:- Move RepairCorruptedPlayerRealms call into MigrateAllGuilds so it runs
for every guild's playerRealms before the migration ladder AND before
InitSync's seed loop. Closes the cold-startup window — by the time the
seed loop reads playerRealms, corrupt entries have been trimmed to
their first hyphen-free segment. - New GBL:ConsolidatePeerKeys (Sync.lua) walks syncState.peers and
guildData.knownPeers, re-runs CanonicalPeerKey on each key, and merges
stale-form entries into their current canonical form by recency.
Idempotent. Called from GUILD_ROSTER_UPDATE in Core.lua after
BuildRosterCache + the migration retrigger, so any peer-state staleness
from earlier in the session (or from prior sessions whose canonicalization
differed) sweeps to current canonical form once the roster is warm.
Tests: 5 new ConsolidatePeerKeys cases (bare→qualified rewrite in syncState
and knownPeers, recency-merge collisions, no-op for same-realm bare, idempotent).
One existing GUILD_ROSTER_UPDATE retrigger test adjusted to use a knownPeers
entry that doesn't incidentally consolidate (the test was checking schema
version stayed at 10; the consolidation now runs every event but is
orthogonal to the migration retrigger). Full suite at 1132 / 0 / 0 / 0.
Lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- Move RepairCorruptedPlayerRealms call into MigrateAllGuilds so it runs
- Sync: name-handling robustness audit (cross-realm + cross-guild)
Audit pass on the full name-handling surface after the connected-realm
runtime fix landed. Three concrete improvements plus doc cleanup.- ResolvePlayerName: drop the cross-guild fallback (Core.lua:290-297).
Previously, when a bare name wasn't in the current guild's playerRealms,
the function iterated self.db.global.guilds and used any other guild's
mapping it found. For users who'd been in multiple guilds, this could
resolve a bank-log name to a realm from a guild the player no longer
belongs to. New behavior: explicit pr arg → current guild → local
realm fallback. Migrations still pass per-guild tables explicitly. - BuildRosterCache: track bare-name ambiguity via a
falsesentinel.
When two guildmates share a bare name on different realms (e.g. an
Alice on Stormrage and a different Alice on Area52 in one connected-
realm guild), last-write-wins would let CanonicalPeerKey false-re-realm
bare arrivals to whichever realm won the race. Two-pass design now
collects distinct realms per bare name; ambiguous bare names get
false. CanonicalPeerKey type-checks for string realm and treats the
sentinel as "unresolvable, keep bare." RepairCorruptedPlayerRealms
already type-checked, so it leaves false untouched. - CanonicalPeerKey: explicit type(realm) == "string" guard for clarity.
The earlierif realm and not realm:find("-")short-circuited correctly
onfalse, but the explicit type check makes the design intent
readable and protects against any future non-string sentinels.
Doc cleanup: removed stale Ambiguate("guild") references in docstrings
and Sync.lua comments now that production no longer calls Ambiguate.
Added a "Name forms quick reference" table to project CLAUDE.md so the
qualified / canonical-peer-key / bare distinction is easy to look up.
Tests: 9 new (3 ResolvePlayerName cross-guild isolation + 4 BuildRosterCache
ambiguity tracking + 2 CanonicalPeerKey under ambiguous bare lookup).
Full suite at 1127 / 0 / 0 / 0. Lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- ResolvePlayerName: drop the cross-guild fallback (Core.lua:290-297).
- Sync: custom local-realm-only strip in CanonicalPeerKey
In-game smoke confirmed that retail's Ambiguate("guild") strips realm for
ALL guildmates of a connected-realm group, not just same-realm. That
collapses two distinct same-name characters across connected realms (e.g.
Alice-Stormrage and Alice-Area52 on a Tichondrius user) into one peer key.
Replace the Ambiguate("guild") delegation with a custom strip that only
fires when the realm equals the local realm (raw or normalized). Cross-
realm suffixes are preserved so connected-realm guildmates with the same
first name stay distinct.
Implementation:- New GBL:_isLocalRealm(realm) helper centralizes the comparison.
- CanonicalPeerKey: qualified input strips iff _isLocalRealm; bare input
consults playerRealms and re-realms cross-realm bare names to Name-Realm.
Behavioral diff vs the previous Ambiguate("guild") delegation: - Same-realm peers: unchanged (still bare).
- Cross-realm-but-connected-realm peers: now keyed as Name-Realm where
before they collapsed to bare. Distinguishes same-name distinct
characters across connected realms. - Bare arrivals: re-realmed via playerRealms when known.
- Same-name peers across NON-connected-realm cross-realm boundaries: would
preserve before too; unchanged.
The InitSync knownPeers seed loop already runs every persisted key
through CanonicalPeerKey, so existing saved variables self-heal to the
new keying scheme on next /reload.
Tests: 2 new (connected-realm distinct-character + bare/qualified
convergence). Mock Ambiguate("guild") behavior is now unused by
CanonicalPeerKey but still implements local-realm-only strip. Full suite
at 1118 / 0 / 0 / 0. Lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- Sync: repair hyphen-corrupted playerRealms entries
Smoke test revealed an old corruption hiding in playerRealms: an offline
guildmate's entry held "Stormrage-Stormrage-Stormrage..." (a realm string
with embedded hyphens). Origin is a long-fixed code path; BuildRosterCache
only writes for currently-rostered members, so corrupt entries for offline
or departed peers persist indefinitely.
Two layers:- RepairCorruptedPlayerRealms walks playerRealms once per BuildRosterCache
invocation. A realm string containing a hyphen is treated as corrupt
(retail realms never contain hyphens); the helper trims it to the first
hyphen-free segment, or removes the entry if no recoverable segment
exists. Idempotent. - CanonicalPeerKey defensively rejects a hyphen-bearing realm and falls
back to bare passthrough. Belt-and-suspenders for the case where the
cleanup hasn't run yet (e.g. between OnEnable and first GUILD_ROSTER_UPDATE).
Project CLAUDE.md updated to reflect the empirical Ambiguate("guild")
behavior in connected-realm guilds: it strips realm for ALL guildmates
regardless of realm, eliminating same-character duplicate keys at the cost
of leaving same-name distinct-character connected-realm collisions
unresolvable. Earlier "preserve cross-realm suffix" framing was overclaimed.
Tests: 6 new (4 RepairCorruptedPlayerRealms + 1 BuildRosterCache wires it
- 1 CanonicalPeerKey defensive check). Full suite at 1116 / 0 / 0 / 0.
Lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- RepairCorruptedPlayerRealms walks playerRealms once per BuildRosterCache
- Sync: route bare peer names through playerRealms in CanonicalPeerKey
Smoke test of PR #13 surfaced a connected-realm gap: the same character's
messages can be keyed under both bare and qualified forms in syncState.peers,
producing duplicate entries in the Online Peers list. Two interacting causes:- MigrateRecoverPeerRealms premature-bump. When GetLocalRealm() is valid
but GetNumGuildMembers() returns 0 (cold roster), the migration walked an
empty roster, recovered nothing, and still bumped schemaVersion to 11.
Affected users are now stuck at 11 with the recovery never having run. - Ambiguate(name, "guild") cannot re-realm a bare name. Stale bare keys in
knownPeers propagate through Sync.lua's seed loop into syncState.peers,
keyed alongside qualified arrivals from current HELLO traffic.
Fix:
- CanonicalPeerKey now consults guildData.playerRealms for bare input. Same
character hashes identically regardless of arrival form. Bare names with
no roster mapping pass through unchanged. - MigrateRecoverPeerRealms returns 0 without bumping when numMembers == 0,
so the migration retries on a later session. - GUILD_ROSTER_UPDATE retriggers MigrateAllGuilds once on first warm-roster
fire (gated by _migrationsRetried), closing the cold-roster gap without
requiring another login. - InitSync's knownPeers seed loop runs each persisted key through
CanonicalPeerKey and consolidates knownPeers in place when a stale key
collapses to a new canonical form. Self-heals stuck-at-11 saved variables
on next /reload.
Tests: 13 new (5 CanonicalPeerKey roster fallback + 1 cold-roster
premature-bump + 3 GUILD_ROSTER_UPDATE retrigger + 4 InitSync seed
consolidation). Full suite at 1110 / 0 / 0 / 0. Lint clean.
No version bump per the bundle-and-PR rule; this work folds into PR #13's
v0.30.5 stamp at PR-merge time.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- MigrateRecoverPeerRealms premature-bump. When GetLocalRealm() is valid
- Sync: strict-gate later migrations + recompute record.id after realm rewrite
Two P1 fixes from Codex review of #13.- Skip-chain prevention. MigrateNormalizePeerNames short-circuits on
cold realm APIs (schemaVersion stays at 8). Both later migrations
used loose>= targetgates, which let them bump straight from
schema 8 to 10 / 11 in that case, permanently skipping the 8 -> 9
work on the next session. Tightened MigrateNormalizeStoredRealms
to require schemaVersion == 9 and MigrateRecoverPeerRealms to
require schemaVersion == 10. Strict equality keeps the chain in
order and lets cold-start sessions retry on the next load. - Recompute record.id after rewriting record.player.
ComputeTxHash includes the player field, so any record whose
player gets normalized has a stale id afterwards. Without rebuild,
sync would re-import the same transaction under a new id and
seenTxHashes would not catch the duplicate. The migration now
recomputes record.id inline whenever it mutates record.player and
rebuilds seenTxHashes once at the end, mirroring the pattern in
MigrateRepairEpochTimestamps.
Tests: 1097 / 0 / 0 / 0 (1092 baseline + 5 new across strict-gate
behavior on schemas 8 and 9, record.id recomputation, and a negative
test confirming seenTxHashes is not rebuilt when no record.player
changed).
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- Skip-chain prevention. MigrateNormalizePeerNames short-circuits on
- Add docs/PROMPT-quality-audit.md (agent-runnable quality-item audit)
A self-contained agent prompt for evaluating four potential infra/quality
investments against this repo's actual code paths: perf benchmarks, memory
audit, mutation testing, and a second linter (selene). Each item gets KEEP
/ MAYBE / DROP framing with acceptance criteria. The prompt was originally
drafted in the context of deciding whether the items that were rejected as
premature for RCLootCouncil_PriorityLoot would land differently here, where
the sync protocol, planner, and audit-log surface make this addon a much
better candidate for actual perf and memory investment.
Repo-internal doc, no version bump or CHANGELOG entry per the
no-bump-for-trivial-docs convention.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - Sync: realm-aware peer keys + record realm normalization (v0.30.5)
Three related fixes for the WoW realm-name pipeline, all triggered by
the in-game observation that rexxybear was showing up as multiple
realm-tagged entries in the Online Peers list despite only being on
Tichondrius.- Peer-identity canonicalization. The sync layer used
Ambiguate(name, "none") at sixteen sites intending to strip realm
from incoming sender names, but in retail "none" returns the name
unchanged ("guild" is the realm-aware strip). All sixteen sites now
route through a new GBL:CanonicalPeerKey helper which wraps
Ambiguate("guild"). Same-realm senders collapse to bare names so
the rexxybear bloat goes away; cross-realm senders keep their realm
suffix so connected-realm same-name peers stay distinct.
Schema-9 migration is realm-aware: collapses same-realm only.
Schema-11 MigrateRecoverPeerRealms is a best-effort recovery for
anyone who ran the intermediate development build that
unconditionally stripped realm. - Record realm normalization. BuildRosterCache and the v2-to-v3
migration stored the realm portion of player names raw
("Aerie Peak"), while ResolvePlayerName's local-realm fallback
produced the normalized form ("AeriePeak"). The asymmetry let the
same player surface as two different record.player values across
the dedup boundary on realms with whitespace in their name. New
GBL:NormalizeRealm helper plus schema-10 MigrateNormalizeStoredRealms
rewrites stored realms in place. Local-realm fallback centralized
in new GBL:GetLocalRealm. - Connected-realm IsGuildMemberOnline disambiguation. The function
compared stripped name against stripped roster fullName, which
would return the wrong online state for two same-name members on
different realms in a connected-realm guild. Now compares both
sides through CanonicalPeerKey.
Test mock for Ambiguate now matches retail semantics across all four
contexts ("none", "all", "short", "guild") with both raw and
normalized realm forms compared. Mock GetGuildRosterInfo returns the
14th lastLogoff value matching retail. Mock GetNormalizedRealmName /
GetRealmName correctly diverge on whitespace.
Test suite: 1092 passing (1080 baseline + 12 net-new across
CanonicalPeerKey, MigrateNormalizePeerNames realm-aware,
MigrateRecoverPeerRealms, connected-realm peer disambiguation, plus
fixture refits for v0.30.5 tests that used Tichondrius against the
default TestRealm).
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
- Peer-identity canonicalization. The sync layer used
- Merge pull request #12 from RussellFeinstein/chore/sync-contributing-md
Sync CONTRIBUTING.md to single-purpose-frozen branch wording - Sync CONTRIBUTING.md to single-purpose-frozen branch wording
PR #11 (v0.30.4) renamed the short-lived chore/infra/hotfix category to
"single-purpose, frozen after PR closes" in CLAUDE.md and replaced the
delete-after-merge contract with the freeze contract. CONTRIBUTING.md's
maintainer-note paragraph still described the old "short-lived...for
one-off changes" model, which is now stale.
Updates the maintainer-note sentence to match the new wording and adds
"freeze contract" to the cross-reference list. No code, no addon
behavior change, no version bump per feedback_no_bump_for_trivial_docs.md.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com - Merge pull request #9 from RussellFeinstein/chore/dev-link-check
Add check-dev-link.sh dev verification script - Add check-dev-link.sh dev verification script
Detects when the AddOns/GuildBankLedger symlink on a dev machine has
been silently replaced by a real directory copy, a regression mode
where WoW loads stale code regardless of repo state. Verifies the
AddOns entry is a symlink, the link's VERSION matches the repo's
VERSION, Libs/ exists, and VERSION agrees with the .toc Version field.
Repo-internal dev tooling. No addon behavior change, no version bump
per feedback_no_bump_for_trivial_docs.md.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com