promotional bannermobile promotional banner

DeltaSync

An communication addon library used on top of the Ace3 framework.
Back to Files

DeltaSync-v4.0.0

File nameDeltaSync-DeltaSync-v4.0.0.zip
Uploader
PmptastyPmptasty
Uploaded
Jul 1, 2026
Downloads
6.1K
Size
73.9 KB
Flavors
ClassicRetailMoP ClassicTitan Reforged ClassicClassic TBC
File ID
8354275
Type
R
Release
Supported game versions
  • 12.0.7
  • 12.0.5
  • 11.2.7
  • 5.5.3
  • 5.5.2
  • 4.4.2
  • 3.80.0
  • 3.4.5
  • 2.5.5
  • 1.15.8
  • 1.15.7

What's new

DeltaSync Changelog

[v4.0.0] (2026-07-01) - Multi-host: DeltaSync is no longer a singleton (MINOR 15)

New Features

  • lib:NewHost(config) — isolated per-host instances. DeltaSync was a singleton: LibStub:NewLibrary returns one shared table, and Initialize wrote every per-host field (namespace, prefixes, callbacks, peerStates, localState, p2p, rosterSync, guildMode, leafHandlers, aceAddon) onto it — so two consuming addons in one client clobbered each other, last-Initialize-wins. NewHost returns a fully isolated host object (setmetatable({}, { __index = lib })) that owns its own copy of all that state plus its own RegisterComm registrations on its own config.aceAddon. Multiple consumers now coexist without cross-talk: distinct namespaces → distinct prefixes → independent sync. Every instance method (BroadcastVersion, RequestData, SendData, BroadcastItemHashes, InitP2P, InitRosterSync, InitGuildMode, DebugStatus, the delta ops, …) is invoked on the returned host. config shape is identical to Initialize. Location: DeltaSync.lua.
  • lib.MINOR is now actually exposed on the handle. Prior versions documented feature-detection via LibStub("DeltaSync-1.0").MINOR >= N, but the field was never set (MINOR lived only in LibStub's internal minors table). It's now assigned on the handle, so DS.NewHost and DS.MINOR >= 15 works as documented. Location: DeltaSync.lua.

Backward Compatibility

  • lib:Initialize(config) is retained as sugar over the multi-host core. It initializes onto the shared lib table itself — an implicit "default host" — so a single un-migrated consumer is byte-compatible with pre-v4.0.0 behaviour (same DS:Method() / DS.p2p:OnItemCompleted() calls, same wire traffic). Caveat: there is only one default-host slot, so two consumers both calling Initialize still clobber each other. The rule: migrate all but at most one consumer to NewHost; a client is fully clobber-free once every consumer is on NewHost. No breaking change to the documented API — NewHost is additive and Initialize is unchanged for a lone consumer; the major bump signals the architectural shift.

Internal Architecture

  • P2PSession / RosterSync / GuildMode moved from file-local singletons to persistent class tables. Each was a local X = lib.xxx singleton whose methods and (for P2P) timer closures captured that one shared table. They are now persistent class tables kept on lib across LibStub upgrades (lib._P2PClass, lib._RosterClass, lib._GuildModeClass), and each host owns its own instance (host.p2p / host.rosterSync / host.guildMode) carrying a _host back-reference. All P2P timer/retry/catch-up closures now capture the per-host instance instead of the global, and every internal send (SendHandshake/SendHashOffer/BroadcastItemHashes) and debug line routes through self._host — so each addon's P2P traffic and debug log stay on its own host. Storing the classes on lib (not a fresh file-local each load) means a hot upgrade redefines methods on the same tables and live instances pick them up through their metatable. Locations: P2PSession.lua, DeltaSyncRoster.lua, DeltaSyncGuildMode.lua.
  • Per-host optional-module sentinels. NewHost/Initialize seed host.p2p/host.rosterSync/host.guildMode to false (an own field) so the core seams (OnComm_OFFER/OnComm_HANDSHAKE's if self.p2p, SendMessage/_GuildModeInbound's self.guildMode) read this host's own falsy value and never fall through __index to the default host's instance. The Init* methods replace the sentinel with a real instance via rawget. GetCommStats switched to a truthiness check so the sentinel isn't misreported as enabled. Location: DeltaSync.lua.
  • Per-host leaf router. host.leafHandlers is a fresh table per host (never the shared lib.leafHandlers), so two hosts both claiming the same leaf type (e.g. "roster") don't collide. Location: DeltaSync.lua.

Wire Format / Protocol

  • Unchanged. No new prefix, no envelope change; the 7-channel-per-addon budget is untouched. A v4.0.0 host and a pre-v4 peer interoperate exactly as before — this is purely a client-side isolation change.

Migration Notes for Consumers

  • All consumers should migrate to NewHost: replace local DS = LibStub("DeltaSync-1.0"); DS:Initialize{...} with self.dsHost = DS:NewHost{...} and call everything on the held handle — DS:Method(...)self.dsHost:Method(...), DS.p2p:OnItemCompleted(...)self.dsHost.p2p:OnItemCompleted(...). Feature-detect with if DS.NewHost and DS.MINOR >= 15 then.
  • Ordering: because Initialize still works, consumers can migrate one at a time; a client is only guaranteed clobber-free once every DeltaSync consumer in it is on NewHost (or all but one are).
  • Non-consumers / untouched call sites: a lone consumer that keeps calling Initialize needs no changes.

LibStub MINOR

  • Bumped from 14 → 15.

Tooling

  • Added rawget to .luarc.json's global whitelist (used by the per-host instance guards).

[v3.2.1] (2026-07-01) - Send APIs return serialized byte size for host logging (MINOR 14)

Improvements

  • lib:BroadcastData and lib:BroadcastItemHashes now return the serialized payload size (bytes) as a second value alongside the existing ok boolean — the same #message metric the receive path already reports as len. Lets a host log accurate outbound send sizes without re-serializing. Purely additive and backward-compatible: existing single-return callers (local ok = lib:BroadcastData(...)) are unaffected, and the early return false failure paths still return a single value. BroadcastData guards the size read (message and #message or 0); BroadcastItemHashes reads #message directly since its early-out already guarantees a non-nil payload. Location: DeltaSync.lua.

LibStub MINOR

  • Bumped from 13 → 14.

[v3.2.0] (2026-06-29) - Guild-mode: WHISPER→GUILD directed routing for whisper-broken servers (MINOR 13)

New Features

  • lib:InitGuildMode(config) / lib:SetGuildMode(enabled) / lib:IsGuildMode() — opt-in "guild-mode". New module DeltaSyncGuildMode.lua (loaded last) for private/emulated cores — notably Whitemane — that don't deliver addon messages over WHISPER (CHAT_MSG_ADDON never fires for whispers), which silently breaks every directed channel. When the user enables the toggle, the five directed channels (QUERY, RESPONSE, DELTA, the directed OFFER reply, HANDSHAKE) are rerouted from WHISPER to GUILD. config.enabled applies a host-persisted toggle at init; config.onChanged(bool) fires on every flip so the host can persist + refresh its menu. Inert until InitGuildMode is called — consumers that don't opt in (e.g. TOGBankClassic) are byte-identical to v3.1.0. This is a USER toggle, not server detection: the host owns the settings UI and persistence (the library ships no GUI/SavedVariables/slash commands). Location: DeltaSyncGuildMode.lua.

Wire Format / Protocol

  • No new prefix. Guild-mode reuses the existing five directed channels; only their distribution flips WHISPER→GUILD. The 7-channel-per-addon budget is unchanged.
  • Recipient stamp (additive, out-of-band). GUILD is a broadcast — every guild member receives and processes it on the shared addon channel — so each directed GUILD send is prepended with \029<recipient>\029, outside the existing <payload>\030<checksum>\031END envelope. Every receiver drops a message stamped for someone else and strips the stamp from its own, so exactly one peer acts — behaviour identical to a whisper, with no N² response storms. \029 (Unit Separator) never begins an AceSerializer payload (which always starts ^1), so detection is unambiguous, and the stamp survives AceComm chunking/reassembly. The recipient is realm-qualified at send time so same-named characters on different connected realms don't both match.
  • Bit-compatible for everyone else. With guild-mode off (the default), nothing is stamped and the directed channels stay on WHISPER — existing consumers are unchanged. A peer without the module that receives a stamped message simply fails the CRC and drops it.

Behaviour / Interactions

  • RosterSync self-disables under guild-mode. RosterSync is cross-guild (it reaches members of other guilds), and GUILD cannot reach a non-guild player — on a whisper-dead server cross-guild sync is impossible regardless. So RequestRosterSync, RS:OnRequest, and RS:OnResponse no-op while guild-mode is active, keeping undeliverable roster traffic off the shared GUILD channel. Host-registered intra-guild leaf types (e.g. cooldowns:) are not disabled — they reroute + stamp correctly. Location: DeltaSyncRoster.lua.
  • Offline guard generalized. The "skip a directed send to a known-offline guild member" guard now applies to guild-mode's directed GUILD sends as well as WHISPER, suppressing guild-wide broadcasts no online peer would act on. Location: DeltaSync.lua.
  • Cost note. The stamp makes non-recipients drop early, but GUILD still delivers the bytes to every member, each of whom fully reassembles a multi-fragment payload before dropping it (the drop is post-reassembly — AceComm exposes no per-fragment hook). Guild-mode buys correctness, not bandwidth; hosts should keep directed payloads (esp. DELTA/RESPONSE) small under guild-mode.

Migration Notes for Consumers

  • Hosts wanting guild-mode: call lib:InitGuildMode{ enabled = sv.guildMode, onChanged = function(on) sv.guildMode = on end } once after Initialize, and wire a settings toggle to lib:SetGuildMode(checked) / lib:IsGuildMode(). The host owns the checkbox and its persistence. Feature-detect with if lib.InitGuildMode then.
  • Non-consumers: no change, no action — the module is inert unless InitGuildMode is called.
  • Detecting guild-mode: LibStub("DeltaSync-1.0").MINOR >= 13, or feature-detect lib.InitGuildMode.

Documentation & Packaging

  • Added a developer-facing README.md at the repo root — what DeltaSync is, dependencies, embedding (.pkgmeta externals + TOC + the 6-file load order), quick start, the 7-channel table, a grouped API reference (including RosterSync and Guild-mode), and the package-version-vs-MINOR scheme. It ships in the package and is self-contained: the docs/ folder and CLAUDE.md are excluded from packaging, so the README links only to files that ship (CHANGELOG.md, LICENSE).
  • Added an MIT LICENSE at the repo root (the project description already referenced one). Both README.md and LICENSE ship in the zip — neither is in the .pkgmeta ignore list.

LibStub MINOR

  • Bumped from 12 → 13.

[v3.1.0] (2026-06-04) - RosterSync: cross-guild sister-roster sharing (MINOR 12)

New Features

  • lib:InitRosterSync(config) — opt-in cross-guild roster sharing. New module DeltaSyncRoster.lua (loaded after P2PSession.lua) lets confederated guilds share membership over the existing WHISPER QUERY/RESPONSE channels — no new prefix, no broadcast, no P2P-offer participation. config.onSisterRosterUpdated(guildKey) fires after a sister roster is applied (host persists + refreshes UI); optional config.isValidPeer(name) gates whom we serve our home roster to (default accept-all; scoped to RosterSync — it never touches the P2P isValidPeer). Inert until called, so consumers that don't opt in (e.g. TOGBankClassic) are byte-identical to v3.0.0. Requires LibGuildRoster-1.0 MINOR 6+, feature-detected — disables cleanly if the sister API is absent.
  • lib:RequestRosterSync(peerName) — the single call a consumer makes. The host's /who discovery finds an online sister-guild member and hands the name over; DeltaSync does the rest: whispers a QUERY carrying the requester's cached {guildKey→membershipHash} map (built from LibGuildRoster:GetKnownRosters() + GetRosterHash()); the provider short-circuits with "no-change" when the requester is already current, else replies with the full membership + its canonical GetHomeGuildKey() + provenance; the requester applies it via LibGuildRoster:SetSisterRoster(theirKey, members, meta). Single round trip, provider-authoritative key. Location: DeltaSyncRoster.lua.
  • lib:RegisterLeafType(prefix, handlers) — generic additive leaf-type router. A small core addition: an optional module claims a leaf-key type prefix so its directed traffic dispatches to it (by baseline.type on QUERY, data.type on RESPONSE/DATA/DELTA) before the host's onDataRequest/onDataReceived. A roster-typed message coexists with the host's own leaves (e.g. cooldowns: / recipes:) without either seeing the other's traffic. When no handler is registered the router is a no-op and host callbacks fire exactly as before. Location: DeltaSync.lua.

Design — division of labour

  • RosterSync owns the wire and writes MEMBERSHIP (SetSisterRoster); LibGuildRoster owns the store (membership + frozen FNV-1a32 hashing + sister API); the consumer owns PRESENCE (its /who poll calls LibGuildRoster:MarkOnline) and PERSISTENCE (re-feeds SetSisterRoster from SavedVariables on login). RosterSync is membership-pure — no presence, no SavedVariables of its own.
  • Trust: cooperative confederation. A provider serves only its own GetHomeGuildKey(), so it can corrupt at most its own guild's roster; the receiver additionally rejects any roster whose stamped providerCharKey is absent from the served members. meta (provider charKey + timestamp) is the audit trail. No crypto in this release.

Robustness

  • Load-order-resilient LibGuildRoster handle. Initialize, InitP2P, and InitRosterSync now re-resolve the LibGuildRoster-1.0 handle at init time (post-PLAYER_LOGIN, when every addon is loaded) instead of relying solely on the file-load-time LibStub upvalue, reassigning the shared upvalue so all call sites pick it up. The hard ## Dependencies: GuildRoster already guarantees load order for a normal install; this additionally covers an embedder that vendors DeltaSync's source without declaring the dependency, or a TOC whose dependency line is lost. Location: DeltaSync.lua, P2PSession.lua, DeltaSyncRoster.lua.
  • ROSTER debug category. Added to DEBUG_CATEGORY so RosterSync's lib:Debug("ROSTER", …) lines render their actual message instead of being swallowed by the no-category branch (which had reduced every line to [ns] ROSTER). Location: DeltaSync.lua.

Wire Format / Protocol

  • No new prefix. RosterSync rides the existing QUERY (request) and RESPONSE (reply) channels over WHISPER, discriminated by type == "roster". The 7-channel-per-addon budget is unchanged.
  • roster:* leaves are directed-whisper-only and are never placed in the P2P getMyHashes map — keeping them out of guild-wide OFFER broadcasts is a hard isolation requirement, not an optimization.
  • Bit-compatible for everyone else. Existing QUERY/RESPONSE/DATA/DELTA payloads are untouched; the leaf router only intercepts payloads whose type matches a registered prefix, and nothing registers one unless a host opts in.

Migration Notes for Consumers

  • TOGProfessionMaster (RosterSync consumer): call lib:InitRosterSync{ onSisterRosterUpdated = … } once after Initialize, then lib:RequestRosterSync(peerName) per online sister member found via /who. Feed presence yourself with LibGuildRoster:MarkOnline(sisterKey, names) from the same /who results, and persist sister rosters by re-feeding SetSisterRoster from SavedVariables on login. Read results via LibGuildRoster:IsInAnyRoster / GetOnlineMembersScoped. Feature-detect with if lib.InitRosterSync then.
  • TOGBankClassic and other non-RosterSync consumers: no change, no action — the module is inert unless InitRosterSync is called.
  • Detecting RosterSync: LibStub("DeltaSync-1.0").MINOR >= 12, or feature-detect lib.InitRosterSync.

Repository (not shipped to CurseForge)

  • Cross-version dev-sync watcher. Added wow-version-replication.ps1 and a VS Code folderOpen task (.vscode/tasks.json) that mirror the working copy from _classic_era_ into the other installed WoW client folders (_classic_, _anniversary_, _retail_) during development, applying the .pkgmeta ignore list so the targets look like the packaged release. Both are excluded from the package (.pkgmeta ignores .vscode/ and *.ps1 / **/*.ps1 — a root-level *.ps1 was added alongside the existing **/*.ps1), so nothing dev-only ships to CurseForge.

LibStub MINOR

  • Bumped from 11 → 12.

[v3.0.0] (2026-06-04) - GuildCache-1.0 retired; roster engine now external LibGuildRoster-1.0 (MINOR 11)

Breaking Changes

  • The embedded GuildCache-1.0 LibStub library has been removed. DeltaSync no longer ships Libs/GuildCache-1.0/GuildCache-1.0.lua, and the GuildCache-1.0 LibStub MAJOR is no longer registered by DeltaSync. Any addon that resolved LibStub("GuildCache-1.0") from DeltaSync's bundle now receives nil. Roster tracking is now provided by LibGuildRoster-1.0 — the standalone GuildRoster CurseForge addon (CF slug libguildroster) — declared as a required dependency that CurseForge auto-installs and WoW loads before DeltaSync. See Migration Notes below.

Changed

  • DeltaSync now consumes LibGuildRoster-1.0 instead of GuildCache-1.0 for player identity (GetNormalizedPlayer), the whisper online-guard (GetMember), and the default P2P isValidPeer (IsInGuild + IsReady). The soft-dependency presence check is retained, plus per-method feature-detection (if GuildRoster and GuildRoster.GetNormalizedPlayer then), so DeltaSync still degrades gracefully when the lib is absent or an older MINOR. Location: DeltaSync.lua, P2PSession.lua.
  • Whisper online-guard switched from a direct lib.guildRoster table read to the GetMember() accessor. Behavior is unchanged — skip a whisper only when the target is a known guild member who is offline; non-members and cross-realm targets return a nil member and pass through. This drops the coupling to the library's internal table name (which changed from guildRoster to roster). Location: DeltaSync.lua:756-770.
  • Default isValidPeer rewritten to preserve prior semantics. The retired GuildCache:IsInGuild returned true on an empty/not-yet-built roster, so non-guild / early-login peers were never blocked. LibGuildRoster:IsInGuild is a strict membership check instead. The default wrapper now accepts any peer when the local player isn't in a guild or the roster isn't ready (IsReady()), and only filters once it is — preserving MINOR≤10 P2P eligibility behavior. Location: P2PSession.lua:150-165.

Bug Fixes / Robustness

  • playerFullName is no longer left nil when the roster lib returns nil early. Initialize now falls back to inline Name-Realm composition whenever GetNormalizedPlayer() yields nil (e.g. called before UnitName resolves), not only when the library is entirely absent. Location: DeltaSync.lua.
  • DebugStatus roster line now reports actual readinessready / loaded (not ready) / not loaded via IsReady(), instead of merely whether the library is present. Location: DeltaSync.lua.

Packaging

  • LibGuildRoster-1.0 is a required standalone-addon dependency, not a vendored copy. .pkgmeta adds libguildroster to required-dependencies (CurseForge auto-installs the GuildRoster addon); DeltaSync.toc declares ## Dependencies: Ace3, AceCommQueue-1.0, GuildRoster and no longer carries a Libs\… load line for the roster lib. No duplicate copy ships in either the full or -nolib build, and GuildRoster (CF libguildroster) loads before DeltaSync at runtime.
  • Interface levels bumped for the Midnight patches: 120001 → 120007 and 120000 → 120005.
  • Dependency version: the installed GuildRoster must be MINOR 6+ (adds GetNormalizedPlayer and the sister-roster API). DeltaSync feature-detects each method, so an older GuildRoster degrades gracefully rather than erroring — but the cleaner paths stay inactive until it catches up. Publish GuildRoster MINOR 6+ on CurseForge before/with this DeltaSync release.

Migration Notes for Consumers

  • Any consumer resolving LibStub("GuildCache-1.0") must migrate to LibStub("LibGuildRoster-1.0") and apply two method renames: IsPlayerOnlineIsOnline, GetOnlineGuildMembersGetOnlineMembers. Unchanged: NormalizeName, GetNormalizedPlayer, GetMember, and the OnMemberOnline / OnRosterReady (and other) CallbackHandler callbacks. ⚠️ IsInGuild keeps its name but is now a strict membership check — guard with IsReady() if you relied on the old accept-all-on-empty-roster behavior.
  • TOGProfessionMaster must migrate in lockstep. It depends on the standalone DeltaSync addon and resolves GuildCache-1.0 in TOGProfessionMaster.lua and Scanner.lua; it will lose guild sync the moment it loads DeltaSync v3.0.0 until migrated. Do not publish DeltaSync v3.0.0 to CurseForge ahead of the matching TOGProfessionMaster release.
  • TOGBankClassic and other non-roster consumers — affected only if they resolved GuildCache-1.0. Consumers using only DeltaOperations / the P2P path with their own isValidPeer need no change.

LibStub MINOR

  • Bumped from 10 → 11.

[v2.0.4] (2026-04-30) - SerializeBaseline carries type and parent end-to-end (MINOR 10)

Bug Fixes

  • SerializeBaseline / DeserializeBaseline were silently dropping every field outside {hash, version, keys} — TOGProfessionMaster v0.2.0 introduced a hash-then-fetch sync protocol that encodes the request shape on the QUERY baseline as either {type = "leaf-data", keys = {...}} (fetch specific leaves) or {type = "subhashes", parent = "guild:cooldowns" | "guild:accountchars"} (drill into a roll-up). The lib's serializer hard-coded the payload table to three fields, so receivers' onDataRequest saw baseline.type == nil, matched neither branch, and silently no-op'd. Every drill-down session in the requesting client stayed in ACTIVE until DELIVERY_TIMEOUT (180s) fired — guild sync silently failed end-to-end since TOGPM v0.2.0; the only data flowing was spontaneous broadcasts triggered by local scans. Diagnosed live by adding ENTRY debug prints to TOGPM's Scanner.lua onDataRequest callback, which showed type=nil parent=nil keys=N on QUERY arrival, confirming the baseline had been gutted on the wire. Location: DeltaSync.lua:1223-1249.

Wire Format / Protocol

  • Forward-compatible. MINOR<10 senders simply don't include type/parent (existing behavior); MINOR>=10 receivers see them as nil and behave as before. MINOR>=10 senders include the new fields; MINOR<10 receivers silently drop them (existing behavior). Mixed-version guilds will half-work — only the direction where the receiver is MINOR>=10 will honor the new request shapes — which is acceptable since TOGProfessionMaster v0.2.6 gates on MINOR >= 10 and disables guild sync below that.

Migration Notes for Consumers

  • TOGProfessionMaster v0.2.0+ — Required this fix. v0.2.6 ships gated on LibStub("DeltaSync-1.0").MINOR >= 10 and prints Guild sync disabled if it loads against an older DeltaSync. Don't ship TOGPM v0.2.6 to CurseForge until DeltaSync v2.0.4 is the published release on CurseForge.
  • TOGBankClassic and other MINOR<10-era consumers — No change required. They never set baseline.type / baseline.parent, so the wire payload is bit-identical to MINOR 9 for them.
  • Detecting MINOR 10 — Consumers that need the new baseline-field carriage can check LibStub("DeltaSync-1.0").MINOR >= 10 (the value at the LibStub:NewLibrary call site).

LibStub MINOR

  • Bumped from 9 → 10.

[v2.0.3] (2026-04-29) - Hash-mismatch offer condition for content-aware-merge consumers (MINOR 9)

Protocol Changes

  • Hash-mismatch offer conditionP2PSession:OnHashListReceived now offers data whenever its hash differs from the peer's, regardless of updatedAt. Previously a relayer with a slightly higher updatedAt (which gets bumped on every RebuildAll, even when content didn't change) would suppress legitimate offers from the actual data owner. updatedAt remains in the wire format for backwards compat but is no longer load-bearing for correctness. Consumers must merge content-aware on receive (max-wins for monotonic data, union for sets) for this to converge. Required by TOGProfessionMaster v0.2.0's relay-capable cooldown/recipe sync. Location: P2PSession.lua:241-258.
  • Candidate sort downgraded to heuristic — The descending-updatedAt insertion sort in P2PSession:OnOffer is retained but is no longer load-bearing for correctness. With content-aware merge on the receive side, dispatching to any candidate converges to the same answer. The sort now serves only as a "try the most-recently-updated peer first" tiebreak when peer load is equal. Comment in code documents the change. Location: P2PSession.lua:284-297.
  • lib:SendHashOffer doc-comment updated — Removed stale "only sends items where our updatedAt is strictly greater than the peer's" wording; replaced with the new caller contract. The function itself is unchanged — it serializes whatever the caller passes. Location: DeltaSync.lua:1149-1159.

