promotional bannermobile promotional banner

DeltaSync

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

DeltaSync — Efficient Data Sync Library for WoW Addons

For addon developers. DeltaSync is a LibStub library that handles the hard parts of keeping guild-member data in sync — version comparison, delta compression, peer-to-peer catch-up, and message integrity — so you can focus on your addon's actual features instead of writing another sync engine from scratch.

It's the same sync stack that powers TOGBankClassic and TOGProfessionMaster, extracted into a reusable library you can embed in a few lines.

Why Use This

DeltaSync — Efficient Data Sync Library for WoW Addons

For addon developers. DeltaSync is a LibStub library that handles the hard parts of keeping guild-member data in sync — version comparison, delta compression, peer-to-peer catch-up, and message integrity — so you can focus on your addon's actual features instead of writing another sync engine from scratch.

It's the same sync stack that powers TOGBankClassic and TOGProfessionMaster, extracted into a reusable library you can embed in a few lines.

Why Use This

If your addon shares data across guild members — inventories, crafting recipes, raid rosters, settings — you've probably run into some combination of:

  • Full re-sends every time anything changes, hammering the guild channel
  • CRC errors under load when several addons compete for chat throttle slots
  • New members joining and having no idea who to ask for a data catch-up
  • Home-rolled hashing that disagrees on tables because Lua iteration order is unstable
  • One slow peer freezing everyone else because there's no timeout on expected data

DeltaSync solves all of these. Drop it in, wire up a handful of callbacks, and you get battle-tested sync behavior that's been running in production on live Classic Era guilds.

What You Get

Bandwidth-efficient sync

Instead of re-sending everything whenever something changes, DeltaSync computes and transmits only the differences between states. In practice this cuts sync traffic by 90-99% for incremental updates. Full sync is always available as a fallback for new members or major changes.

Peer-to-peer catch-up

When a player logs in missing data, DeltaSync broadcasts a summary of what they have and lets any peer with fresher data step up. No single "banker" bottleneck, no manual request dance. If the first peer is busy, it politely declines and the next best peer takes over automatically.

Message integrity you can trust

Every structured message is wrapped with a checksum and a stop-marker. On the receive side, truncation and corruption are detected separately — so if a message got mangled mid-flight, you find out instead of silently applying garbage. Corrupt messages are logged with diagnostic info, not quietly dropped.

A fair share of your addon-message budget

WoW caps each addon at 16 communication prefixes. DeltaSync uses 7 of yours (one per channel type), leaving 9 for your own use. Prefixes are auto-derived from your addon's name so you don't have to pick unique strings yourself.

Multiple addons in one client, with zero cross-talk

