Repository navigation
Addon snapshot contract SavedVariables
Generated from
docs/reference/addon-savedvariables.md. Edit that file in the repository: changes made in the wiki are overwritten.
The RaidManager addon for World of Warcraft 3.3.5a writes what it observes about each character the player logs
into to WTF/Account/<ACCOUNT>/SavedVariables/RaidManager.lua. The desktop companion reads that file and uploads
it, and the API turns it into character facts. This page is the contract between the three: the addon writes exactly
this shape, and the companion and the API accept it. It is schema version 1.
The addon has no network access and never stores the website's authentication secret or any other credential
(story #16). WoW writes the file on /reload, logout or client exit, never while the player plays.
-
Values: only strings, numbers, booleans and tables; no functions and no shared or cyclic tables.
-
Raw game values: the addon stores what the game returns (ids, item strings, tokens, localized names, seconds), never a judgment such as a GearScore, a role, an instance mapping or an eligibility. RaidManager derives those.
-
One snapshot per character: a new capture replaces the character's sections; the file keeps no history.
-
Observed or unavailable: every section is a table with a
status:-
"observed": the game answered. The section hasobservedAtand its data. An observed absence is explicit, such as an emptyitemslist,empty = trueon a gear slot orinGuild = false. -
"unavailable": the game didn't answer, or the data can't be read now. The section has no data, and may havereasonandattemptedAt. The addon givesreason = "not-captured"for a section its version doesn't capture yet.
A section is never left out to mean "none", and an unavailable value is never written as an empty one.
-
-
Times:
observedAtandattemptedAtaretime()on the player's computer, in seconds since 1970 (UTC).serverTimerecords the game server's clock at the same moment, so RaidManager can detect a wrong local clock. -
Versions:
schemaVersionchanges with any change to this shape. Removing or renaming a field is a breaking change and needs a new version, this page, a changelog entry and the companion change in the same release.
RaidManagerDB = {
["schemaVersion"] = 1,
["addonVersion"] = "0.1.0",
["characters"] = {
["Icecrown|Arthasdk"] = { --[[ one character snapshot ]] },
},
}| Field | Type | Meaning |
|---|---|---|
schemaVersion |
number | The version of this contract: 1. |
addonVersion |
string | The addon's version from its TOC. |
characters |
table | One snapshot per character, keyed "<realm>|<name>" with the names as the game returns them, in UTF-8. |
| Field | Type | Meaning |
|---|---|---|
realm |
string |
GetRealmName(), such as "Icecrown". |
name |
string |
UnitName("player"). |
capturedAt |
number |
time() when the addon last wrote this snapshot. |
client |
table |
locale (GetLocale(), such as "enUS") and build ("12340"), to map localized names. |
serverTime |
table |
hour and minute from GetGameTime(), with observedAt. |
identity |
section | Who the character is. |
guild |
section | The character's guild. |
professions |
section | Primary and secondary professions. |
equipped |
section | The gear the character wears. |
equipmentSets |
section | Saved equipment sets: the character's loadouts. |
talents |
section | Talent groups (dual specialization) with points, ranks and glyphs. |
lockouts |
section | Saved instances, raids included, with reset data. |
| Field | Type | Meaning |
|---|---|---|
guid |
string |
UnitGUID("player"), a hex string such as "0x0700000001A2B3C4". |
level |
number |
UnitLevel("player"). |
class |
string | The class token from UnitClass("player"), such as "DEATHKNIGHT". |
race |
string | The race token from UnitRace("player"), such as "Scourge". |
faction |
string |
"Alliance" or "Horde". |
sex |
number |
UnitSex("player"): 2 male, 3 female. |
| Field | Type | Meaning |
|---|---|---|
inGuild |
boolean |
false when the character is in no guild; then no other field is present. |
name |
string | The guild name. |
rank |
string | The character's rank name. |
rankIndex |
number | The rank index, 0 for the guild master. |
GetGuildInfo("player") can return nothing shortly after login: the section is then "unavailable" with
reason = "not-loaded" until PLAYER_GUILD_UPDATE answers.
items lists the skill lines under the professions and secondary skills headers, in the game's order.
| Field | Type | Meaning |
|---|---|---|
items[].name |
string | The localized profession name, such as "Tailoring". |
items[].header |
string | The localized header it was listed under, such as "Professions" or "Secondary Skills". |
items[].rank |
number | The current skill. |
items[].maxRank |
number | The maximum skill of the character's current training. |
A character with no profession has items = {}. The headers are found by the client's localized names
(TRADE_SKILLS and SECONDARY_SKILLS, without a final colon). The section is "unavailable" with
reason = "collapsed" when the player collapsed one of these headers in the skill list, which hides its rows, and
with reason = "not-loaded" while the skill list is empty.
slots lists the inventory slots 1 to 19 in order, one entry each.
| Field | Type | Meaning |
|---|---|---|
slots[].slot |
number | The inventory slot, 1 (head) to 19 (tabard); 4 is the shirt. |
slots[].empty |
boolean |
true when nothing is equipped there; then no item field is present. |
slots[].status |
string |
"unavailable" when the slot holds an item the game didn't describe; then no item field is present. |
slots[].itemString |
string | The item string from the link, "item:itemId:enchantId:gem1:gem2:gem3:gem4:suffixId:uniqueId:level", where gem1 to gem4 are gem enchantment ids. |
slots[].itemId |
number | The item id. |
slots[].enchantId |
number | The enchant id, 0 when none. |
slots[].gems |
table | The four gem enchantment ids in socket order, such as 3628, 0 for an empty or missing socket. They aren't the gems' item ids: RaidManager maps them to gems. |
slots[].suffixId |
number | The random suffix id; it can be negative. |
slots[].uniqueId |
number | The unique id. |
A slot is "unavailable" with reason = "not-cached" when the game shows an item there but gives no link yet, and
with reason = "unreadable-link" when the link holds no complete item string.
items lists the equipment sets saved in the game's equipment manager. A character with none has items = {}.
| Field | Type | Meaning |
|---|---|---|
items[].name |
string | The set's name, such as "Unholy". |
items[].icon |
string | The set's icon texture path. |
items[].slots[] |
table | One entry per slot 1 to 19, with the fields of equipped, plus the ones below. |
items[].slots[].ignored |
boolean |
true when the set ignores the slot. |
items[].slots[].location |
string | Where the item is: "equipped", "bags" or "bank". |
A set item in the bank is only readable while the bank is open: its slot is status = "unavailable" with
reason = "in-bank", location = "bank" and the itemId the set records. A slot is also unavailable when the
set's item is nowhere on the character (reason = "missing"), with itemId only when the game gives the item's id
(Warmane's client gives -1, which isn't kept), or when its link can't be read (reason = "not-cached", with the
itemId). The section is "unavailable" with reason = "not-loaded" while the game hasn't loaded a set's name.
| Field | Type | Meaning |
|---|---|---|
activeGroup |
number |
GetActiveTalentGroup(): 1 or 2. |
groups[] |
table | One entry per talent group, 1 to GetNumTalentGroups(). |
groups[].group |
number | The talent group. |
groups[].tabs[] |
table | One entry per talent tree, in the game's order. |
groups[].tabs[].name |
string | The localized tree name, such as "Blood". |
groups[].tabs[].pointsSpent |
number | The points spent in the tree. |
groups[].tabs[].ranks |
string | One digit per talent of the tree, in GetTalentInfo index order: its current rank. |
groups[].glyphs[] |
table | One entry per glyph socket, 1 to GetNumGlyphSockets(). |
groups[].glyphs[].socket |
number | The socket. |
groups[].glyphs[].type |
number |
1 major, 2 minor. |
groups[].glyphs[].enabled |
boolean | Whether the socket is unlocked at the character's level. |
groups[].glyphs[].empty |
boolean |
true when an unlocked socket has no glyph. |
groups[].glyphs[].spellId |
number | The spell id of the glyph. |
The section is "unavailable" with reason = "not-loaded" while the game hasn't loaded the talent trees.
| Field | Type | Meaning |
|---|---|---|
complete |
boolean |
true when every saved instance the game counted was read. Only a complete scan replaces a character's raid saves; an incomplete one is kept as evidence without erasing them. |
items[] |
table | One entry per saved instance, raids and dungeons, in GetSavedInstanceInfo order. |
items[].name |
string | The localized instance name, such as "Icecrown Citadel". |
items[].lockoutId |
number | The lower part of the lockout id. |
items[].idMostSig |
number | The upper part of the lockout id; RaidManager combines the two. |
items[].resetSeconds |
number | Seconds until the reset, counted from the section's observedAt. |
items[].difficulty |
number | The game's raw difficulty, read with maxPlayers: for a raid with a 10-player and a 25-player size, 1 10 normal, 2 25 normal, 3 10 heroic, 4 25 heroic; a raid with one size reports 1 whatever its size, such as Gruul's Lair with 25 players or Molten Core with 40. |
items[].difficultyName |
string | The localized difficulty name. |
items[].maxPlayers |
number | The instance size. |
items[].isRaid |
boolean |
true for a raid. |
items[].locked |
boolean |
false for an expired save the player can still extend. |
items[].extended |
boolean |
true when the player extended the save. |
A character with no save has complete = true and items = {}. The addon asks the game with RequestRaidInfo when
the character enters the world, on a new zone and on /rm sync, at most once every five seconds, and reads the saves
when UPDATE_INSTANCE_INFO answers. Until a first answer, the section is "unavailable" with
reason = "not-answered"; after one, an earlier answer stays until the next arrives, and its observedAt tells its
age.
The companion uploads each character snapshot of the file to the API as JSON with the same field names, one character
per request; Character snapshots says what RaidManager keeps from it. The API
reads equipmentSets but doesn't import it, and keeps no guid.
How the companion reads and uploads the file (#550):
-
Folders: at its first start it searches each fixed drive's root and its folders down to three levels for an
installation, a folder with
Wow.exebesideWTF\Account, skipping system folders such asWindowsandUsers. Each folder underWTF\Accountis an account; the player can exclude one, which also drops its waiting snapshots, and can add a folder holdingWTF\Accountwith Choose folders. -
Reading: the companion parses the file as data and never runs it. It checks every watched file each five
seconds and reads a change only when the next check finds the same size and time and the file can be opened, so it
never reads a file WoW is still writing. Only
schemaVersion1 is read. -
A cut file: the characters written before the cut upload. The cut character shows as waiting for WoW, and once
the file has stayed the same for 30 seconds, as an incomplete snapshot the player fixes with
/reloador a logout. -
Queue: waiting snapshots are kept in
%LOCALAPPDATA%\RaidManager\sync-queue.json, keyed by realm, name andcapturedAt; a newer snapshot of a character replaces the waiting one, and one as old as the last accepted isn't queued again. It also keeps each character's latest accepted snapshot and when it was accepted, so after a restart the sync screen still shows the last success and the recent uploads (#592). The file keeps a hash of the account folder, never its name. -
Answers: 202 and 200 remove the snapshot; 400 drops it and shows the refusal. A network failure, a timeout or a
server error retries after 5 seconds, doubling to at most 5 minutes, each wait up to a fifth longer at random; 429
waits for
Retry-After(a minute without it); 401 stops uploads until the companion is paired again. - Pause: the companion keeps reading and queuing but uploads nothing; resuming uploads the queue at once.
Combat statistics (UnitStat and the like), per-encounter progress and the bank's contents aren't captured: the
stories of this release don't need them, and statistics depend on auras at the moment of capture. Adding one is a
new schema version.
The fixtures in test/Fixtures/Addon/SavedVariables/ are files in the shape WoW writes, each under the folder tree a
player's installation has. The addon's tests, the companion and the API parse them. The game indents with tabs and
the fixtures with spaces, so a parser must not depend on spacing or on the -- [n] comments after list entries.
| Fixture | What it shows |
|---|---|
one-character |
One account, one character, every section observed: full gear with enchants, gems and a negative suffix, two talent groups with glyphs and an empty socket, two equipment sets, a guild, and observed empty saves. |
multiple-accounts |
Two account folders and three characters on two realms, one with an accented letter in its name, one in no guild, one with no profession and no equipment set. |
incomplete-scan |
A character captured right after login: guild and saves unavailable, one gear slot unavailable, a set item in the bank, an incomplete second capture of saves. |
raid-save |
Raid saves with reset data: a locked 25-player save, an extended 10-player heroic save, an expired save that can still be extended, a dungeon save, and a lockout id with an upper part. |
- Product scope
- Architecture
- Visual guide
- Diagram maintenance
- Review a pull request
- Design a screen
- Test the addon
- Set up Discord
- Provision the server
- Glossary
- API contracts
- Addon snapshot contract
- Project automation
- Work management specification
- Warmane lockout evidence
- Planning
-
Decisions
- Index
- Foundation and authentication
- Character synchronization
- Documentation publication
- Protected automatic squash merges
- Viewed files before approval
- Operator review before a peer
- Auto-merge when ready
- Merge with the workflow token
- Meaningful review comments
- Read queries from the write database
- Website session and API trust
- Interactive Server rendering with Radzen
- Scalar API reference
- GitHub Wiki mirror
- Coverage gate
- Work item hierarchy
- Penpot mockups
- Generated Penpot mockups
- Dark design system
- SonarCloud findings
- Addon roster import
- Discord role checks
- Discord servers at sign-in
- Community roles
- Work item classification
- Work management specification adoption
- OVH test environment
- Environment secrets
- PostgreSQL
- Companion pairing
- WoW addon in Lua
- Windows companion in Avalonia
- Companion in the Microsoft Store
- Workflows on a self-hosted runner