Wire Format / Protocol

  • Bit-identical to MINOR 8. The OFFER hash-list-broadcast and hash-offer messages still carry {itemKey → {hash, updatedAt}} entries, the QUERY/RESPONSE/DATA channels are untouched, and the checksum envelope is unchanged. Old MINOR 7/8 consumers can still receive offers from a MINOR 9 sender — they'll just see more of them, and their existing offer-collection logic continues to pick the entry with the highest updatedAt.

Migration Notes for Consumers

  • Existing consumers (TOGBankClassic, etc.) — No change required. TOGBankClassic uses DeltaOperations but not the P2P offer path, so this MINOR is a no-op for it. Any consumer that does use P2P will simply emit (and observe) more hash-offers when content actually differs.
  • New content-aware-merge consumers (TOGProfessionMaster v0.2.0+) — Must implement merge-on-receive semantics in onDataReceived: max-wins for monotonic fields like cooldown remaining-time, union for sets like crafter lists. Without content-aware merge, two peers with concurrent edits will overwrite each other's changes.
  • Detecting MINOR 9 — Consumers that need to assert the new offer semantics can check LibStub("DeltaSync-1.0").MINOR >= 9 (the value at the LibStub:NewLibrary call site).

LibStub MINOR

  • Bumped from 8 → 9.