New in v4.0.0: DeltaSync:NewHost(config) returns an isolated per-host object that owns its own namespace, prefixes, callbacks, peer state, and P2P sessions. Two — or ten — addons in the same client each call NewHost and run completely independent sync, with no shared state to clobber. (Before v4.0.0 the library was a singleton: whichever addon initialized last silently took over the others' prefixes and callbacks.) You call every DeltaSync method on the handle you hold, so the addons never see each other's traffic. The old DeltaSync:Initialize(config) call still works as backward-compatible sugar for a single consumer.

Guild roster tracking via LibGuildRoster-1.0

DeltaSync uses LibGuildRoster-1.0 — the standalone LibGuildRoster addon — to track who's in your guild and who's online, so it can avoid whispering offline members and default its peer-eligibility check. It's a required dependency that CurseForge installs automatically alongside DeltaSync; DeltaSync also feature-detects each method, so it degrades to reasonable inline defaults if the library is somehow unavailable. (As of v3.0.0 this replaces the previously-bundled GuildCache-1.0, which has been retired — see Recent Updates.)

Cross-guild roster sharing (optional)

New in v3.1.0: RosterSync lets confederated guilds share their member lists. Your addon's /who discovery finds an online member of an allied guild and calls lib:RequestRosterSync(peerName) — DeltaSync whispers that peer, compares membership hashes, pulls the roster only if it changed, and feeds it into LibGuildRoster's sister-roster store, all over the existing whisper channels (no extra prefix, no guild broadcast). Entirely opt-in via lib:InitRosterSync(config); addons that never call it are completely unaffected.

Guild-mode for whisper-broken servers (optional)

New in v3.2.0: some private/emulated cores — notably Whitemane — don't deliver addon messages over whisper, which silently breaks the directed query/response/delta channels. Guild-mode is a user-toggled fallback that reroutes those directed channels onto the guild channel, stamping each message with its intended recipient so every other member drops it on receipt — directed behavior over a broadcast transport, no whisper required. Opt-in via lib:InitGuildMode(config) / lib:SetGuildMode(enabled); the host owns the settings toggle, and addons that never enable it are byte-identical to before.

Dependencies

  • Ace3 (required) — provides LibStub, AceComm, AceSerializer
  • AceCommQueue-1.0 (required) — serializes outgoing messages so chunked payloads don't interleave and corrupt each other. Your host addon embeds this alongside Ace3.
  • LibGuildRoster (required) — the guild-roster engine described above. CurseForge installs it automatically when you install DeltaSync.

All three are declared as hard Dependencies: in the TOC, so WoW loads them before DeltaSync.

Quick Start

Basic setup

-- In your addon's OnInitialize / OnEnable:
local AceAddon     = LibStub("AceAddon-3.0")
local AceCommQueue = LibStub("AceCommQueue-1.0")
local DeltaSync    = LibStub("DeltaSync-1.0")

-- 1. Make sure your AceAddon instance has AceComm and AceCommQueue
local MyAddon = AceAddon:NewAddon("MyAddon", "AceComm-3.0")
AceCommQueue:Embed(MyAddon)

-- 2. Create your isolated DeltaSync host (v4.0.0+). HOLD the returned handle —
--    call every DeltaSync method on it, never on the shared LibStub handle.
local host = DeltaSync:NewHost({
    namespace = "MyAddon",
    aceAddon  = MyAddon,   -- REQUIRED — DeltaSync sends through your addon
    onDataReceived = function(sender, data, len)
        -- apply received data or delta
    end,
    onDataRequest = function(sender, baseline)
        -- peer asked for data; call host:SendData(sender, myData)
    end,
})

Sending and receiving

-- Announce your current state to the guild
host:BroadcastVersion(myVersion, myHash)

-- Respond to a data request
host:SendData(sender, myData,  false)   -- full sync
host:SendData(sender, myDelta, true)    -- delta sync

Peer-to-peer catch-up

host:InitP2P({
    getMyHashes     = function()        return myItemHashes           end,
    hasContent      = function(key)     return myData[key] ~= nil     end,
    hasMissingItems = function()        return nextMissingItem ~= nil end,
    onSyncAccepted  = function(key, sender)
        host:RequestData(sender, myBaseline)
    end,
})

host:BroadcastItemHashes(myItemHashes)

Who Should Use This

  • Addons that sync structured data (inventories, rosters, recipes, settings) across guild members
  • Addons where incremental updates are common and full re-syncs are wasteful
  • Addons that want new members to catch up from any peer, not just a single broadcaster
  • Addons that want CRC-level message integrity without writing it themselves

What You Don't Have to Write

  • AceComm prefix registration and routing
  • A wire format that survives partial delivery and chat-throttle chunk interleaving
  • A P2P collect/offer/dispatch pipeline with timeouts and busy fallback
  • Stable content hashing for Lua tables (iteration order is not your friend)
  • An online-member cache that handles connected-realm name suffixes correctly

Recent Updates

v4.0.0 (Current)Multi-host: DeltaSync is no longer a singleton. DeltaSync:NewHost(config) returns an isolated per-host object that owns its own namespace, prefixes, callbacks, peer state, and P2P / RosterSync / guild-mode instances — so multiple consuming addons in one client coexist without clobbering each other's prefixes and callbacks. (Previously the library was a singleton and whichever addon called Initialize last silently took over the rest.) The wire format is unchanged, so a v4.0.0 host interoperates with any existing peer.

  • DeltaSync:NewHost(config) — the new entry point; config is identical in shape to the old Initialize. Hold the returned handle and call every method on it: DS:Method(...) becomes host:Method(...), and DS.p2p:OnItemCompleted(...) becomes host.p2p:OnItemCompleted(...). Delta ops, serialization, InitP2P, RosterSync, guild-mode, and DebugStatus are all called on the host.
  • Backward compatibleDeltaSync:Initialize(config) still works, now as sugar over an implicit "default host", so a single un-migrated consumer is unaffected. Two consumers both on Initialize still share the one default-host slot and clobber each other, so migrate all but at most one consumer to NewHost.
  • Library revision bumped to MINOR 15, and lib.MINOR is now actually set on the handle (previously documented for feature-detection but never assigned). Detect the multi-host API with DeltaSync.NewHost and LibStub("DeltaSync-1.0").MINOR >= 15.

v3.2.1Send-size telemetry: lib:BroadcastData and lib:BroadcastItemHashes now return the serialized payload size (bytes) as an additive second value alongside the existing ok boolean — the same #message metric the receive path already reports — so a host can 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 failure paths still return a single value.

  • Library revision bumped to MINOR 14 — feature-detect with LibStub("DeltaSync-1.0").MINOR >= 14 before reading the new second return value.

v3.2.0Guild-mode: an opt-in, user-toggled mode for private/emulated servers (e.g. Whitemane) that don't deliver addon whispers. It reroutes the five directed channels (QUERY, RESPONSE, DELTA, the directed OFFER reply, HANDSHAKE) from whisper to guild, stamping each directed send with its intended recipient so every other guild member drops it on receipt — identical end behavior to a whisper, with no new prefix and no wire-format change for anyone with it off. Purely additive: a consumer that never calls lib:InitGuildMode is byte-identical to v3.1.0.

  • lib:InitGuildMode(config) / lib:SetGuildMode(enabled) / lib:IsGuildMode() — opt-in init (applies a host-persisted toggle, fires onChanged on every flip) plus the runtime toggle and state query. The host owns the settings checkbox and its persistence; the library ships no UI, SavedVariables, or slash commands.
  • Recipient-stamped directed broadcasts — each directed GUILD send is prefixed with an out-of-band \029<recipient>\029 header (outside the CRC envelope); receivers drop messages addressed to someone else and strip their own before deserializing. \029 never begins an AceSerializer payload, so detection is unambiguous, and the stamp survives AceComm chunking. The recipient is realm-qualified at send time so same-named characters on different connected realms don't both match.
  • Interactions — RosterSync (cross-guild, whisper-only) self-disables while guild-mode is active, and the offline-member send guard applies to directed guild sends too, keeping undeliverable traffic off the shared channel.
  • Library revision bumped to MINOR 13 — feature-detect with LibStub("DeltaSync-1.0").MINOR >= 13 or if lib.InitGuildMode then.

v3.1.0RosterSync: optional cross-guild "sister roster" sharing for confederated guilds, layered on the existing WHISPER channels — no new prefix, no broadcast. A single call, lib:RequestRosterSync(peerName), whispers an online allied-guild member, compares membership hashes (pulling only when they differ), and feeds the result into LibGuildRoster's sister-roster store. Purely additive: a consumer that never calls lib:InitRosterSync is byte-identical to v3.0.0.

  • lib:InitRosterSync(config) / lib:RequestRosterSync(peerName) — opt-in init plus the one call a consumer makes. DeltaSync owns the wire, LibGuildRoster owns the store, and the host owns presence (its /who poll) and persistence. Membership-only, single round trip, provider-authoritative guild key.
  • lib:RegisterLeafType(prefix, handlers) — generic, additive leaf-type router: an optional module claims a leaf type prefix so its directed QUERY/RESPONSE traffic routes to it before the host's data callbacks. Roster traffic coexists with a host's own leaves (e.g. cooldowns/recipes) without either seeing the other's messages; a no-op when nothing registers.
  • Cooperative trust + isolation — a provider serves only its own guild's roster, the receiver rejects a roster whose stamped provider isn't in it, and roster traffic is directed-whisper-only (it never enters the guild-wide P2P offer path).
  • Library revision bumped to MINOR 12 — feature-detect with LibStub("DeltaSync-1.0").MINOR >= 12 or if lib.InitRosterSync then.

v3.0.0 — The bundled GuildCache-1.0 roster library has been retired in favor of LibGuildRoster-1.0 (the standalone LibGuildRoster addon), now a required dependency that CurseForge installs automatically. This is a breaking change for any addon that resolved LibStub("GuildCache-1.0") from DeltaSync's bundle. No wire-format change; the P2P sync protocol is untouched.

  • GuildCache-1.0 removed — DeltaSync no longer registers the GuildCache-1.0 LibStub MAJOR. Roster tracking, name normalization, and online-state come from LibStub("LibGuildRoster-1.0") instead.
  • Consumer migration — Addons that used DeltaSync's GuildCache should switch to LibStub("LibGuildRoster-1.0") and rename IsPlayerOnlineIsOnline and GetOnlineGuildMembersGetOnlineMembers. NormalizeName, GetNormalizedPlayer, GetMember, and the roster callbacks keep their names. Note that IsInGuild is now a strict membership check rather than accept-all on an empty roster.
  • Graceful degradation preserved — DeltaSync feature-detects each library method, so it still falls back to inline defaults (realm derivation, skipped whisper guard, accept-all peers) when the library is absent or older.
  • Library revision bumped to MINOR 11 — Consumers can assert the new layout with LibStub("DeltaSync-1.0").MINOR >= 11.

v2.0.3 — Hash-mismatch offer condition for content-aware-merge consumers. The P2P offer path used to gate on updatedAt > peer.updatedAt, which suppressed legitimate offers from the actual data owner whenever a relayer's updatedAt happened to be higher (e.g. bumped on every RebuildAll even when content didn't change). v2.0.3 flips the condition to "offer whenever our hash differs from the peer's." The wire format is bit-identical to MINOR 8, so older consumers continue to interoperate.

  • Hash-mismatch offer conditionP2PSession:OnHashListReceived now offers data whenever its hash differs from the peer's, regardless of updatedAt. Required by content-aware-merge consumers (TOGProfessionMaster v0.2.0+) that resolve concurrent edits with merge-on-receive instead of last-writer-wins.
  • Candidate sort downgraded to heuristic — The descending-updatedAt insertion sort in 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.
  • Library revision bumped to MINOR 9 — Consumers that need to assert the new offer semantics can check LibStub("DeltaSync-1.0").MINOR >= 9.
  • Compatibility — No API or wire-format changes. Existing consumers (TOGBankClassic, etc.) that don't use the P2P offer path are entirely unaffected. Consumers that do use P2P will simply emit and observe more hash-offers when content actually differs. Adopting the new semantics on the receive side requires implementing content-aware merge in onDataReceived (max-wins for monotonic fields, union for sets).