[v2.0.2] (2026-04-28) - GuildCache-1.0 callbacks + real-time online tracking + login-race retry (GuildCache MINOR 2)

New Features

  • GuildCache-1.0 callbacks (CallbackHandler-1.0) — Consumers can now react to roster changes without polling. Six events fire via lib.callbacks:Fire(...): OnRosterReady (once after the first successful non-empty rebuild), OnRosterUpdated (every rebuild), OnMemberOnline(name), OnMemberOffline(name), OnMemberJoined(name), OnMemberLeft(name). All payloads are canonical "Name-Realm" strings. Registration follows the standard CBH pattern: GuildCache.RegisterCallback(self, "OnMemberOnline", function(_, name) ... end) — first handler arg is the event name (CBH convention), second is the payload. The lib.callbacks = lib.callbacks or CBH:New(lib) guard preserves consumer subscriptions across LibStub upgrades. Location: Libs/GuildCache-1.0/GuildCache-1.0.lua:50.
  • Real-time CHAT_MSG_SYSTEM parsing for online/offline/join/leave transitions — Previously, online/offline transitions were only detected when WoW happened to fire a roster update. GuildCache now subscribes to CHAT_MSG_SYSTEM and matches the four guild system messages (has come online, has gone offline, has joined the guild, has left the guild / has been kicked out of the guild). The first three update lib.guildRoster in place and fire the corresponding callback immediately; "joined" defers to the next RequestGuildRoster() rebuild so the new entry has populated class/level/rank fields before consumers see OnMemberJoined. The GUILD_ROSTER_UPDATE rebuild diff still fires the same callbacks as a catch-up safety net for any system message a consumer didn't see. Location: Libs/GuildCache-1.0/GuildCache-1.0.lua:300.
  • lib:GetMember(name) query — Returns the full member entry ({name, class, level, rank, rankIndex, rankName, isOnline}) or nil. Useful for consumers that need the rank-index for isValidPeer filters. Location: Libs/GuildCache-1.0/GuildCache-1.0.lua:287.
  • Member entry shape extended additively — Entries now include name, rankIndex, and rankName alongside the existing class, level, rank, isOnline fields. The legacy rank field is retained as an alias for rankName (set to the same value) so MINOR=1 consumers that read entry.rank are unaffected. rankIndex unlocks rank-based peer filtering, which the rank-name string can't reliably do across guilds with custom rank labels.

Bug Fixes

  • Login-race retry in _RebuildGuildRosterGetNumGuildMembers() returns 0 in a brief window after PLAYER_LOGIN even when the player is in a guild. The previous rebuild silently produced an empty roster in that window and waited for the next GUILD_ROSTER_UPDATE to recover, which could leave consumers' first roster query returning misleading results. The rebuild now detects this case (IsInGuild() true && GetNumGuildMembers() == 0), increments lib.retryCount, re-issues RequestGuildRoster(), and bails. After MAX_RETRIES = 5 consecutive zero-member responses it accepts the empty roster as the truth (player genuinely has no guild). Counter resets to 0 on the first successful non-empty rebuild or on a confirmed not-in-guild state. Location: Libs/GuildCache-1.0/GuildCache-1.0.lua:154-160.
  • PLAYER_LOGIN now requests a roster instead of synchronously rebuilding — Old behavior: PLAYER_LOGIN called _RebuildGuildRoster() directly, which under the new retry scheme would have eaten retries on the very first attempt. New behavior: PLAYER_LOGIN only invalidates the cached normalized player name and (if IsInGuild()) calls RequestGuildRoster(). The GUILD_ROSTER_UPDATE event handler is the single canonical entry point for rebuilds, which keeps retry semantics clean. Location: Libs/GuildCache-1.0/GuildCache-1.0.lua:361-366.
  • Event re-registration on LibStub upgrade — Old behavior used if not lib._guildCacheFrame then create end, which meant a hot upgrade from MINOR=1 to a higher MINOR that subscribes to a new event (e.g. CHAT_MSG_SYSTEM) would never receive that event — the existing frame kept its MINOR=1 subscription set. New behavior calls UnregisterAllEvents() then re-registers all needed events on every load, while reusing the same frame instance (so any consumer holding a reference is unaffected). Location: Libs/GuildCache-1.0/GuildCache-1.0.lua:352-358.