v2.0.2 — GuildCache-1.0 gains CallbackHandler-1.0 events and real-time online/offline tracking via CHAT_MSG_SYSTEM, plus a login-race retry that fixes empty-roster results in the brief window after PLAYER_LOGIN. (GuildCache MINOR 2.)

  • GuildCache callbacks — Six events fire via CallbackHandler-1.0: OnRosterReady, OnRosterUpdated, OnMemberOnline(name), OnMemberOffline(name), OnMemberJoined(name), OnMemberLeft(name). Payloads are canonical "Name-Realm" strings. Subscriptions persist across LibStub upgrades.
  • Real-time CHAT_MSG_SYSTEM parser — Online/offline/join/leave transitions fire immediately on the system message instead of waiting for the next GUILD_ROSTER_UPDATE. The roster-rebuild diff still fires the same callbacks as a catch-up safety net.
  • Login-race retry — When IsInGuild() is true but GetNumGuildMembers() momentarily returns 0 after PLAYER_LOGIN, GuildCache now re-issues RequestGuildRoster() (up to MAX_RETRIES = 5) instead of silently producing an empty roster.
  • Member entry shape extended additively — Entries now include name, rankIndex, and rankName alongside the existing fields. Legacy rank is retained as an alias for rankName so MINOR=1 consumers are unaffected. rankIndex unlocks rank-based peer filtering that custom rank labels can't reliably support.