Improvements

  • Diff-loop fires both online AND offline transitions (intentional deviation from the LibGuildRoster-1.0 reference implementation, which only fires online). Reasoning: a consumer registering OnMemberOffline shouldn't have an asymmetric gap depending on whether CHAT_MSG_SYSTEM was visible. CHAT_MSG_SYSTEM is still the low-latency primary driver — the diff is the catch-up safety net, and it now works both directions. Comment in code documents the divergence. Location: Libs/GuildCache-1.0/GuildCache-1.0.lua:200-217.
  • wipe(lib.guildRoster) instead of table reassignment — Consumers that stashed local roster = GuildCache.guildRoster get the same updated table after a rebuild instead of holding a stale reference. Required for the new diff semantics to work correctly across calls.
  • .luarc.json — added C_GuildInfo and GuildRoster to diagnostics.globals so the lua-language-server stops flagging the new (C_GuildInfo and C_GuildInfo.GuildRoster) or GuildRoster compatibility shim.

API Surface for Migrating Consumers

  • TOGProfessionMaster currently uses LibStub("LibGuildRoster-1.0"):RegisterCallback("OnMemberOnline", fn) for crafter-online alerts and embeds libs/LibGuildRoster-1.0/. After this lands, TOGPM can swap to LibStub("GuildCache-1.0"):RegisterCallback("OnMemberOnline", fn) and drop the embedded folder — same callback name, same payload contract, same function(_, name) handler signature.

Wire Format / Protocol

  • Unchanged. GuildCache is a pure local-roster library — no AceComm, no SavedVariables.

[v2.0.1] (2026-04-27) - Ship the missing DeltaSyncChannel.lua (MINOR 8)

Bug Fixes

  • DeltaSyncChannel.lua was missing from the repo, breaking every embedder that set channelModule.enabled = true — v2.0.0 added the integration scaffolding for an optional CHANNEL transport (config parsing into self.channelModule, dispatch from SendMessage to self:SendViaChannel, compact-codec branches in BroadcastData / OnComm_DATA), and a defensive error at DeltaSync.lua:497-502 that raises "channelModule.enabled = true but DeltaSyncChannel.lua was not loaded" when the implementation file is absent. The actual file — defining lib:InitChannelModule() (CHAT_MSG_CHANNEL frame setup, prefix-dispatch, self-filter, control-char strip) and lib:SendViaChannel(prefix, body, target) (SendChatMessage transport with 255-byte cap warning) — never got copied across from the consumer addon where it was authored. v2.0.1 ships the file. Embedders whose Initialize({channelModule = {enabled = true, ...}}) was hitting the defensive error in v2.0.0 will work after upgrading to MINOR 8. Location: new file DeltaSyncChannel.lua, registered in DeltaSync.toc after DeltaSync.lua.

New Files

  • DeltaSyncChannel.lua — optional CHANNEL transport, ~160 LOC. Adds two methods to the DeltaSync-1.0 LibStub handle: InitChannelModule() (called automatically by Initialize when channelModule.enabled = true) and SendViaChannel(prefix, body, target) (called automatically by SendMessage for any prefix whose channelConfig.distribution == "CHANNEL"). Embedders that only use GUILD/PARTY/RAID/WHISPER do not need to load this file. WoW Classic silently drops CHAT_MSG_ADDON for custom channel numbers, so this module is the documented workaround: send via raw SendChatMessage (255-byte cap → host must supply a compact serializer/deserializer pair), receive via a dedicated CHAT_MSG_CHANNEL event frame that dispatches into the existing OnComm_* handlers.

Improvements

  • MINOR bumped 7 → 8 at DeltaSync.lua:9 — embedders whose private copies were on MINOR 7 (i.e. consumer addons that had been carrying their own DeltaSyncChannel.lua to work around the missing file) will now be overridden by the packaged MINOR 8 lib via LibStub, and can drop their embedded copies. PS's Libs/DeltaSync/DeltaSyncChannel.lua is the canonical source — same content, modulo a DeltaSync_Channel.luaDeltaSyncChannel.lua filename harmonization in the header comment and missing-lib error string.
  • .luarc.json — added SendChatMessage and DEFAULT_CHAT_FRAME to diagnostics.globals so the lua-language-server stops flagging the new transport's WoW API calls.

[v2.0.0] (2026-04-20) - DeltaSync-1.0 mod 7 Parity with TOGProfessionMaster

New Features

  • DeltaSync-1.0 bumped to MINOR = 7, pulling the merged TOGPM (mod 2) / PersonalShopper (mod 6) superset into the standalone repo — The two forks that had been living in consumer addons at the same DeltaSync-1.0 MAJOR are now consolidated here. Every embedder whose private copy is at mod 2, mod 6, or mod 7 will now be overridden by this packaged version (LibStub picks the highest MINOR, so both forks and future embedders converge on the CurseForge-released lib). Location: DeltaSync.lua:8.
  • NormalizeSender public method — New lib:NormalizeSender(name) canonicalizes AceComm's inconsistent sender strings (bare "Name" on same-realm vs "Name-Realm" on cross-realm). Every OnComm_* handler now routes the sender arg through it before comparison or dispatch, so self-ignore and peer lookups work identically regardless of realm topology. Location: DeltaSync.lua:800.
  • onOfferReceived(sender, data) callback on Initialize config — New raw-OFFER inspection hook for consumers that want to observe hash-list-broadcasts without hooking into the full P2P session state machine. Fires before the P2PSession collect window processes the offer. Location: DeltaSync.lua:418.
  • snifferFrame fallback for non-AceComm receive paths — When a consumer has a comm prefix configured with distribution = "CHANNEL" (raw public chat channel), AceComm's :RegisterComm doesn't fire. DeltaSync now creates a dedicated CHAT_MSG_ADDON event frame that dispatches to the normal OnComm_* handlers, keeping CHANNEL-distributed messages on the same receive path as GUILD/WHISPER. Location: DeltaSync.lua:570.
  • DebugStatus slash-style command — New lib:DebugStatus() prints a diagnostic block: namespace, MINOR, player name, registered prefixes, and AceComm wiring state. Useful when an embedder's sends are silently dropping and you need to confirm which piece of the config is missing. Location: DeltaSync.lua:1412.
  • Debug category/tag toggle API — New IsCategoryEnabled / SetCategoryEnabled / IsTagEnabled / SetTagEnabled plus GetDebugFrame / CreateDebugTab / RemoveDebugTab / BufferDebugMessage / RedrawDebugMessages. Gives host addons a structured way to surface DeltaSync's debug output in their own UI (the TOGPM debug tab consumes these). Location: DeltaSync.lua:1471-1658.
  • GetPrefixInfo / IsPrefixAvailable / GetPeerStates / GetCommStats introspection API — Read-only accessors for the current prefix allocation, peer sync state table, and per-channel send/receive counts. Intended for debug panels and telemetry. Location: DeltaSync.lua:1760-1800.
  • GetProtocolVersion returns the wire-format revision — Returns the MINOR value, for embedders that want to guard calls behind a minimum version. Location: DeltaSync.lua:1406.

Breaking Changes

  • aceAddon is now a required Initialize() config key — Mod 7 no longer embeds AceComm-3.0 into lib; instead it calls self.aceAddon:SendCommMessage(...) on the host addon's own AceAddon instance. An embedder's Initialize({...}) call that omits aceAddon will register receive handlers (via the new CHAT_MSG_ADDON sniffer fallback) but silently fail to send, putting them in a half-broken state. Migration: add aceAddon = self (or whatever holds your AceAddon:NewAddon(...) handle) to the config table passed to Initialize. Location: DeltaSync.lua:451, DeltaSync.lua:753.
  • AceSerializer no longer embedded into liblib:Serialize(...) and lib:Deserialize(...) methods that AceSerializer:Embed(lib) would have added are gone. The library now calls LibStub("AceSerializer-3.0"):Serialize(...) at use-time (cached in a file-local upvalue) so it stays decoupled from Ace's MINOR upgrades and doesn't duplicate methods the host addon already has. Migration: consumers that called lib:Serialize must switch to LibStub("AceSerializer-3.0"):Serialize on their own handle. Location: DeltaSync.lua:19-25.
  • AceCommQueue-1.0 throttling responsibility moved to host addon — Because mod 7 routes sends through self.aceAddon:SendCommMessage, the wrap target for CRC-protection queuing is the host addon, not the library. Embedders must LibStub("AceCommQueue-1.0"):Embed(theirAceAddon) themselves immediately after NewAddon(...), or they'll see chunk-interleaving CRC corruption under sync load. ## Dependencies: AceCommQueue-1.0 remains in DeltaSync.toc so load order is still enforced. Migration: one-line :Embed call in the consumer. Location: host-addon responsibility; documented in CLAUDE.md.
  • Wire format is unchanged but receive paths now normalize sendersOnComm_* handlers route the sender argument through NormalizeSender before comparison/dispatch. Messages from older-mod embedders still decode via the legacy AceSerializer-only fallback in DeserializeWithChecksum, so no user-visible wire break. Mentioned here because "same wire format, different handler behavior" is the kind of thing that bites during migration debugging.
  • GuildCache extracted into its own LibStub library GuildCache-1.0 — Previously, roster-cache methods (NormalizeName, GetNormalizedPlayer, IsPlayerOnline, IsInGuild, GetOnlineGuildMembers) hung off the DeltaSync-1.0 lib handle and guildRoster was at lib.guildRoster. These have moved to a new LibStub library with MAJOR GuildCache-1.0, embedded at Libs/GuildCache-1.0/GuildCache-1.0.lua. Consumers that called DeltaSync:NormalizeName(x) (etc.) must now call LibStub("GuildCache-1.0"):NormalizeName(x), or grab the handle once: local GC = LibStub("GuildCache-1.0"); GC:NormalizeName(x). Motivation: GuildCache is independently useful (other addons can embed it without pulling the full sync stack) and follows the same "embedded initially → external dependency later" path DeltaSync itself is on. Same pattern TOGPM uses for LibGuildRoster-1.0. Inside DeltaSync, all consumers now route through a file-local upvalue with soft-dep presence checks, so embedders that drop GuildCache get graceful degradation (inline realm derivation, no whisper online-guard, accept-all isValidPeer default).