v2.0.1 — Ships the missing DeltaSyncChannel.lua. v2.0.0 added the integration scaffolding for an optional CHANNEL transport (SendChatMessage send + CHAT_MSG_CHANNEL receive, for working around WoW Classic's silent drop of CHAT_MSG_ADDON on custom channel numbers), but the implementation file itself never made it into the package — any embedder that set channelModule.enabled = true in their Initialize config hit a defensive error at boot. v2.0.1 fixes that.

  • New file DeltaSyncChannel.lua — optional ~160 LOC module loaded after DeltaSync.lua in your TOC. Adds InitChannelModule() (CHAT_MSG_CHANNEL frame setup with self-filter and prefix dispatch into the existing OnComm_* handlers) and SendViaChannel(prefix, body, target) (raw SendChatMessage transport, with a 255-byte cap warning since custom channels don't get AceComm's chunking).
  • Library revision bumped to MINOR 8 — Embedders whose private copies were on MINOR 7 (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.
  • Compatibility — No API or wire-format changes from v2.0.0. Embedders using only GUILD/PARTY/RAID/WHISPER distribution don't need to load the new file at all and see no behavior change.

v2.0.0 — Major release folding in the matured sync engine from TOGProfessionMaster and splitting the guild-roster cache into its own companion library.

  • GuildCache extracted into GuildCache-1.0 — Roster and name-normalization methods moved from the DeltaSync handle to their own LibStub library at Libs/GuildCache-1.0/. Other addons can now embed just GuildCache without the full sync stack. This is a breaking change: calls like DeltaSync:NormalizeName(x) become LibStub("GuildCache-1.0"):NormalizeName(x).
  • aceAddon is now a required Initialize() config key — DeltaSync routes sends through your AceAddon's SendCommMessage instead of embedding AceComm into itself. This keeps throttling and CRC protection wrapping the correct send path. Existing consumers need a one-line addition: pass your AceAddon:NewAddon(...) handle as config.aceAddon.
  • AceCommQueue moved from library-side to host-side — Your addon embeds AceCommQueue into its own AceAddon handle now. One line: LibStub("AceCommQueue-1.0"):Embed(MyAddon).
  • Cross-realm name normalization fixed — Connected-realm peers whose name arrives with a foreign realm suffix are no longer silently rewritten to the local realm. Their per-realm data stays separate where it should.
  • New debug APIslib:DebugStatus() prints a diagnostic block showing namespace, library revision, registered prefixes, and wiring state. Useful when sends are silently dropping and you need to confirm which config key is missing.

v1.0.0 — First stable release. P2P session hardening, integrity checks on every channel, and a whisper-online guard so offline members stop eating send slots.

  • DELIVERY_TIMEOUT added so a peer that accepts a sync-request and then goes silent no longer pins an inbound session slot forever
  • All seven channel types (not just DATA) now use the CRC + stop-marker wire format
  • Whisper sends now skip guild members the roster knows are offline
  • Several P2P state-machine edge cases fixed where late messages could mutate unrelated sessions

Bug Reports & Feedback

Found a bug or have a suggestion? Reach out on Discord.

Credits

  • Pimptasty — Author

Special Thanks:

  • The Old Gods guild community for real-world testing via TOGBankClassic and TOGProfessionMaster
  • Ace3 library maintainers for the excellent framework
  • AceCommQueue-1.0 for solving the chunk-interleave CRC problem

License

DeltaSync is open-source software. See the LICENSE file for details.

If your addon shares data across guild members — inventories, crafting recipes, raid rosters, settings — you've probably run into some combination of:

  • Full re-sends every time anything changes, hammering the guild channel
  • CRC errors under load when several addons compete for chat throttle slots
  • New members joining and having no idea who to ask for a data catch-up
  • Home-rolled hashing that disagrees on tables because Lua iteration order is unstable
  • One slow peer freezing everyone else because there's no timeout on expected data

DeltaSync solves all of these. Drop it in, wire up a handful of callbacks, and you get battle-tested sync behavior that's been running in production on live Classic Era guilds.

What You Get

Bandwidth-efficient sync

Instead of re-sending everything whenever something changes, DeltaSync computes and transmits only the differences between states. In practice this cuts sync traffic by 90-99% for incremental updates. Full sync is always available as a fallback for new members or major changes.

Peer-to-peer catch-up

When a player logs in missing data, DeltaSync broadcasts a summary of what they have and lets any peer with fresher data step up. No single "banker" bottleneck, no manual request dance. If the first peer is busy, it politely declines and the next best peer takes over automatically.

Message integrity you can trust

Every structured message is wrapped with a checksum and a stop-marker. On the receive side, truncation and corruption are detected separately — so if a message got mangled mid-flight, you find out instead of silently applying garbage. Corrupt messages are logged with diagnostic info, not quietly dropped.

A fair share of your addon-message budget

WoW caps each addon at 16 communication prefixes. DeltaSync uses 7 of yours (one per channel type), leaving 9 for your own use. Prefixes are auto-derived from your addon's name so you don't have to pick unique strings yourself.

Guild roster tracking via LibGuildRoster-1.0

DeltaSync uses LibGuildRoster-1.0 — the standalone LibGuildRoster addon — to track who's in your guild and who's online, so it can avoid whispering offline members and default its peer-eligibility check. It's a required dependency that CurseForge installs automatically alongside DeltaSync; DeltaSync also feature-detects each method, so it degrades to reasonable inline defaults if the library is somehow unavailable. (As of v3.0.0 this replaces the previously-bundled GuildCache-1.0, which has been retired — see Recent Updates.)

Cross-guild roster sharing (optional)

New in v3.1.0: RosterSync lets confederated guilds share their member lists. Your addon's /who discovery finds an online member of an allied guild and calls lib:RequestRosterSync(peerName) — DeltaSync whispers that peer, compares membership hashes, pulls the roster only if it changed, and feeds it into LibGuildRoster's sister-roster store, all over the existing whisper channels (no extra prefix, no guild broadcast). Entirely opt-in via lib:InitRosterSync(config); addons that never call it are completely unaffected.

Guild-mode for whisper-broken servers (optional)

New in v3.2.0: some private/emulated cores — notably Whitemane — don't deliver addon messages over whisper, which silently breaks the directed query/response/delta channels. Guild-mode is a user-toggled fallback that reroutes those directed channels onto the guild channel, stamping each message with its intended recipient so every other member drops it on receipt — directed behavior over a broadcast transport, no whisper required. Opt-in via lib:InitGuildMode(config) / lib:SetGuildMode(enabled); the host owns the settings toggle, and addons that never enable it are byte-identical to before.

Dependencies

  • Ace3 (required) — provides LibStub, AceComm, AceSerializer
  • AceCommQueue-1.0 (required) — serializes outgoing messages so chunked payloads don't interleave and corrupt each other. Your host addon embeds this alongside Ace3.
  • LibGuildRoster (required) — the guild-roster engine described above. CurseForge installs it automatically when you install DeltaSync.

All three are declared as hard Dependencies: in the TOC, so WoW loads them before DeltaSync.

Quick Start

Basic setup

-- In your addon's OnInitialize: local AceAddon = LibStub("AceAddon-3.0") local AceCommQueue = LibStub("AceCommQueue-1.0") local DeltaSync = LibStub("DeltaSync-1.0")

-- 1. Make sure your AceAddon instance has AceComm and AceCommQueue local MyAddon = AceAddon:NewAddon("MyAddon", "AceComm-3.0") AceCommQueue:Embed(MyAddon)

-- 2. Hand your AceAddon handle to DeltaSync DeltaSync:Initialize({ namespace = "MyAddon", aceAddon = MyAddon, -- REQUIRED — DeltaSync sends through your addon onDataReceived = function(sender, data) -- apply received data or delta end, onDataRequest = function(sender, baseline) -- peer asked for data; call DeltaSync:SendData(sender, myData) end, })

Sending and receiving

-- Announce your current state to the guild DeltaSync:BroadcastVersion(myVersion, myHash)

-- Respond to a data request DeltaSync:SendData(sender, myData, false) -- full sync DeltaSync:SendData(sender, myDelta, true) -- delta sync

Peer-to-peer catch-up

DeltaSync:InitP2P({ getMyHashes = function() return myItemHashes end, hasContent = function(key) return myData[key] ~= nil end, hasMissingItems = function() return nextMissingItem ~= nil end, onSyncAccepted = function(key, sender) DeltaSync:RequestData(sender, myBaseline) end, })

DeltaSync:BroadcastItemHashes(myItemHashes)

Who Should Use This

  • Addons that sync structured data (inventories, rosters, recipes, settings) across guild members
  • Addons where incremental updates are common and full re-syncs are wasteful
  • Addons that want new members to catch up from any peer, not just a single broadcaster
  • Addons that want CRC-level message integrity without writing it themselves

What You Don't Have to Write

  • AceComm prefix registration and routing
  • A wire format that survives partial delivery and chat-throttle chunk interleaving
  • A P2P collect/offer/dispatch pipeline with timeouts and busy fallback
  • Stable content hashing for Lua tables (iteration order is not your friend)
  • An online-member cache that handles connected-realm name suffixes correctly

Recent Updates

v3.2.1 (Current)Send-size telemetry: lib:BroadcastData and lib:BroadcastItemHashes now return the serialized payload size (bytes) as an additive second value alongside the existing ok boolean — the same #message metric the receive path already reports — so a host can 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 failure paths still return a single value.

  • Library revision bumped to MINOR 14 — feature-detect with LibStub("DeltaSync-1.0").MINOR >= 14 before reading the new second return value.

v3.2.0Guild-mode: an opt-in, user-toggled mode for private/emulated servers (e.g. Whitemane) that don't deliver addon whispers. It reroutes the five directed channels (QUERY, RESPONSE, DELTA, the directed OFFER reply, HANDSHAKE) from whisper to guild, stamping each directed send with its intended recipient so every other guild member drops it on receipt — identical end behavior to a whisper, with no new prefix and no wire-format change for anyone with it off. Purely additive: a consumer that never calls lib:InitGuildMode is byte-identical to v3.1.0.

  • lib:InitGuildMode(config) / lib:SetGuildMode(enabled) / lib:IsGuildMode() — opt-in init (applies a host-persisted toggle, fires onChanged on every flip) plus the runtime toggle and state query. The host owns the settings checkbox and its persistence; the library ships no UI, SavedVariables, or slash commands.
  • Recipient-stamped directed broadcasts — each directed GUILD send is prefixed with an out-of-band \029<recipient>\029 header (outside the CRC envelope); receivers drop messages addressed to someone else and strip their own before deserializing. \029 never begins an AceSerializer payload, so detection is unambiguous, and the stamp survives AceComm chunking. The recipient is realm-qualified at send time so same-named characters on different connected realms don't both match.
  • Interactions — RosterSync (cross-guild, whisper-only) self-disables while guild-mode is active, and the offline-member send guard applies to directed guild sends too, keeping undeliverable traffic off the shared channel.
  • Library revision bumped to MINOR 13 — feature-detect with LibStub("DeltaSync-1.0").MINOR >= 13 or if lib.InitGuildMode then.

v3.1.0RosterSync: optional cross-guild "sister roster" sharing for confederated guilds, layered on the existing WHISPER channels — no new prefix, no broadcast. A single call, lib:RequestRosterSync(peerName), whispers an online allied-guild member, compares membership hashes (pulling only when they differ), and feeds the result into LibGuildRoster's sister-roster store. Purely additive: a consumer that never calls lib:InitRosterSync is byte-identical to v3.0.0.

  • lib:InitRosterSync(config) / lib:RequestRosterSync(peerName) — opt-in init plus the one call a consumer makes. DeltaSync owns the wire, LibGuildRoster owns the store, and the host owns presence (its /who poll) and persistence. Membership-only, single round trip, provider-authoritative guild key.
  • lib:RegisterLeafType(prefix, handlers) — generic, additive leaf-type router: an optional module claims a leaf type prefix so its directed QUERY/RESPONSE traffic routes to it before the host's data callbacks. Roster traffic coexists with a host's own leaves (e.g. cooldowns/recipes) without either seeing the other's messages; a no-op when nothing registers.
  • Cooperative trust + isolation — a provider serves only its own guild's roster, the receiver rejects a roster whose stamped provider isn't in it, and roster traffic is directed-whisper-only (it never enters the guild-wide P2P offer path).
  • Library revision bumped to MINOR 12 — feature-detect with LibStub("DeltaSync-1.0").MINOR >= 12 or if lib.InitRosterSync then.

v3.0.0 — The bundled GuildCache-1.0 roster library has been retired in favor of LibGuildRoster-1.0 (the standalone LibGuildRoster addon), now a required dependency that CurseForge installs automatically. This is a breaking change for any addon that resolved LibStub("GuildCache-1.0") from DeltaSync's bundle. No wire-format change; the P2P sync protocol is untouched.

  • GuildCache-1.0 removed — DeltaSync no longer registers the GuildCache-1.0 LibStub MAJOR. Roster tracking, name normalization, and online-state come from LibStub("LibGuildRoster-1.0") instead.
  • Consumer migration — Addons that used DeltaSync's GuildCache should switch to LibStub("LibGuildRoster-1.0") and rename IsPlayerOnlineIsOnline and GetOnlineGuildMembersGetOnlineMembers. NormalizeName, GetNormalizedPlayer, GetMember, and the roster callbacks keep their names. Note that IsInGuild is now a strict membership check rather than accept-all on an empty roster.
  • Graceful degradation preserved — DeltaSync feature-detects each library method, so it still falls back to inline defaults (realm derivation, skipped whisper guard, accept-all peers) when the library is absent or older.
  • Library revision bumped to MINOR 11 — Consumers can assert the new layout with LibStub("DeltaSync-1.0").MINOR >= 11.

v2.0.3 — Hash-mismatch offer condition for content-aware-merge consumers. The P2P offer path used to gate on updatedAt > peer.updatedAt, which suppressed legitimate offers from the actual data owner whenever a relayer's updatedAt happened to be higher (e.g. bumped on every RebuildAll even when content didn't change). v2.0.3 flips the condition to "offer whenever our hash differs from the peer's." The wire format is bit-identical to MINOR 8, so older consumers continue to interoperate.

  • Hash-mismatch offer conditionP2PSession:OnHashListReceived now offers data whenever its hash differs from the peer's, regardless of updatedAt. Required by content-aware-merge consumers (TOGProfessionMaster v0.2.0+) that resolve concurrent edits with merge-on-receive instead of last-writer-wins.
  • Candidate sort downgraded to heuristic — The descending-updatedAt insertion sort in 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.
  • Library revision bumped to MINOR 9 — Consumers that need to assert the new offer semantics can check LibStub("DeltaSync-1.0").MINOR >= 9.
  • Compatibility — No API or wire-format changes. Existing consumers (TOGBankClassic, etc.) that don't use the P2P offer path are entirely unaffected. Consumers that do use P2P will simply emit and observe more hash-offers when content actually differs. Adopting the new semantics on the receive side requires implementing content-aware merge in onDataReceived (max-wins for monotonic fields, union for sets).

v2.0.2 — GuildCache-1.0 gains CallbackHandler-1.0 events and real-time online/offline tracking via CHAT_MSG_SYSTEM, plus a login-race retry that fixes empty-roster results in the brief window after PLAYER_LOGIN. (GuildCache MINOR 2.)

  • GuildCache callbacks — Six events fire via CallbackHandler-1.0: OnRosterReady, OnRosterUpdated, OnMemberOnline(name), OnMemberOffline(name), OnMemberJoined(name), OnMemberLeft(name). Payloads are canonical "Name-Realm" strings. Subscriptions persist across LibStub upgrades.
  • Real-time CHAT_MSG_SYSTEM parser — Online/offline/join/leave transitions fire immediately on the system message instead of waiting for the next GUILD_ROSTER_UPDATE. The roster-rebuild diff still fires the same callbacks as a catch-up safety net.
  • Login-race retry — When IsInGuild() is true but GetNumGuildMembers() momentarily returns 0 after PLAYER_LOGIN, GuildCache now re-issues RequestGuildRoster() (up to MAX_RETRIES = 5) instead of silently producing an empty roster.
  • Member entry shape extended additively — Entries now include name, rankIndex, and rankName alongside the existing fields. Legacy rank is retained as an alias for rankName so MINOR=1 consumers are unaffected. rankIndex unlocks rank-based peer filtering that custom rank labels can't reliably support.

v2.0.1 — Ships the missing DeltaSyncChannel.lua. v2.0.0 added the integration scaffolding for an optional CHANNEL transport (SendChatMessage send + CHAT_MSG_CHANNEL receive, for working around WoW Classic's silent drop of CHAT_MSG_ADDON on custom channel numbers), but the implementation file itself never made it into the package — any embedder that set channelModule.enabled = true in their Initialize config hit a defensive error at boot. v2.0.1 fixes that.

  • New file DeltaSyncChannel.lua — optional ~160 LOC module loaded after DeltaSync.lua in your TOC. Adds InitChannelModule() (CHAT_MSG_CHANNEL frame setup with self-filter and prefix dispatch into the existing OnComm_* handlers) and SendViaChannel(prefix, body, target) (raw SendChatMessage transport, with a 255-byte cap warning since custom channels don't get AceComm's chunking).
  • Library revision bumped to MINOR 8 — Embedders whose private copies were on MINOR 7 (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.
  • Compatibility — No API or wire-format changes from v2.0.0. Embedders using only GUILD/PARTY/RAID/WHISPER distribution don't need to load the new file at all and see no behavior change.

v2.0.0 — Major release folding in the matured sync engine from TOGProfessionMaster and splitting the guild-roster cache into its own companion library.

  • GuildCache extracted into GuildCache-1.0 — Roster and name-normalization methods moved from the DeltaSync handle to their own LibStub library at Libs/GuildCache-1.0/. Other addons can now embed just GuildCache without the full sync stack. This is a breaking change: calls like DeltaSync:NormalizeName(x) become LibStub("GuildCache-1.0"):NormalizeName(x).
  • aceAddon is now a required Initialize() config key — DeltaSync routes sends through your AceAddon's SendCommMessage instead of embedding AceComm into itself. This keeps throttling and CRC protection wrapping the correct send path. Existing consumers need a one-line addition: pass your AceAddon:NewAddon(...) handle as config.aceAddon.
  • AceCommQueue moved from library-side to host-side — Your addon embeds AceCommQueue into its own AceAddon handle now. One line: LibStub("AceCommQueue-1.0"):Embed(MyAddon).
  • Cross-realm name normalization fixed — Connected-realm peers whose name arrives with a foreign realm suffix are no longer silently rewritten to the local realm. Their per-realm data stays separate where it should.
  • New debug APIslib:DebugStatus() prints a diagnostic block showing namespace, library revision, registered prefixes, and wiring state. Useful when sends are silently dropping and you need to confirm which config key is missing.

v1.0.0 — First stable release. P2P session hardening, integrity checks on every channel, and a whisper-online guard so offline members stop eating send slots.

  • DELIVERY_TIMEOUT added so a peer that accepts a sync-request and then goes silent no longer pins an inbound session slot forever
  • All seven channel types (not just DATA) now use the CRC + stop-marker wire format
  • Whisper sends now skip guild members the roster knows are offline
  • Several P2P state-machine edge cases fixed where late messages could mutate unrelated sessions

Bug Reports & Feedback

Found a bug or have a suggestion? Reach out on Discord.

Credits

  • Pimptasty — Author

Special Thanks:

  • The Old Gods guild community for real-world testing via TOGBankClassic and TOGProfessionMaster
  • Ace3 library maintainers for the excellent framework
  • AceCommQueue-1.0 for solving the chunk-interleave CRC problem

License

DeltaSync is open-source software. See the LICENSE file for details.

The DeltaSync Team

Forgeborn tier frameprofile avatar
  • 2
    Followers
  • 27
    Projects
  • 269.0K
    Downloads
Donate

More from PmptastyView all

  • BijouRR - Revived project image

    BijouRR - Revived

    A Bijou and Scarab auto rolling tool for ZG and AQ20 raids.

    • 1.9K
    • July 22, 2026
  • TOG Bank Classic project image

    TOG Bank Classic

    The Old Gods' version of GBankClassic-Revived - with in-game item requests.

    • 12.2K
    • July 22, 2026
  • TOG Tools project image

    TOG Tools

    ToGTools is a convenient place to aggregate one off tools that don't make sense to have it's own addon.

    • 294
    • July 21, 2026
  • Dibs project image

    Dibs

    A master‑loot tool for Classic Era raids: everyone calls dibs on the gear they want before the raid, so when an item drops the masterlooter instantly sees who's competing — for fast, fair, drama‑free distribution. Works for pugs too.

    • 32
    • July 19, 2026
  • BijouRR - Revived project image

    BijouRR - Revived

    A Bijou and Scarab auto rolling tool for ZG and AQ20 raids.

    • 1.9K
    • July 22, 2026
  • TOG Bank Classic project image

    TOG Bank Classic

    The Old Gods' version of GBankClassic-Revived - with in-game item requests.

    • 12.2K
    • July 22, 2026
  • TOG Tools project image

    TOG Tools

    ToGTools is a convenient place to aggregate one off tools that don't make sense to have it's own addon.

    • 294
    • July 21, 2026
  • Dibs project image

    Dibs

    A master‑loot tool for Classic Era raids: everyone calls dibs on the gear they want before the raid, so when an item drops the masterlooter instantly sees who's competing — for fast, fair, drama‑free distribution. Works for pugs too.

    • 32
    • July 19, 2026