Improvements

  • Cross-realm NormalizeName correctness — Previous implementation's regex "^(.-)%-(.+)$" matched greedy across every hyphen in the string, which broke for realm names containing hyphens (rare but possible). Replaced with trimmed:match("%-[^%-]+$") — only appends the local realm for bare same-server names, and leaves any existing -Realm suffix untouched so cross-realm peers are stored per-realm correctly. Includes an explicit doc note about AceComm's inconsistent sender-realm-suffix behavior. Location: GuildCache.lua:37-61.
  • ComputeDelta / ApplyDelta convenience wrappers retained — Despite the substantial DeltaSync.lua rewrite, the wrapper methods that delegate to DeltaOperations.lua remain at the same call sites so consumer code built against v1.0.0 does not need to change delta-ops call sites. Location: DeltaSync.lua:1739-1748.
  • DeltaOperations.lua and P2PSession.lua unchanged — Both files are byte-identical to the current TOGPM copy; no merge work required. All delta/hash/P2P session machinery works exactly as in v1.0.0.

[v1.0.0] (2026-04-03) - P2P Session Hardening, CRC Integrity & GuildCache Guard

New Features

  • DELIVERY_TIMEOUT for in-flight data streams — After a peer replies sync-accept, the requester now arms a DELIVERY_TIMEOUT timer (default 180s) so a provider that accepts and then goes silent can no longer pin an inbound session slot forever. On timeout the session transitions to FAILED, the slot frees, and catch-up logic retries another peer. Location: P2PSession.lua.
  • TryAcquireSendSlot / ReleaseSendSlot outbound accounting — Outbound sends now use explicit slot acquisition symmetric with the inbound maxActiveSessions model. A safety timer (SEND_TIMEOUT, default 90s) auto-releases slots the host forgets to release, so a crash in the host's send path can't deadlock the sender. Public API: host calls lib.p2p:ReleaseSendSlot(requester) after the wire send returns. Location: P2PSession.lua.
  • SerializeWithChecksum / DeserializeWithChecksum wrapped around all 7 channels — Every comm path (VERSION, DATA, QUERY, RESPONSE, DELTA, OFFER, HANDSHAKE) now goes through the checksum envelope (<AceSer payload>\030<checksum>\031END). Corrupt or truncated messages are detected at the edge and dropped with an INTEGRITY-MISMATCH log line instead of feeding garbage into delta apply. Graceful legacy-format fallback keeps compatibility with addons still on the older AceSerializer-only wire. Location: DeltaSync.lua.

Bug Fixes

  • OFFER/HANDSHAKE session state machine gaps — Several sequences where a peer's hash-offer arrived after the collect window closed, or a sync-accept arrived for an item the dispatch loop had already given to another peer, left orphan session state. State transitions now validate against the current session table so late messages are ignored rather than mutating unrelated sessions. Location: P2PSession.lua.
  • Whispering offline guild memberslib:SendMessage() with a WHISPER distribution would happily queue messages to members who had logged out, wasting the AceComm slot and causing spurious "no such player" errors in chat. Added a guard via GuildCache:IsPlayerOnline(target) that drops the send with a debug log when the target isn't online. Location: DeltaSync.lua.

Improvements

  • docs/COMMUNICATION.md brought in line with the 7-channel reality — Documentation still described the original 5-channel VERSION/DATA/QUERY/RESPONSE/DELTA layout. Rewrote the channel table to include OFFER and HANDSHAKE, documented the hash-list-broadcast wire format and the sync-request/sync-accept/sync-busy handshake payloads, and fixed every markdownlint warning in the file. Location: docs/COMMUNICATION.md.
  • docs/DESIGN.md updated for Phase 2 completion — Marked Phase 2 (P2P session layer) as complete, rewrote the architecture diagrams to show OFFER/HANDSHAKE as separate channels from DELTA, and added the "embedded vs global shared addon" section explaining the 16-prefix budget trade-off. All markdownlint errors fixed. Location: docs/DESIGN.md.
  • CurseForge description refreshed — Added the v0.0.3-alpha entry to the external changelog block consumed by CurseForge's project page. Location: docs/Curseforge_Description.html.

[v0.0.3-alpha] (2026-04-02) - Standalone GuildCache Module

New Features

  • GuildCache.lua — generic guild presence cache — New file providing NormalizeName, GetNormalizedPlayer, IsPlayerOnline, IsInGuild, and GetOnlineGuildMembers off the lib handle. Maintains lib.guildRoster (normalizedName → {isOnline, class, level, rank}) via GUILD_ROSTER_UPDATE. Extracted so consuming addons don't each have to reinvent name normalization and roster tracking, and so the library itself can gate whispers on online state. Location: GuildCache.lua.
  • isValidPeer callback on InitP2P — Host addons can now filter which guildmates are eligible P2P peers (e.g. rank, role, "only trust officers"). Called during offer collection and dispatch; peers that fail the check are skipped. Location: P2PSession.lua.
  • TableContains, TableCount, ValidateDelta utility methods on lib — Small helpers previously duplicated across consumers, now exposed centrally. ValidateDelta does version/timestamp/hash structural checks before ApplyStructuredDelta runs. Location: DeltaSync.lua, DeltaOperations.lua.

Bug Fixes

  • QUERY channel was still using bare AceSerializer format — Every other channel had migrated to the checksum-wrapped format, but QUERY was missed, so queries crossing the wire were not integrity-checked. Migrated to SerializeWithChecksum/DeserializeWithChecksum for consistency and to catch the same class of truncation bugs. Location: DeltaSync.lua.
  • Extra end syntax error in DeltaSync.lua — A stray end kept the file from loading cleanly on fresh embed. Removed. Location: DeltaSync.lua.
  • Duplicated utility block in DeltaSync.lua — A block of helper functions was copy-pasted during the earlier P2P merge; the duplicate shadowed the authoritative copy. Removed. Location: DeltaSync.lua.
  • Initialize() doc comment referenced removed parameters — Cleaned up the header block so it matches the current config schema. Location: DeltaSync.lua.

Improvements

  • Initialize() now derives local player identity via GuildCache — Instead of each host addon passing playerName/playerFullName through config, Initialize() calls GuildCache:GetNormalizedPlayer() on first use. Hosts that still pass these values override the derived ones. Location: DeltaSync.lua.
  • P2PSession normalizes senders through Norm()/Me() helpersOnHashListReceived and OnOffer used to compare sender strings directly, which broke when AceComm delivered bare names vs. fully-qualified names on the same payload. All sender comparisons now go through normalization. Location: P2PSession.lua.
  • .luarc.json — WoW API globals added — Several API functions (C_Timer, GetRealmName, etc.) were missing from the LSP globals list, causing noise in the problems panel. Added them. Location: .luarc.json.
  • Single-consumer constraint documented — Added a header block to P2PSession.lua clarifying that one LibStub instance serves one embedding addon (the 7-prefix budget is per-addon, not per-library). Location: P2PSession.lua.
  • CurseForge description — multi-consumer claim corrected — Earlier marketing text implied two addons on the same client could share one DeltaSync instance. They can't (each gets its own LibStub copy with its own prefixes); wording updated. Location: docs/Curseforge_Description.html.

[v0.0.2-alpha] (2026-04-01) - P2P Session Protocol & Ace3 Integration

New Features

  • P2PSession.lua — generalized port of TOGBankClassic's P2P protocol — New file implementing the broadcast / collect / dispatch / handshake loop: lib:BroadcastItemHashes kicks off a GUILD hash-list, peers reply with hash-offer whispers during the collect window, dispatch picks the newest peer per stale item and sends a sync-request, and the peer replies sync-accept (initiates data exchange) or sync-busy (try next peer). No single "banker" bottleneck — any peer with fresh data can serve. Host integrates via callbacks on InitP2P(config): getMyHashes, hasContent, hasMissingItems, onSyncAccepted, onDataRequest, onDataReceived. Location: P2PSession.lua.
  • OFFER and HANDSHAKE channel types — Two new addon-message prefixes (-o and -h) take the library from 5 channels to 7 out of the 16-prefix-per-addon WoW budget. OFFER carries hash-list-broadcasts (GUILD) and hash-offer replies (WHISPER); HANDSHAKE carries the three sync-request/accept/busy messages. Location: DeltaSync.lua.
  • High-level P2P API on libBroadcastItemHashes(items, priority), SendHashOffer(target, items, priority), SendHandshake(target, payload, priority). These are thin wrappers over SendMessage but document the protocol intent. Location: DeltaSync.lua.
  • Full CRC wire-format frameworkSerializeWithChecksum / DeserializeWithChecksum wrap every outgoing payload with an ASCII RS separator (\030), an additive checksum, and a stop marker (\031END). The deserializer does best-effort corrupt-payload decoding on INTEGRITY-MISMATCH so a debug log can still show what the peer was trying to send. Location: DeltaSync.lua.

Improvements

  • Serialization switched from custom built-in to AceSerializer-3.0 — The earlier length-prefixed SerializeData/DeserializeData (added in v0.0.1-alpha) was functional but every consuming addon already embedded AceSerializer anyway. Switched to AceSerialization-3.0 via LibStub and embed it alongside AceComm/AceCommQueue in RegisterCommChannels, eliminating ~150 lines of hand-rolled serializer code. Location: DeltaSync.lua.
  • AceCommQueue-1.0 and Ace3 declared as hard dependenciesDeltaSync.toc now lists them under ## Dependencies, enforcing load order. Previous bundled copies under Libs/ removed since AceCommQueue is shipped as a standalone addon. Location: DeltaSync.toc.
  • .pkgmeta tightened for CurseForge release — Added required-dependencies (initially ace3, AceCommQueue-1.0), removed the now-unused externals block, and expanded the ignore list to catch docs/**, *.ps1, *.bat, and the code-workspace file so they don't end up in the zip. Location: .pkgmeta.

Bug Fixes

  • AceCommQueue-1.0 listed as a CurseForge required-dependency caused a 400/1018 upload error — CurseForge only accepts slugs for projects published on its platform; AceCommQueue is GitHub-only, so declaring it in required-dependencies: broke uploads. Removed from .pkgmeta required-dependencies — load order is still enforced via ## Dependencies: in DeltaSync.toc, which is all WoW actually cares about. Location: .pkgmeta.

[v0.0.1-alpha] (2026-03-17) - Initial Library Extraction from TOGBankClassic

New Features

  • Library skeleton as DeltaSync-1.0 via LibStub — First pass at the standalone library, extracted and generalized from TOGBankClassic's proven delta sync system. Target: any WoW addon that needs guild-scoped P2P data sync can embed this library and get 90-99% bandwidth reduction on updates. Location: DeltaSync.lua, DeltaSync.toc.
  • Built-in length-prefixed serializerSerializeData / DeserializeData replace the earlier tostring() placeholder with a real type-tagged format supporting tables, strings, numbers, booleans, nil, and circular-reference protection. (Superseded in v0.0.2-alpha by AceSerializer, but the API surface remains so host addons don't have to change call sites.) Location: DeltaSync.lua.
  • ComputeDelta() / ApplyDelta() convenience wrappers on lib — Shorthand that delegates to ComputeStructuredDelta / ApplyStructuredDelta in DeltaOperations.lua so hosts don't have to know the module split. Location: DeltaSync.lua.
  • Self-ignore on all 5 message handlers — VERSION, QUERY, RESPONSE, DATA, and DELTA now drop messages whose sender matches the local player. Handled for both bare UnitName and fully-qualified Name-Realm formats because AceComm delivers either depending on realm context. Without this, every broadcast you sent came back and triggered your own handlers. Location: DeltaSync.lua.

Bug Fixes

  • Duplicate ValidateDelta() in DeltaSync.lua — A stub version shadowed the complete implementation in DeltaOperations.lua (which does full version/timestamp/hash validation). Removed the stub so the authoritative one is always used. Location: DeltaSync.lua.

Improvements

  • playerName and playerFullName stored during Initialize() — Cached on lib at init time so handlers don't re-query the WoW API on every incoming message. Location: DeltaSync.lua.

This mod has no related projects