ItemDB-v1.6.0
What's new
Changelog
[v1.6.0] - 2026-10-17
The first four sections below ship (a second Vanilla BiS provider; threat and armour
reduction priced in EP -- both MINOR 45; on-use rating trinkets priced from TBC on; IsBiS
returning a fresh list); the rest are under tools/ or Tests/, which the packager does not
ship, plus .pkgmeta (packaging only) and README.md.
Vanilla: a second BiS provider, "bisclassic", through phase 6 (MINOR 44 -> 45)
Data/Vanilla/_core/BiSClassic.luaregisterslib:LoadBiS("bisclassic", ...): 623 sets for 9 classes, phases 0-6, including the fire / nature / shadow / frost / arcane resistance lists. Thewowsimsprovider stops early (hunter at phase 1, mage and shadow priest at 1, rogue at 2, warlock at 0 + 2, warrior tank at 1; WoWSims master checked 2026-10-16), so nothing in the library answered "what is BiS in AQ40 / Naxx" for most classes (Dibs thread 000389037f53, the Badge of the Swarmguard report).wowsimsstays the default:ItemDB.tocloads it first.- Source: BestInSlotClassic (GPL-2.0, github.com/Warkdev/BestInSlotClassic) at commit
302ab4ecca0c, cached by the new
tools/fetch-bisclassic.py(deskitemdb-fetch, sourcebisclassic) and read by the newtools/build-bis-classic.py. Facts extracted, files not redistributed -- the AtlasLootClassic terms. Its model is a per-class/spec priority ranking plus each item's phase; the builder mirrors itsSearchBis(an item counts from its phase on, lowest priority wins). Choices the source leaves to its UI, made here: phase 0 is phase 1 without raid or world-boss items; a fractional phase (2.5) counts from the next whole phase; PvP-rank gear is left out; random-suffix rankings collapse to the bare item id. - Weapons and races (Peer Review 5ba689c6 B1-B3; the first cut got all three wrong and built
warrior fury phase 1-2 from the Orc main hand 17068 and the Human off hand 18832, a pairing no
race's list gives). Now: ONE SET PER RACE GROUP (the races whose rankings are identical), the
largest group on the plain token and the others suffixed with their race names
(
default_human,default_orc, hunterdefault_dwarf_nightelf); one-hander and two-hander priorities are never compared -- the source numbers them as separate lists -- so a spec itsWEAPON_ICONSoffers both setups gets the one-hand set on its token and the two-hander on<token>_2h(dropped when it ranks no two-hander); rankings are keyed by (item, OffHand), so an item the source ranks as both a main hand and an off hand (19363, 19104, 19170) keeps both.Tests/bis_classic_check.pygains fixtures for each, red-checked by restoring the merge and the item-only key (both went red, then green). - KNOWN, reported to Dibs: Badge of the Swarmguard (21670) is BiS here for Enhancement Shaman
(phases 5-6), not for hunters -- BestInSlotClassic ranks it 12th-13th among hunter trinkets,
behind Drake Fang Talisman and Jom Gabbar.
IsBiS(21670)now answers for the shaman. - CONSUMER-VISIBLE through an unchanged API:
IsBiS(itemID)with nosourcereads every loaded provider, so on Vanilla it now also returnsbisclassichits -- including the resistance lists. A consumer that shows "BiS for ..." from it without a source will show more. Pass"wowsims"to keep the old answer. - Items restricted to one faction (the source's
Factionfield) are not filtered: a set is faction-agnostic, likewowsims'. split-seasonal.pyskipsBiSClassiclikeBiS(it is keyed by class, not item).Tests/bis_classic_check.pypins the selection rule on a synthetic ranking.- Peer Review c9097ad3 B6-B8. New
lib:GetBiSSetSpecs(classID [, source]): the sorted spec tokens a FIXED SET source holds (default: the default set source), so a consumer findsdefault_humanortank_2hwithout rebuilding the naming rule.GetBiSSpecswas the only lister and reads the EP weight scales, which carry none of those tokens; its README row now says so. Same MINOR 45, because 45 is not released yet. READMEIsBiSrow: one hit per spec token per phase, so a warrior item in all three race groups' lists gives three a phase.build-bis-classic.pyrace_rankingsevaluated only the races some row named, so a playable race no row named got no set at all. NewPLAYABLE_RACES(the original-Classic race/class matrix): with an unrestricted row present every playable race is evaluated and an unnamed one gets the unrestricted rows, as the source'sSearchBisgives it; without one, a missing race or a named race the class cannot be is refused. The real cache builds the same 623 sets. Four new fixtures, red-checked by disabling the unnamed-race path (the check went red).
Vanilla and TBC: threat reduction and target-armour reduction priced in EP (MINOR 45)
- Operator, 2026-10-16: "threat reduction is HUGE for DPS classes". Fetish of the Sand Reaver
(21647, -70% threat for 20 s every 3 min), Eye of Diminution (23001, -35% for 20 s every 2 min),
Badge of the Swarmguard (21670) and, on TBC, Icon of Unyielding Courage (28121, ignore 600
armour for 20 s every 2 min) were in
LoadUnscoredProcs, scored 0, and Dibs' list hid them. tools/build-use-effects.py: the on-use path now emitsTHREAT_REDUCTION_PCT/SPELL_THREAT_REDUCTION_PCT(aura MOD_THREAT, sign flipped so a reduction is positive) andTARGET_ARMOR(aura 123 MOD_TARGET_RESISTANCE, negative, armour bit). Newuse_window: an on-use spell whose aura is a proc trigger (the Badge) emits the AVERAGE stack count over its window -- Poisson arrivals at the proc's CMaNGOSspell_proc_eventppm (26480: 15 per minute), capped by the triggered aura'sCumulativeAura-- 661.1 armour, not the 1,200 cap. New pure helpersuse_keys/average_stacks, pinned by the newTests/use_window_check.py(18 assertions, red-checked by restoring the old sign test).Scoring/Vanilla.lua/Scoring/TBC.lua: derived rules for the three keys through the spec's crit weight (1% crit taken as 1% damage, 0.5% for spell crit). Armour:(A + K) / 100points per 1%, VanillaA = 3731,K = 5500(WoWSims Classic level-63 boss and armour constant); TBCA = 7685(Magtheridon),K = 10557.5, through crit rating. Threat 1:1 with damage is an UPPER BOUND, ItemDB's own and stated as such; no community weight exists for it.LibItemDB-1.0.luaeffectWeight: a derived rule may carryexclude = { [classID] = { [spec] = true } }, read fromlib.bisWeightsSpec. Threat reduction excludes every tank and healer spec. A scale not registered throughLoadBiSWeightshas no spec and is not excluded.- Data:
Data/Vanilla/_core/UseEffects.luaandData/TBC/_core/UseEffects.luarebuilt at the builds their siblings carry; the diff is exactly those items leaving the unscored list. The post-buildsplit-seasonal.pyadded its partition marker line toData/Vanilla/_core/ItemPlaces.luaandTokenRewards.lua, which had not been through it before; no row changed. Tests/libitemdb_spec.lua: seven specs on synthetic rows (armour and threat pricing, the tank / healer exclusion, an unregistered scale), red-checked by disabling the exclusion.- Peer Review 917f21ea follow-ups. The exclusion list is ONE table,
lib._noThreatSpecsin the core, which bothScoring/Vanilla.luaandScoring/TBC.luaread (each carried its own copy). New specs: both flavours' threat rules hold that table; every spec it names is a spec the shipped Vanilla, TBC and Forever scales load (red-checked withferal_tnak); and no two specs in any flavour's shipped scale share one table, sincelib.bisWeightsSpecis keyed by table and a shared one would carry the last spec's exclusion. The TBC[2].defaultand[5].defaultscales are byte-identical toholyanddiscipline, so excluding them is correct.Data/Forever/_core/UseEffects.luarebuilt at 1.60.1.69913 (Forever loadsScoring/Vanilla.lua): 15 -> 17 on-use rows, 480 -> 478 unscored. - Peer Review a7919e37. The Badge of the Swarmguard (21670) still scored 0 on Forever:
use_windowread its ppm from CMaNGOS only, and Forever has no CMaNGOS DB. It now falls back to the version's OWN wowsims ppm (by the on-use spell, then by the item; a per-hit chance is not a ppm and is skipped), never Vanilla's CMaNGOS rate, whichbuild-sets.pyremoved as a defect. Forever rebuilt at 1.60.1.69913: 17 -> 18 on-use rows, 478 -> 477 unscored; the Badge prices at 481.3 armour (Vanilla, at CMaNGOS's 15 ppm: 661.1).Tests/use_window_check.pynow also reads Forever's file (26 assertions). New spec, the reverse of the token check: every tank or healer scale in the Vanilla, TBC and Forever weights (Stamina weighted 0.5 or more, or healing 0.5 or more and above spell damage) is inlib._noThreatSpecs. Red-checked by dropping shamantankfrom the list, and the shared-table spec by aliasing one Vanilla rogue scale: exactly those two failed. The_noThreatSpecscomment now names Forever.
TBC to Mists: on-use rating trinkets priced; the Badge scored on Wrath and Mists
build-use-effects.py'suse_keyshad no branch for aura 189 (MOD_RATING), so every on-use buff that grants a combat rating (haste, crit, hit, dodge, armour penetration, mastery ...) fell through to the unscored list on every flavour from TBC on. It now decodes the mask through the shareditemdb_common.rating_keys(misc, version), the tablebuild-effects.pyandbuild-aura-buffs.pyalready use, so an on-use trinket and a food carrying the same bit name the same key. Rebuilt, each at the build its siblings carry: Wrath 115 -> 166 on-use rows (940 -> 889 unscored), Cata 189 -> 273 (1421 -> 1338), Mists 264 -> 399 (1954 -> 1820); TBC now ships 83 on-use rows and 601 unscored. Vanilla and Forever are unchanged (no aura 189 in a 1.x client).- The Badge of the Swarmguard (21670) on Wrath: CMaNGOS wotlk has its ppm (15), but there its stacking aura is +28 armour penetration rating, not -200 target armour, so the missing rating branch was the whole cause. It prices at 92.6 armour penetration rating.
- On Mists: no CMaNGOS DB, and wowsims/mop does not model the Badge.
use_windownow also reads per-spell ppm from Mists' own SkyFire 5.4.8 world DB (mistsskyfire.sqlite, the samespell_proc_eventcolumns), Mists only, so Cata never takes Mists' number. 26480 is ppm 3 there; the stack is +28 crit rating on that client, so the Badge prices at 21 crit rating. Meteorite Crystal (46051, SkyFire ppm 5) also gained a use window: 100 spirit. - On Cata the Badge stays unscored: no world DB, and the cached wowsims/cata source has no Badge model (its only TBC trinket file is commented out).
- Peer Review cc49825a U1/U2: the build log now names the tier each use window took its ppm from
(Forever: wowsims/forever spell 26480, ppm 10, 2.407 stacks), and
use_window's docstring notes that the by-item wowsims tier keeps an item's first proc.Tests/use_window_check.pypins Forever'sm=481.3, the Wrath and Mists Badge rows, and fiveuse_keysaura-189 cases (33 assertions); red-checked by disabling the branch, exactly the three positive decodes failed.
IsBiS returns a fresh list (Peer Review cc49825a I1)
lib:IsBiS(itemID)with no source returned the reverse index's own list, so a consumer that sorted or trimmed the result edited every later answer for that item until the index rebuilt. Both paths now build a new list (the hit records inside are still shared). New spec mutates one result and asserts the next call is unchanged; it failed (3 expected, 4 got) before the fix.
Tools only -- the armor-curve verify says what it skipped (Peer Review 3748bed0)
- My TBC self-audit said shields went unchecked because wago serves no
ItemArmorShieldat 2.5.6.68941. Wrong: no TBC shield carries walk armour (507 of them, the TBC walk's KNOWN COST), soverify_armorskipped every one at itswant <= 0test before the table mattered, and the run printed only exact / low / OVER -- a green line that read as if shields were covered. The verify now prints a "not checked" line: shields and cloth/leather/mail/plate with no walk armour (the latter named, up to 12). TBC at 2.5.6.68941: 507 shields and 6 body pieces skipped (4728, 5091, 19837, 33073-33075 -- ilvl-0 placeholders, a shirt-slot and a ranged-slot piece), 11,362 exact, 0 OVER. The comment that claimed the unchecked count stops a TBC run is corrected: it guards only a version whose walk carries shield armour (Vanilla, Forever, Mists, Retail).verify_armornow returns a third value, the skipped counts. - A hidden item (test / placeholder, never served) that comes out OVER is excused, named and not
failed; hidden items that match still count as exact. Forever's OVER row 16213 is "Test Quality
Modifier Chest", whose armour is whatever a developer typed. (The first cut skipped every hidden
row, which cut TBC's exact matches from 11,362 to 9,769 -- caught in the self-audit.)
hidden_idsmoved frombuild-core-wago.pyintoitemdb_commonso both builders read Hidden.lua one way. - Forever's other OVER row, 273646 "Half-Eaten Boots" (walk 43, curve 143), is now a listed
ARMOR_EXCEPTIONSentry, printed by name on every run. The row is still compared: only an OVER result is excused, and an entry whose row is no longer OVER prints as STALE so it cannot go on excusing nothing. Its cause is NOT VERIFIED: wago's ForeverItemSparseat 1.60.1.69913 has no row for it (itsItemrow is there). A "-100 Armor" bonus would fit the gap exactly. Forever's verify now exits 0 (7,537 exact, 592 low, 0 OVER); Vanilla passes too (6,715 exact, 12 low, 0 OVER). Tests/armor_curve_check.py13 -> 18 checks. The skipped-count check was written first and failed on the old two-value return.
Docs -- the changelog back under GitHub's release-body limit
- CHANGELOG.md was 127,330 characters, over GitHub's 125,000-character release-body limit, so the next release would have been rejected with HTTP 422. v1.1.3, v1.1.2 and v1.1.1 moved unchanged to CHANGELOG_ARCHIVE.md; checked section by section (32 before, 32 after, no word changed).
- markdownlint's line-length rule (MD013) stays off. Turning it on at 80 columns (Peer Review b4eb143b, M1) was tried and reverted on the operator's ruling, 2026-10-17: "there is only one line length rule, you need to move stuff from the changelog into the archive". The one length this repo keeps is the changelog's size, kept by archiving the oldest whole versions. The 80-column reflow tool written for M1 was removed with it.
- The
markdownlintsuite was shown to fail (with MD013 on it exited 1, so the npm shim passes the failure through), and it now lints CHANGELOG_ARCHIVE.md too (Peer Review b4eb143b, M2). - CLAUDE.md: "merge- precedence" corrected to "merge-precedence", and the README rules' closing paragraph ("Write it for a dev...") moved out of the last nested bullet it had been read into.
Tools only -- derive-core-maps.py re-run on Vanilla, TBC and Mists (Peer Review d89d615b)
- First run of
read_walk_coreonitemdb_common.core_rowssince the v1.4.1 refactor: every InventoryType maps to one token on all three versions, matchingitemdb_common.INVTYPE. Every stat id's top key matchesbuild-core-wago.STAT_KEY, orEXTRA_ARMOR/RESIST_STATfor ids 50-56, except Mists ids 11 and 40 (2 items each, no mapping; not investigated). - The structural check now NAMES every item where wago and the walk disagree (the full list, not a truncated one), instead of only counting them. TBC's 2 are the WoW Tokens (122270, 122284), which the client reports as quality 8 with no subclass. Mists has 98 quality, 18 item-level and 7 subclass differences, where the shipped walk value is the client's own answer; nothing shipped changed.
- The comment claiming the true stat key "wins by orders of magnitude" is replaced with what was measured: about 3-4 to 1 for the primary stats, with ratings among the runner-ups on TBC/Mists.
- A stat id with five or fewer hits now lists its items. That named Mists ids 11 and 40: Test
Sword Chest 13712, Test Sword Skill Trinket 33003 and Juno's Test Gem 34967, all in
Data/Mists/_core/Hidden.lua.build-core-wago.pydocuments them as deliberately unmapped. build-core-wago.pygainsverify_structure(): before it writes Wrath/Cata it reports where wago's quality, subclass, equip slot and item level disagree with the TBC and Mists walks, whichverify()(stats only) never compared. Its docstring said the gate compared "every emitted key on every item", which read as covering structure; it now says the gate is stats only. Report-only, because no Wrath/Cata client is on hand to say which side is right;Tests/core_wago_check.pyholds the counts exactly (TBC: the two WoW Tokens; Mists: 98 / 7 / 18), so a new drift goes red. Red-checked by reading item level from the wrong field: both counts failed.- Forever also run: its ItemSparse carries no stat amounts, so nothing matches by value; its core is walk-built, so nothing shipped depends on that.
- Peer Review c53c25e2 nits:
verify_structure's docstring names the equip slot it compares, and the headerbuild-core-wago.pywrites into Wrath/Cata files now says stats are gated and structure only reported. The shipped Data headers pick that up at their next rebuild.
Packaging -- .pkgmeta cleaned, and its checker declared
.pkgmetacarried comment lines, which the no-comments rule forbids; removed. One was# curseforge-project-id: 1558208, which the packager never read:release.shtakes the id from-por the TOC's## X-Curse-Project-ID: 1558208(inItemDB.toc).- Dropped the
"*.sh"and"*.bat"ignore entries; no tracked file matches either. - The harness's
.pkgmetachecker is now the declaredpkgmetasuite in.writ-suites.json. Its WARN [7] (nocurseforge-project-id:) asked for a keyrelease.shdoes not read; reported to WoWAPITesting (inbox d446ea6d), fixed in harness f6e5fde, which reads the TOC id instead.
Docs -- GetTokenRewards named a GetSlot the library does not have (Dibs, inbox 65ade047)
- The comment above
lib:GetTokenRewardsand itsREADME.mdrow told consumers to read each reward's slot withGetSlot. No such method exists, so following that raised a nil-method error. Both now nameGetInfo's 5th return (equipLoc). Comment and docs only.
Tests -- harness pin 407bd26 -> f6e5fde
- Harness f5389cb runs a block's examples after its whole body, as busted does. That left
Tests/pricewindow_spec.lua's file-levelrequire("env.frames")+libs.load("LibAceGUIWidgets-1.0")in place before its two "without the widget library" examples, and both failed. The load now happens once, in the window block's firstbefore_each. Suite 926/926.
[v1.5.0] - 2026-10-16
Added -- a Shaman tank weight scale (Dibs' contract 46c74258)
GetBiSWeights(7, "tank")now resolves on Vanilla, TBC and Forever, in the defaulthawsjonsource. HawsJon publishes no Shaman tank scale and no published Classic one was found, sotools/build-bis-weights.py's newDERIVED_SPECScopies HawsJon's own Protection Paladin scale -- the one published model of a mana-using shield tank whose threat comes from spell damage. Each generatedBiSWeights.luasays in its header that[7].tankis a copy, not HawsJon's, and the builder refuses to copy once HawsJon publishes the spec itself.- The copy drops
DPS_OFFHAND(DERIVED_DROP). A paladin can never dual-wield, so that weight never applied to its own scale; a TBC shaman can (Dual Wield, 30798), and keeping it would let an off-hand weapon compete with a shield on a tank scale. Dropped on all three versions so they share one rule (Peer Review 4c1a17ee P3). PARRY_PCTis kept, verified from the client data rather than the talent's name: Enhancement talent 16268 ("Parry" on Era, "Spirit Weapons" on TBC) learns spell 18848, whoseSpellEffectis the same effect (type 22) as the baseline Parry spell 3127, in both the 1.15.9.70003 and 2.5.6.68941 tables. Shield Specialization backs the block weights and Anticipation the dodge weight. Forever's tree was not checked. (The first cut of this entry and the builder comment said TBC had noTalents.luaand that Anticipation gives defense; both were wrong -- Peer Review 4c1a17ee P1/P2.)itemdb_common.headerwrites a blank header line as--with no trailing space after it. Every builder's generated headers lose that whitespace the next time they are rebuilt.- No scorer change: the Vanilla/TBC scoring rules carry no spec-keyed tables, and the
per-class
ScoreModeldefault applies. No MINOR bump -- the API is unchanged and Dibs detects the scale byGetBiSWeightsreturning non-nil. - New
Tests/derived_weights_check.py(declared in.writ-suites.json, red-checked by editing one weight in the shipped row, then by puttingDPS_OFFHANDback) pins that each derived scale equals its source minus the dropped keys, that the header names it as a copy and each drop, and that[7].defaultis stillelemental.
Added -- what a tier token is turned in for (MINOR 44, Dibs' contract 1c5f8e8d2faf)
DB:GetTokenRewards(itemID)returns the gear a boss-dropped token is exchanged for, every class's together, as a fresh table sorted by item id, ornilfor an item that is not a token (never an empty table). Dibs prices a token by a substitute item level and slot, and asked for a default derived from the rewards.lib:LoadTokenRewardsreplaces per token. MINOR 43 -> 44.- New
tools/build-tokens.py->Data/<V>/_core/TokenRewards.luafor Vanilla (87 tokens), TBC (159), Wrath (226) and Mists (115), listed in those four TOCs. The client has no such table; the exchange is server-side, so it reads the same world DBbuild-droprates.pydoes:quest_templateturn-ins (every Vanilla token) andnpc_vendor/npc_vendor_templateExtendedCostagainst the client'sItemExtendedCostDB2 (TBC onward). A token is an item required by count 1, not itself equippable, dropped by an encounter inSources.luaorDropRates.lua, and rewarding equippable gear in that version's_core. - Three defects found while building it, each fixed and pinned by the new
Tests/tokens_check.py(declared in.writ-suites.json, red-checked):- Head of Onyxia and Head of Nefarian were missing: quest 7490 takes the head and rewards
nothing; its
NextQuestInChain7491 needs no item and rewards the rings. A reward-less turn-in is now followed down its talk-only chain (depth 3). - Chestguard of the Fallen Defender listed six Gladiator pieces beside its seven T4 chests.
tbc-db prices Karynna's Season 1 stock (template 555) at the same
ExtendedCost1235 the T4 vendor uses, and the 2.5.6 client reads 1235 as one T4 chest token. A vendor now counts only when most of its cost rows resolve to a token (template 554 and npc 26089 fail that, at exactly half), and a reward carrying resilience is never a token reward. Every T4, T5, T6 and Sunwell row now matches AtlasLoot's own token table (AtlasLootClassicData/Token.lua, BCC); 29753 and 30243 are pinned against it. - SkyFire's
quest_templatehas no required-item columns, so Mists refused to build; the quest half now degrades to nothing there and the vendor half carries it.
- Head of Onyxia and Head of Nefarian were missing: quest 7490 takes the head and rewards
nothing; its
- Not covered, stated rather than hidden: Forever, Cata and Retail ship no file: the pipeline has no world DB for any of the three. Mists carries the Wrath-era tokens only; why Pandaria's own tier tokens are absent was not investigated. Wrath's Trial of the Crusader trophies were not checked. Nothing ran in a live client.
Fixed -- the armor-curve verify hid its own failures (tools only, nothing ships)
tools/build-core-wowsims.pyverify_armorprinted low and OVER rows from one list capped at 12. Low rows (items with bonus armor, expected, often hundreds) filled the cap first, so the Forever run of 2026-10-12 reportedOVER 2and named neither item. OVER rows are the failures, so they now print in full under their own heading; low rows keep the first-12 sample.- That run's two OVER items are 16213 (walk 531, curve 541) and 273646 (walk 43, curve 143).
16213 is "Test Quality Modifier Chest", flagged hidden, and its Forever
ItemSparserow (1.60.1.69913) carriesQualityModifier=-10, whichbase_armordoes not read; whether that field accounts for the 10-armor gap is not verified. 273646 ("Half-Eaten Boots", Forever-only) has no row in the cached ForeverItemSparseat that build, so the walk came from a newer client and the item cannot be inspected at the pin. Players are unaffected: Forever's core is walk-built, so its shipped armor is the client's own figure and this formula does not reach it. Rerun 2026-10-12 with the fix: both rows print underOVER:; Vanilla unchanged (6,715 exact, 12 low, 0 over). - No shipped flavour carries curve-built armor today: every
Data/*/_core/Armor.luaheader (and Vanilla's_seasonal/Armor.lua) namesbuild-core.py(a walk) orbuild-core-wago.py, andbuild-core-wowsims.pyrefuses to overwrite either. So neither OVER cause reaches a player. armor_curvestreatsItemArmorShieldas optional. wago answers HTTP 400 for it at TBC 2.5.6.68941 while serving the other three curve tables, and the TBC verify died there before checking a single piece. Shields are now "not computable" (base_armorreturnsNone) and--verifycounts them asunchecked, never as low (low means "carries bonus armor"). A 504 or any other error still raises. The TBC verify itself has not run yet: wago answered 504 at both 2.5.6.68941 and 2.5.5.67511 the same day.- Peer Review (ad843502): unchecked rows first sat outside the total, so
--verifyexited 0 with every shield skipped, and a build at such a pin would have shipped every shield with no base armor. Now unchecked rows count toward the total and never toward a pass, so--verifyexits 1 while any remain; and the newrequire_shield_curvemakes the build refuse when the shield table is absent and any shield would ship. A wrong 400 then costs a rerun, never bad data. - New
Tests/armor_curve_check.py(declared in.writ-suites.json, 13 cases) pins that path, the 504 re-raise, the build refusal, the failing partial verify and the robe-reads-chest row. Red-checked three ways: theNoneguard replaced with an empty table (got 0), the old return restored (got(1, 1)), and the refusal short-circuited (gotFalse). - New repo desk tools:
itemdb-verify-pinned(a builder's--verifyat an exact--wago-build, so the check reads the build the shipped files carry -- wago.tools answered 504 for the installed 1.15.9.70003 while 1.15.9.69722 is cached) anditemdb-derive-maps(tools/derive-core-maps.py --version V --wago-build B, read-only;itemdb-buildcannot pass its version flag).
[v1.4.1] - 2026-10-12
Fixed -- where-to-get places filed in the wrong zone (Dibs' DIBSREQ-IDB, Searing Whelp)
Dibs' report (inbox 2c393671f8ee): Dibs showed Small Flame Sac dropping from Searing Whelps in
Thousand Needles on Classic Era. The whelps live in the Wyrmbog, Dustwallow Marsh.
- Root cause:
tools/build-item-places.pyfiles each spawn under the zone whoseUiMapAssignmentrectangle holds it. Those rectangles are map-image bounding boxes and overlap far past the real borders: every Searing Whelp (4324) spawn is inside the Barrens, Thousand Needles AND Dustwallow rectangles, and the tie-break ("the smallest rectangle") chose Thousand Needles. No rule over the rectangles alone gets it right (smallest, deepest-inside and nested were all tried), and the CMaNGOScreature_zone/gameobject_zonetables are empty. - New
tools/fetch-questie.py(deskitemdb-fetch, sourcequestie) caches QuestieDB's (github.com/Questie/QuestieDB) NPC and object spawn tables for Classic, TBC, Wotlk, Cata, MoP and Forever at a pinned commit (cd4ac424,tools/wago_cache/questie.source.json), each file verified against its git blob id, and reads them: per entity,{AreaTable zone: [(x, y)]}. It sharesfetch-att.py's GitHub helpers (resolve/fetch_onenow take the repo's API, cache and raw roots, defaulting to AllTheThings'). Placer.placestakes that as a hint: a spawn inside several rectangles goes to the zone QuestieDB files the entity under (nearest recorded point when it lists several candidates), then the zone the entity's unambiguous spawns use, then the smallest rectangle. The AreaTable id becomes a UiMap through the client's ownUiMapAssignment.AreaID(zone_areas, a SET of UiMaps per area, so an area on two zone maps hints both). The coordinates shipped are still the CMaNGOS spawn's; QuestieDB only picks the zone, and none of its data is shipped. QuestieDB carries no licence file (GitHub's licence API answers 404, recorded inquestie.source.json); it is a build-time input only.- A hint whose nearest QuestieDB point is more than
HINT_MAX_YARDS(250) from the spawn gives way to the entity's clear spawns, but still beats the smallest rectangle. Measured on shipped spawns before choosing it: Vanilla, the same world as QuestieDB, median 0 yards, 99th percentile 49, max 4,644; Cata, whose places are SkyFire 5.4.8 spawns against QuestieDB's 4.x points, median 0, 90th 50, 99th 560. It moved four entities on Cata and Mists, none elsewhere: Coral Shark (5434) drops The Cape of Stranglethorn, Winterfall Runner (10916) drops Felwood, and the Firefin Snapper and Oily Blackmouth schools (180683 / 180682) drop Thousand Needles. Which side is right for those four is not verified. tools/fetch-questie.pyrefuses a line that opens a row ([<id>]) but is not one whole[id] = {...},line, instead of skipping it, and looks the licence up before its already-pinned early return so a cache pinned offline does not keep "not reached".- The builder reports which rule settled the overlapping spawns -- summed over the entities it
ships, not every entity it placed (a vendor weighed for the zone spread and not chosen used to
count) -- the distance spread above, any QuestieDB area that sits on more than one zone map
(none on any flavour; its points would be measured in a frame that may not be theirs), and how
many places changed zone against the file it replaces AND against git HEAD. Rebuilt every
flavour QuestieDB covers; places that changed zone against HEAD, the cap included: Vanilla 775
of 4,725, TBC 916 of 6,510, Wrath 1,213 of 8,559, Cata 1,253 of 9,601, Mists 1,351 of 10,216.
Vanilla's shipped overlapping spawns: 36,450 settled by QuestieDB, 344 by the entity's
clear spawns, 96 by clear spawns over a far hint, 14 by a far hint, 33 by the smallest
rectangle. Checked in the built file, not in a client: Searing Whelp to Dustwallow Marsh,
Skeletal Warrior (48) and Gavin Gnarltree (225) from Deadwind Pass to Duskwood, Gruff Swiftbite
(100) from Duskwood to Elwynn Forest, Mine Spider (43) to Elwynn Forest. Vendor choice shifts
slightly where a vendor's zone changed (vendors are spread over zones), so a few translated
names enter or leave
PlaceNames.lua. Retail is unchanged: QuestieDB stops at Mists. Vanilla is pinned to 1.15.9.69722, the build its siblings carry. Tests/item_places_check.py: the hint against both old rules, nearest-point choice, an area on two zone maps, the distance cap both ways, the shipped-only report, a split row refused, the move report, and the real case on real data -- Searing Whelp places in 1445 only, and without the hint it still comes out 1441, so the check can fail. A missing cache fails it rather than skipping.
Changed -- Peer Review f4bc13b5 follow-ups
Tests/itemdb_common_check.py's every-[id]=assertion now red-checks itself on every run: a scratch tree with one rowcore_rowscannot read must come out unequal.Data/Vanilla/_core/EquipStats.lua(hand-edited for the earlier red-check) is rebuilt by the builder at 1.15.9.69722 and re-split, rather than restored by hand.- New desk tool
itemdb-verify(repo scope) runs a builder's read-only--verify, whichitemdb-buildcannot pass.
Fixed -- gear school resistances on Vanilla, TBC and Wrath (MINOR 43, Dibs' DIBSREQ-IDB)
Dibs' contract (inbox f93b32b37ec4): a frost-resistance sort in Dibs' Planner found almost
nothing on Classic Era, because GetStats returned no school resistance for most gear. Glacial
Cloak (22658) had stamina and armor but not its 24 frost; Polar Tunic (22661) was missing 40
frost and Arcanist Robes (16798) 10 fire.
- Root cause: the Classic clients'
GetItemStatsnever reportsItemSparse.Resistances_1..6(0 items in the Vanilla and TBC walks, asbuild-core-wago.pyalready recorded), so the walk-built cores carried only resistance that comes from an on-equip aura (16061: column 0, walk fire 9). The Wrath core reproduces the TBC walk and so lacked them too. tools/build-equip-stats.pynow readsResistances_1..6in its existingItemSparsepass and emitsRESISTANCE<n>_NAME(1 holy, 2 fire, 3 nature, 4 frost, 5 shadow, 6 arcane) intoEquipStats.lua, whichGetStatsmerges. An item whose core ALREADY carries a school (an on-equip aura the walk reported) and whoseItemSparsecolumn names the same school would need the two summed, whichGetStats' core-wins merge cannot do -- so the builder REFUSES and names the item (Peer Review13334356F1; the first cut only counted it on stderr). None exists on any version today.- Rebuilt: Vanilla 1,371 items (pinned to 1.15.9.69722, the build its siblings carry, then
split-seasonal.py), TBC 667, Wrath 723. Forever gains none: itsItemSparsecolumns are empty and its walk already reports resistances. Mists, Cata and Retail store resistances as stat slots, which their cores already read; not rebuilt. itemdb_common.core_rowsis now THE reader of the shipped class files (CORE_ROW+ thelib:LoadCore(test), andcore_idsis a view over it. It replaces four private copies of the row regex and file walk inbuild-equip-stats.py,build-core-wago.py,build-core-wowsims.pyandderive-core-maps.py(whose hand-keptNON_ITEMside-table list is gone). Found through it:core_resist_keysassigned per row, so an id with a base AND a seasonal row kept only the last file's schools (Peer Review13334356F1b); it now takes the union.Tests/data_baseline.jsonre-recorded. The 14 shrinks it accepted were all already in the released v1.4.0 tree, HEAD being that release:Data/Retail/_core/Effects.lua1,186 -> 1,182 (-4),Data/Vanilla/_seasonal/Quest.lua451 -> 450 (-1) and the twelveData/Vanilla/_seasonal/<locale>/Names.lua6,523 -> 6,522 (-1 each; the same item moved to the baseQuest.lua/Names.lua, both +1). Also adopted: RetailHidden.lua(new, 13,923) and the grownHidden.luaon Cata, Forever, Mists, TBC and Wrath.- MINOR 42 -> 43 so a consumer can gate on the data (
select(2, LibStub("LibItemDB-1.0")) >= 43); no API change.CLAUDE.md's MINOR rule now says so: it moves for an API change OR when a consumer needs to detect new data (Peer Review13334356F2). Tests/equip_resist_check.pyred-checked by removing Glacial Cloak's row from the Vanilla file: it failed on that item, and passed again once restored.
[v1.4.0] - 2026-10-10
Dibs' DIBSREQ-IDB-010 (inbox d7219561224e): on a rogue, Dibs' Buffs tab offered five foods and
priced every sharpening stone at 0.
New -- sharpening stones and weightstones score (MINOR 42)
lib.StatScorers.weaponDamagepricesWEAPON_DAMAGE(flat damage per hit) as damage / swing speed x the hand's weapon-DPS weight, the weight the weapon's own DPS uses. The hand comes fromopts.equipLocthrough the rules'dpsSlot; the speed fromopts.attack.weaponSpeed/offhandSpeed(the latter falls back to the former). No speed or a ranged slot answers 0 rather than a guessed number. Registered byScoring/Vanilla.luaandScoring/TBC.lua;WEAPON_DAMAGEjoins bothSKIP_KEYSso the flat walk never prices it. Capability flaglib.SCORE_WEAPON_DAMAGE. Until now no code read the key, so Dense Sharpening Stone scored 0 for every class.
New -- which weapons a stone fits (MINOR 42, Dibs' DIBSREQ-IDB-011)
DB:GetImbueWeapons(id)and aweaponsfield on everyGetConsumableBuffsrow: theGetItemTypenames a sharpening stone (Axe,Dagger,Polearm,Sword) or weightstone (Fist Weapon,Mace,Staff) can be applied to;nilfor an oil.build-effects.pyreads it from theSpellEquippedItemsrow of the spell carrying the temporary enchant (Dense Sharpening Stone's 16138: subclass mask 33219; Dense Weightstone's 16622: 9264; Brilliant Wizard Oil's 25122: mask 0, inventory types only) and writes it as a third table inEffects.lua,lib:LoadImbueWeapons. Vanilla 14 stones (12 base, 2 seasonal), TBC 14.WEAPON_TYPE/weapons_from_maskmoved frombuild-talents.pyintoitemdb_common, so the talents'weaponstags and the stones' cannot disagree.- The other half of DIBSREQ-IDB-011 needed no code: melee swing speed has shipped since v1.2.0 as
MELEE_WEAPON_SPEEDon every flavour (Thunderfury 1.9, Gorehowl 3.6). The README only ever mentioned it underSearch, which is why it read as ranged-only; it is now documented beside the stone scorer and in the stat vocabulary table.
New -- Retail consumable buffs (Data/Retail/_core/Effects.lua)
- Retail ships Effects for the first time: 584 consumables (381 food, 147 other, 18 potions, 12
flasks, 9 scrolls, 3 elixirs, 14 weapon imbues), loaded by
ItemDB_Mainline.toc. A trial build on 2026-10-09 resolved only 50 items and was discarded, because Retail stores almost every buff amount asCoefficientx a game-table column withEffectBasePointsF0. itemdb_common.spell_effect_scaled_valueresolves Retail scaling. The scaling class lives on the SpellEffect row (ScalingClass), not on SpellScaling as on Cata / Mists, and the game table names its columns (Item,Consumable,Gem1, ...,Mana Consumable). The formula is TrinityCore master'sSpellEffectInfo::CalcBaseValue: the level cap (90) clamped to the spell's SpellScaling Min/MaxScalingLevel (or itsSpellLevels.BaseLevelunder SPELL_ATTR10 0x1000), the class column x Coefficient, raised to 1 when in (0, 1), rounded half away from zero. Class -7 is read as the Item column with its item-level multiplier taken as 1. Class -6 and SPELL_ATTR11 0x4 (item-level scaled) stay unresolved rather than ship a guess.- Amounts are the level-90 value, like Cata (85) and Mists (90).
- The Retail path is chosen by the build's major, not by the row having a
ScalingClasscolumn (Peer Review c27e8a07 M4): Forever's SpellEffect carries that column too, and Mists 5.5.4 has an unnamed one in the same place. A zero game-table column now answers "no scaling" and keeps the stored base instead of shipping 0 (L1; every per-class column of the 12.1.0 table is 0). - Both scaling paths now round through one
itemdb_common.c_round(half away from zero, as the client does); the Cata / Mists path used Python's half-to-evenround(), so an exact n.5 would have shipped one low. Pinned inTests/spell_scaling_check.py(2.5 -> 3). Rebuilding Cata and Mists Effects, UseEffects and AuraBuffs and Retail Effects with it moved no line. - Verified against Wowhead's Retail tooltips: Well Fed 19706 "Stamina and Versatility increased
by 1" (Goretusk Liver Pie 724 ships Stamina 1, Versatility 1); Hearty Meals 222740 "4 Stamina
and 2 Agility". Both are pinned in
Tests/spell_scaling_check.py(Retail cases) andTests/effects_check.py. - Aura 189's Versatility bits (CR 29-31, mask 0x70000000) map to
ITEM_MOD_VERSATILITYon Retail (the rating table, nowitemdb_common.rating_groups). They were reported unmapped on 154 Retail rows. - Retail spells carrying SPELL_ATTR12 0x00200000 (
USE_FLOAT_VALUES_FOR_SCALING_AMOUNTS,SpellMisc.Attributes_12) keep their fractional scaled amount instead of being rounded, as TrinityCore master'sCalcBaseValuedoes (itemdb_common.retail_finish_scaled;build-effects.pywrites such a float to 4 decimals). No shipped Retail row is fractional today, so no data moved;Tests/spell_scaling_check.pypins both halves of the rule (Peer Review c27e8a07 L2/F1). - Rune of Darkness 139461 and Blessing of the Light 139459 (Legion consumables that save you from
a fatal blow) shipped
ABSORB0..6=100000000, the client's "absorb everything" amount. Every Scoring file pricesABSORBnas health, so the first Retail scorer would have ranked them above every real item.build-effects.pyskips exactly that amount (ABSORB_ALL, reported as unresolved69:absorb-all) rather than a ">= N" cutoff, so a new large legitimate absorb still ships (Peer Review, thread c5c4048f). Retail Effects now has 584 rows;Tests/effects_check.pyasserts no shipped Effects file carries the amount (red on 139459 / 139461 before the rebuild). - Not done: Retail has no Scoring file, so nothing prices these rows yet, and no Retail ConsumableGroups.
- Two stale builder comments fixed (Peer Review c6500056 D7).
build-consumable-groups.pynow says 13506 is subclass 0 on Vanilla.build-effects.pynow gives Trogg Brew as HEALTH=30.
New -- Retail hidden items; Forever's level cap; AllTheThings read on Retail and Forever
- Retail ships
Data/Retail/_core/Hidden.luafor the first time, loaded byItemDB_Mainline.toc(Peer Review c27e8a07 L6). Before it, no Retail item was ever skipped by rankings or search. build-hidden.pynever read AllTheThings on Retail or Forever:_att_categorieslooked fordb/Retailanddb/Forever, but ATT's TOC loadsdb\[Game]\, which the client fills withStandard(Retail) andCamelot(Forever).ATT_DB_DIRmaps both. With no installed ATT,att_cache_nifalls back to the fetched source tree (tools/wago_cache/att/, Retail only, never-implemented only), and every Retail build cross-checks that reader against the installed list.- Retail's
NeverImplemented.lualists unreleased APPEARANCES (a source id with a bonus id), and the same item id can be live loot: Manaforged Displacement Chassis 237528 is a 24% Plexus Sentinel drop (Wowhead tooltip) and ATT lists it under both.att_extract.luanow printsbothfor a never-implemented id it also finds in content, andbuild-hidden.pydoes not hide those.att_extract.luaalso reports a failing data file or handler on stderr instead of swallowing it in apcall, which silently truncated the tree. - Forever gets
LEVEL_CAP60 (WoW Forever stays at 60): 18 items above it are hidden, all unusable at 60 (the Mesh / Strapped / Sterling Chain sets Vanilla already hides, the Azzinoth blades at 70). Retail gets no cap: 0 shipped items require more than Midnight's 90, so an entry would hide nothing today and real gear once the cap moves.LEVEL_CAP_PROBEkeeps Retail's count in every build's output. - Stormproof Sloop 217272 is hand-hidden (
tools/curated_hidden.py): it only works inside Plunderstorm, and its 9999 all-school absorb is a "negate the first hit" effect, not a health pool. The curated list's docstring now admits mode-only items. - Forever: 1,641 -> 1,664 hidden (18 by level cap, 6 by ATT).
att_extract.luacrashed on line 5 of ATT'sInstances.luaandHolidays.luain every classic flavour that reported it (Wrath, Cata, Mists; TBC's ten new rows below are holiday items, which points to the same loss there, not confirmed from a TBC error line), dropping the rest of each file: every instance drop and every holiday item. Instances post-processes constructor results inline ((function(t) t.objectID = t.providers[...][2] ... end)(e(1626, {providers = ...}))) and the stub constructors returned a bare{}; Holidays compares_.WOWAPI.GetItemCount(...) < 1, and the stub returned a table. Constructors now return their options table (missing fields resolve to the permissive stub) and every_.WOWAPIfunction answers 0. No file reports an error after the fix.- Rebuilt every flavour's Hidden under it. Shipped -> now: Vanilla 2,110 -> 2,110, TBC 4,707 ->
4,717, Retail (new) 13,923, Forever 1,664. Wrath, Cata and Mists rose by 26, 66 and 90 over the
crashing build: removed-with-patch items inside instances and holidays that the truncated tree
never reached. TBC's ten are Lord Ahune's original Midsummer drops (35494-35497, 35507-35509,
35511, 35514), which ATT records as removed in 2.5.5 (35514:
awp=20003, rwp=20505, u=2, beside a 2.5.5 replacement set), and the Yellow Brewfest Stein 32912 (its ATT entry not read). - Returning the options table also changed Vanilla's
eraset, whichsplit-seasonal.pyreads (self-audit 3d5d5d3f A3): a parent's argument scan now reached item nodes' ownproviders, so 190307 (named only ini(190308, {providers={{"i",190307}, ...}}), the same quest as 190308 and 190309, both already in base) became era content and moved from_seasonal/to base -- one row in Quest and each Names file. That is the header's stated rule, so it is kept, but now on purpose:recItemscans an item node's own options rather than relying on the parent call's shape.Tests/seasonal_ids_check.pypins 184937 and 190307 as era and in base (red on 190307 with the bare{}and no item-node scan put back). A second split-seasonal run moves nothing.
Changed -- flat % buffs price by attack family on Wrath / Cata / Mists (Peer Review c6500056 D2-D6)
- The scoring rules'
derivedentries replacepairwithfamily(physical/melee/ranged/spell) inScoring/Wrath.lua,Cata.luaandMists.lua.LibItemDB-1.0.luafamilySharescales the rating conversion by the spec's share of that family, read off the scale's weights byspellShare: spell power / (spell power + attack power), else Intellect / (Intellect + the better of Strength / Agility), else 0.5. Melee vs ranged follows the ranged-DPS weight beating the main-hand one, as the speed scorer reads it. An approximation for hybrids, exact at both ends. - Tanks and hybrids get a stated share (Peer Review c27e8a07 M3). The ratio gave Wrath protection
paladin a 0.75 spell share (spell power 0.4 vs AP 0.13), so its melee crit / hit % priced at a
quarter, and enhancement 0.47. The rules take an optional
spellShare = { [classID] = { [spec] = 0..1 } }, consulted before the ratio;LoadBiSWeightsrecords each weight table's class / spec in the weak-keyedlib.bisWeightsSpec, so no scoring call has to pass them. Every tank and retribution is 0 (melee); enhancement is 0.3, an estimate (shocks and Maelstrom bolts are real spell damage), not a simulation result.SPELL_SHAREin each of the three Scoring files. - The derived conversion is written once (S1):
scoreStatsprices a derived key througheffectWeight, which now also returns the rule, base weight and share;weightFactorsturns them into breakdown factors, so a use or proc row shows the conversion and share the same way a stat row does (L7) and its factors multiply to its EP. scoreProcEffectskeeps the higher of melee and ranged AP, asscoreStatsandscoreUseEffectsdo (S2). The omission was a double-count, not a choice:build-use-effects.pywrites a generic "+X attack power" spell as both a melee and a ranged row, and the same builder reads proc spells. No shipped proc carries the pair today (the one ranged-AP proc, Dragonstalker's 8-piece, is ranged-only), so no shipped score moves.- New
Tests/scoring_rules_agree_spec.lua(S3): the three Scoring files'derivedtables must tag the same keys with the same family and rating, each family must match its key's prefix, and every stated share must be in 0..1 and agree wherever a spec appears in more than one flavour.cata_scoring_specandmists_scoring_specgained hybrid-share, mixed-sign and use / proc cases (L8);wrath_scoring_specmoved its ratio cases to a synthetichybridspec, sinceenhancementis now stated. - D2: a spell-only % (Destruction Potion 22839's
SPELL_CRIT_PCT=2) priced at full crit-rating weight for an arms warrior; it is now 0 for a physical spec.cata_scoring_specandmists_scoring_specasserted the old price and were rewritten. The two test rings 18968 / 18982 (SPELL_CRIT_PCT=50) were already hidden on Cata and Mists, so needed no change. - D4: the pair's "better member" gave a caster the melee hit conversion; each spelling now prices only for its own family.
- D5 / F2: the pair kept the larger contribution, so a mixed-sign or negative pair dropped its
penalty. Shares sum to 1 across a stat's spellings, so the spellings are now summed and the sign
survives. Burning Blossom 23247 (
CRIT_PCT=3,SPELL_CRIT_PCT=3) still sums to one 3% crit. - D3:
scoreUseEffectsandscoreProcEffectslooked upw[alias[k] or k]only, so a % buff inside an effect (Felstriker 12590'sCRIT_PCTproc, Battlegear of Wrath 8-piece'sPARRY_PCT) scored 0 from TBC on with no breakdown row. Both now go througheffectWeight, the same derived conversion and family share as the stat walk (derivedRuleis shared withscoreStats). - D6: the derived % keys carry labels in the three Scoring files (
Crit %,Spell Haste %, ...); the breakdown showed the raw key. A derived row adds a<family> sharefactor when the share is not 1. - The melee / ranged AP pair is settled once (Peer Review 4eaaa2da M1, L1):
apPairEp/apPairCloseservescoreStats,scoreUseEffectsandscoreProcEffects. The on-use scorer summed every same-side AP effect but itemised one merged row from the LAST effect's factors, so with two effects on one side the row's factors did not multiply to its EP; it now emits a row per effect. The pair is sign-correct (L2): same-sign sides keep the larger magnitude, opposite signs sum, so a lone AP penalty is no longer dropped bymax(-x, 0). No shipped item is known to carry either case. Also L3 (the MistsSPELL_SHAREcomment names the AP-vs-SP ratio for the specs that weight AP), L5 (Cata / Mists tank-copydefaultscales get stated shares) and L7 (hp5 is no longer described as unpriced inScoring/Mists.luaandmists_scoring_spec). - D8: no capability flag; it ships inside MINOR 42, and the README says so.
- Suite 917/0 (re-run 2026-10-10 after every change above); 100% line coverage on
LibItemDB-1.0.luaand the three Scoring files.buffs-ep-vanilla/buffs-ep-tbcred only on Sayge's Dark Fortune of Damage, the known Dibs-side row.
Fixed -- the seasonal overlay kept rows a rebuild had dropped
tools/split-seasonal.pyre-partitioned the UNION of the base file and the existing overlay, so a seasonal row the builder stopped emitting stayed in_seasonal/for ever. The Vanilla Effects overlay still shipped 227813 (mp5 1104) and 231778 (mp5 851), drink ticks the decoder fix above removed, and 232433 (MANA=426) with no category. The base file the split writes now starts with a-- partitioned by tools/split-seasonal.pyline; a base without it that still holds a seasonal row is a builder's fresh output, and its old overlay is replaced instead of merged. Files split before the line existed hold no seasonal row, so they are merged as before and marked. Every Vanilla_coreand localeNames.luafile gains that line. Pinned inTests/split_seasonal_check.py(red with the replacement disabled). The 679 stale ids the docstring records in the non-enUS VanillaNames.luaoverlays leave on the next locale rebuild.
Fixed -- Well Fed foods, drinks and TBC rating buffs in Effects
tools/build-effects.pynever followed SpellEffectEffect64 (TRIGGER_SPELL), which is how most Well Fed foods reach their buff (Blessed Sunfruit 18124 -> 18125, Runn Tum Tuber Surprise, the Mightfish / Bass / Sagefish). Those foods had no Effects row and nofoodgroup. It follows the trigger now.- Every drink shipped its drinking tick as mp5: the restore while seated and real mp5 are the same
aura 85. Spells whose aura ends on standing up (
SpellInterrupts.AuraInterruptFlags_0 & 0x40000) are now the eat / drink itself and contribute no stat. Refreshing Spring Water lost its bogus 42 mp5; Blessed Sunfruit Juice 13813 and Bottled Alterac Spring Water 19318 went from 735 mp5 to their real Well Fed +10 Spirit. Vanilla: 55 such rows, TBC: 96. - Inside a Well Fed buff (reached from an eat / drink spell) the periodic-mana and periodic-heal
auras 24 / 8 are regen, not a restore, and are emitted per 5 seconds as
ITEM_MOD_MANA_REGENERATION/ITEM_MOD_HEALTH_REGEN_SHORT: Nightfin Soup 8 mp5, Smoked Sagefish 3, Sagefish Delight 6, Poached Sunscale Salmon 6 hp5. Elsewhere they stay unmapped. - Aura 189 (MOD_RATING) was unmapped, so TBC's rating foods and elixirs carried only their other
stats (Spicy Hot Talbuk had its +20 Spirit and not its +20 hit rating). The misc value is a bitmask
over the client's
CR_*indices minus one (read from the classic_anniversary tree'sBlizzard_CharacterFrame/TBC/PaperDollFrame.lua); each group maps to the TBC gear key. TBC Effects rows 379 -> 395. - The "only mp5 / max pool, so it is food" category fallback is gone. After the seated fix it
caught only Trogg Brew 4953, a drink buff that stacks with Well Fed; it is
othernow, andfoodmeans the Well Fed path only. build-effects.pynow reports every item whose use chain reached a trigger and still produced no stat, with the chain -- that report is how the missing foods were found.- The same decoder now builds Wrath, Cata, Mists and Forever, each pinned to the build its other
_corefiles carry (3.4.3.58936, 4.4.2.60895, 5.5.4.68806, 1.60.1.69913), withbuild-consumable-groups.pyrerun after. Effects rows now: Wrath 518 (446 before), Cata 551, Mists 662, Forever 386. Every flavour also gained itsLoadImbueWeaponstable (Wrath 15 stones, the rest 14). - Rating bits 24 and 25 (
CR_ARMOR_PENETRATION25,CR_MASTERY26, from the classic tree'sBlizzard_CharacterFrame/Cata/PaperDollFrame.lua) map toITEM_MOD_ARMOR_PENETRATION_RATINGandITEM_MOD_MASTERY_RATING_SHORT, the keys Wrath / Cata / Mists gear and Scoring already use. Mists reported 12 mastery rating auras as189:mask33554432and gained 10 rows (652 -> 662); Wrath gained 2 (516 -> 518), Cata 1 (550 -> 551). - Aura 227 (PERIODIC_TRIGGER_SPELL_WITH_VALUE) is followed like aura 23, with the triggered spell's amount taken from the 227 row. WoW Forever files its Well Fed foods this way: Goretusk Liver Pie's 1248384 carries 227 (amount 3) -> 1248422 "Well Fed" (MOD_STAT Strength, its own points a placeholder 1), and the higher foods hand 5 / 10 / 15 / 20 to the same three Well Fed spells. 101 Forever foods gained a row (285 -> 386) (Goretusk Liver Pie +3 Strength, Succulent Pork Ribs +5). Not verified in a live Forever client.
- Retail was built later in this release, once its level-scaled amounts resolved (see "Retail consumable buffs" above).
Fixed -- the food exclusion group
tools/build-consumable-groups.pygroups everyfood-typed item in the version's_core/Effects.luaasfoodwhere WoWSims / CMaNGOS named no slot. WoWSims types only the foods its sim offers: Vanillafood11 -> 67, TBC 134 (it already had CMaNGOS's well-fed mask). Runbuild-effectsbeforebuild-consumable-groups.- Vanilla Effects rebuilt at the core's own pin, 1.15.9.69722 (
itemdb-build-pinned); a first rebuild at the installed client's newer 1.15.9.70003 failed the one-build-per-kind spec.
Fixed -- hp5 scores as a heal
ITEM_MOD_HEALTH_REGEN_SHORThad no pricing route in any weight scale, so Poached Sunscale Salmon 13932 (+6 hp5) was the one Well Fed food at 0 EP for every spec.Scoring/Vanilla.luaandScoring/TBC.luanow price it throughderivedthe way the proc heal bucket prices a heal: hp5 / 5 = HPS, xepPerHPS(3.5) = healing power, x the healing weight (per the operator's ruling that a heal has EP). Read off the rules MODEL, so a per-classLoadScoreModeloverride ofepPerHPSdoes not reach it.
Fixed -- protection potions score for tanks
- A school protection potion's shield (
ABSORB0..ABSORB6) had no pricing route, so all eleven TBC Major Protection Potions scored 0 EP for every spec. Absorbed damage is health not lost, so it is now priced as a health pool (the operator's ruling, 2026-10-09; resistance stays unpriced):Scoring/Vanilla.luaderives it through Stamina at 10 HP per point, likeHEALTH, with breakdown labels "Absorb (Fire)" etc.;Scoring/TBC.luaandScoring/Wrath.luaalias it ontoHEALTH, which their scales weight directly;Scoring/Cata.luaandScoring/Mists.luaderive it through Stamina at 14 HP, like theirHEALTH. Mists' header, which said no flavour priced it, is corrected. Pinned by a spec in each of the five flavours; the Vanilla and TBC specs were red-checked by removing the rule (both failed).
Fixed -- a shield against several schools was priced once per school
build-effects.pywrites oneABSORBnkey per bit of a shield's school mask, so a shield that absorbs any school (mask 127) shipsABSORB0..ABSORB6, and every flavour's scorer added them up: the Greater Ward of Shielding 23576 scored as 28,000 health instead of 4,000, Potion of Petrification on Wrath as 42,000.scoreStatsnow runscollapseAbsorbsfirst, keeping only the largestABSORBnon a row, for every flavour and on both the score and the breakdown routes. The data keeps one key per school (Dibs reads the raw keys). Peer Review inbox85dbb074I1. Pinned by a seven-school spec inlibitemdb_spec.luaand the TBC absorb spec, both red with the collapse removed.
Fixed -- rolled spell amounts shipped at their lowest roll
itemdb_common.spell_effect_base_pointsignoredEffectDieSides, so an amount that rolls shipped its floor: Sayge's Dark Fortune of Damage 23768 (base 0, 10 sides, "1-10%") readPCT_DAMAGE=1on TBC, Wrath and Mists, and every protection potion its minimum (Greater Fire Protection 13457ABSORB2=1950, rolling 1950-3250). A roll now returns its expected value, base + (sides + 1) / 2, the number Retail itself stores inEffectBasePointsF(5.5 for 23768); an integral average stays an integer. Classic Era's fixed 10% is unchanged. The data written through the helper was rebuilt: Vanilla's protection potions now read 2600 (matching Forever, which already shipped the average), the lesser ones 400-1800, TBC's Sayge 5.5. Pinned inTests/spell_scaling_check.py. Peer Review inbox85dbb074I2.The same helper also fixed an off-by-one in Season of Discovery data: those spells store a fixed amount with
EffectDieSides 0, and the old code always added 1._seasonal/Effects.luavalues drop by one, mostly to round numbers (not checked against an in-game tooltip) (233964 spell power 181 -> 180, 233965 Stamina 101 -> 100, 233969 armor 451 -> 450). That file loses three rows -- drinks 227813 and 231778 (the seated-drink fix) and 232433's stale mana row -- and gains Scroll 228227.Mists' Greater Fire Protection 13457 ships
ABSORB2=2607, not 2600: that is the Mists client's own value (spell 17543 Coefficient 0.79 x the spell-scaling table's 3300 at its level-48 cap), not a decoder error. Peer Review inbox58162a3b.
Fixed -- rating consumables scored twice from Wrath on; Forever foods missing stats
build-effects.pysplit each rating aura's bits into a melee key and a_SPELL_key, which is right for Classic Era and TBC (separate ratings) and wrong from Wrath on, where hit, crit and haste are one rating each: an elixir carrying both keys scored twice.rating_groups(version)now gives Wrath, Cata, Mists and Retail one key per rating; Classic Era, TBC and Forever keep the split. Rows merged: Wrath 43 (14 spell-only rows renamed, e.g. 41169), Cata 50, Mists 76; TBC is byte-identical. Resilience reads bits 14-15 only from Cata on (bit 16 isCR_LIFESTEALthere; no shipped row moved). Peer Review inboxfa38856eI1, I5.- Forever: aura 290 (crit chance) now writes
CRIT_PCTandSPELL_CRIT_PCT-- assuming Forever's crit is unified, not verified -- and aura 465 writes armor (RESISTANCE0_NAME). New rows: Grilled Squid 13928 and Hot Smoked Bass 13929 (1% crit), Plated Armorfish 286152 (150 armor, now groupedfood); Elixir of the Mongoose 13452 and four more elixirs gain their crit. Run speed (aura 129) stays unmapped. A 227 trigger's amount is now collected before decoding, so a spell reached first another way still gets it; it still feeds every effect of the spell, because eight Forever attack-power foods store AP and RAP both as the placeholder 1. Peer Review inboxfa38856eI3, I4, I7. - The same double count survived for flat percentages: Burning Blossom 23247 ships
CRIT_PCT=3,SPELL_CRIT_PCT=3(two auras, 52 and 57), and Wrath / Cata / Mists derive both ontoITEM_MOD_CRIT_RATING, so it priced as 6% crit. Fixed in scoring, not the data, because these are two real auras: aderivedentry may now carrypair = "<name>", andscoreStatsprices each pair as its best member (ties to the lower key) with one breakdown row.Scoring/Wrath.lua,Cata.luaandMists.luapair crit, hit and haste %. Vanilla and TBC are unchanged -- they weight melee and spell apart. Specs in the three*_scoring_spec.lua, red-checked by removing Wrath'sSPELL_CRIT_PCTpair (1 failed). Peer Review inboxfa38856e, reopen0f50301a.
Fixed -- protection potions get an exclusion group, one per school
- wowsims/classic ships its Greater Protection Potion configs commented out, so none of Classic
Era's eleven school protection potions had an exclusion group and the Buffs-tab audit (and,
presumably, Dibs' grouped picker) skipped them.
build-consumable-groups.pynow reads each item's cooldown category (ItemEffect.SpellCategoryID, at the buildEffects.luarecords) and groups untyped items aspotionswhen they carry the category at least two thirds of the wowsims-typedpotionsitems share, with no tie -- otherwise it adds nothing and prints the vote. It never overrides a slot wowsims or CMaNGOS gave. The first cut demanded a unanimous category, which TBC's 20004 and 30793 (not on category 4) silently defeated. Classic Era: category 4, 13 of 13; 12 items added (6048-6052, 13456-13461, Potion of Fervor 1450). TBC: 36 of 38; 14 added. The Flame Deflector, Ice Deflector and Frozen Rune are not on the potion cooldown and stay ungrouped. Peer Review inbox85dbb074I3. - A
potionsgroup means "only one active", and the potions only share a cooldown: protection potions of different schools can be drunk before a pull and run together (inferred from the shared cooldown and pre-combat use, not verified in game). So anypotionsitem carrying a school absorb (ABSORBn) is filed asprotection-<school>(protection-allfor all seven schools) instead, on every flavour -- ItemDB's own rule, overriding wowsims' typing on TBC. Classic Era 11 items moved, TBC 22. The generated header now names both passes and the client build the category was read at. Peer Review inbox62329f9dP2-P5. - "On every flavour" was not true: the pass only re-filed items already in
potions, and Wrath's wowsims types no potion while Forever's cooldown vote fails (4 of 8), so both shipped noprotection-*row and Dibs dropped every protection potion there. An ungrouped item whose Effects type ispotionand which carries a school absorb now qualifies too. Wrath gains 28, Forever 11; Classic Era, TBC, Cata and Mists are unchanged. Flask of Petrification 13506 staysflaskon TBC andprotection-allfrom Wrath: the client renames it Potion of Petrification and moves it to the potion subclass there.Tests/effects_check.pyasserts every flavour's absorb potions carry aprotection-*group (red-checked: Wrath 6048 set topotionsfails it). README's group paragraph, Forever count (26 -> 159) and stone-scoring scope corrected. Peer Review inbox62329f9d, reopenc4974c0e.
Fixed -- hp5 on Wrath, Cata and Mists, and missing breakdown labels
- No Wrath, Cata or Mists weight scale weights
ITEM_MOD_HEALTH_REGEN_SHORT, so hp5 scored 0 for everyone there. Those rules now price it through spell power by default, as Classic Era and TBC do (hp5 / 5 xepPerHPSx the healing weight), under a new derived-rule flagscaleWins: a scale that weights hp5 itself takes precedence -- including an explicit 0, which the first cut read as "no weight" and overrode (LibItemDB-1.0.luanow tests~= nil; Peer Review inbox58162a3bN1). Specs inwrath_scoring_spec.lua, both hp5 spellings. - Sharpening stones and weightstones scored 0 on Wrath, Cata and Mists: only TBC registered the
weaponDamagescorer. Each ofScoring/Wrath.lua,Cata.luaandMists.luanow skipsWEAPON_DAMAGEin the flat walk, registers the scorer (damage / weapon speed xDPS_MAINHANDorDPS_OFFHAND; 0 in a ranged slot or with no speed) and labels it "Weapon damage (stone)". A spec with no weapon-DPS weight (Mists protection warrior) still scores a stone 0. Specs in each flavour's scoring spec, red-checked. Peer Review inboxfa38856eI2. - Shield rows on Cata and Mists showed the raw key (
ABSORB2) in the score breakdown; they now read "Absorb (Fire)" etc. as on Classic Era. Classic Era and TBC gain a "Health per 5 sec" label.
Fixed -- the 11 languages' seasonal item names were never rebuilt
build-locales.pytook its item ids fromcore_ids(), which reads the base_coreclass files only, so it never wrote a seasonal (Season of Discovery / Fresh) id.split-seasonal.pythen carried each language's old_seasonal/<locale>/Names.luaforward unchanged and only moved its header's build number, which claimed a rebuild that had not happened.core_idsandread_namestakeinclude_seasonal, whichbuild-locales.pynow passes. Rebuilt at 1.15.9.69722: 24,127 names per language (15 English fallbacks), and the split regenerated all eleven overlays from them (6,523 each, the same count as the enUS walk's overlay). The "679 stale ids" the split's comment recorded were measured against an older client build; at this build no overlay shrank.- The same base-only read froze four more overlays:
build-classes.py,build-equip-stats.py,build-sets.pyandbuild-item-sources.pynow read seasonal ids too. Rebuilt and re-split at 1.15.9.69722:_seasonal/Classes.lua10 -> 2,894 rows, EquipStats 2,667, ItemLocations 676, Sets 287 -> 284 (1571, 1664 and 1955 now have one shipped member, below the two a set needs).itemdb_common.shipped_idsnow reusesread_names(..., include_seasonal=True)instead of its own pattern. Peer Review inbox62329f9dP1, P6. build-locales.pyleft the walk locale out of its default, so enUS's RandomProps stayed at the previous build while the other eleven moved on (provenance_specred). The default is now every locale; the walk locale's names are still never rewritten. Suffix tables are now written only for a flavour whose TOC loads them (Classic Era, TBC, Mists): Forever's client has none, so every Forever run used to fail 12 of 12 and could not exit 0. Peer Review inboxbc500fc0B1, B3.
Changed -- dev tooling
Tests/itemdb_common_check.pyasserts that the PythonWEAPON_TYPEtable (tools/itemdb_common.py) and its Lua copy inLibItemDB-1.0.luaagree; nothing did before.tools/build-score-model.py's docstring no longer says healing is worth 0.- New repo-scoped desk tool
itemdb-split-seasonal(runstools/split-seasonal.py <version>):itemdb-buildonly runsbuild-*.py, so the post-build seasonal split had no desk route. - New audit
Tests/buffs_ep_audit.lua <toc>, declared asbuffs-ep-vanillaandbuffs-ep-tbc: loads one TOC's library, Data and Scoring files and prices every row Dibs' Buffs tab can show (GetConsumableBuffsrows with an exclGroup; raid / world / totem auras at max rank) for all 37 class/specs, failing on any row at 0 EP that carries no percentage Dibs prices. A standalone process, not a spec, because LibStub refuses a second library load in one state. Rows carrying only magic resistance are listed as a known exception and do not fail it (Classic Era 10, TBC 13). It stays RED on one row in each flavour: Sayge's Dark Fortune of Damage (PCT_DAMAGE, which Dibs drops; reported to Dibs on inbox058b4bc68b2e). - New
Tests/seasonal_ids_check.py: finds every builder that writes a_seasonaloverlay (from the overlay's own "Auto-generated by" header) and fails if it reads its version's ids withoutinclude_seasonal=True. Red-checked by restoring the base-only call inbuild-classes.py. Tests/aura_buffs_check.pyparsed values as integers and crashed on TBC's 5.5; it now reads fractional values, and pins Sayge 23768 per flavour (Classic Era 10, TBC 5.5, Forever 10). It also checked thatbuild-effects.py's mastery bit agreed withbuild-aura-buffs.py'sRATING_BITS; that second table is now gone. Both builders read one table,itemdb_common.rating_groups/rating_keys, and the check compares every bit 0-31 on all seven versions in both directions, so a bit only one side knows (Retail Versatility 28-30) fails it (Peer Review c27e8a07 L5/S4). Wrath, Cata and Mists AuraBuffs rebuilt byte-identical.- New
Tests/effects_check.py(29 checks): per-version rating groups, resilience bits, Forever's 290/465 mapping, and that no Wrath/Cata/Mists row carries a rating and its_SPELL_twin. Red-checked by putting Wrath back on the split groups (5 failed, the twin rule on 43 rows). - The Mists compile spec is split by locale:
Tests/data_compile_mists_spec.luacompiles the TOC's non-locale files and the newTests/data_compile_mists_locales_spec.luaitsData/Mists/<locale>/files, through apartargument onTests/data_compile.lua(one side takesisLocaleFile, the other its complement, so every file is compiled exactly once). The single file hit the harness's 60 s per-file limit in one full run and passed alone; that overrun's cause is unknown (load from a concurrent run is the likeliest, not verified). Measured after the split in one full green run (919 passed, 0 failed,--times): 0.69 s and 2.25 s CPU, about 3 s together, so the unsplit file would likely fit too -- the split makes each half smaller; it is not shown to be what fixed the timeout. The 60 s limit was not raised.
[v1.3.0] - 2026-10-07
New -- full-scan progress events (MINOR 41)
LibItemDB_ScanProgress("full", scanned, total)now fires after every 500-listing batch of a full Auction House scan,itemnil. Asked by TOGBankClassic (inbox8cc455f1df85, the one gap left on LIBREQ-PRICE-005): full-scan progress was poll-only.Price/Scanner.lua's two batch loops (fullProcessLegacy/fullProcessModern, identical apart from the row read) are now onefullProcess(n, readRow), so the progress lives in one place.- The
/itemdbScan tab shows "Scanning the whole Auction House... 500/1203" once the list arrives (BuildScanStatus).
Fixed
GetScanState()reported the LAST TARGETED scan'sscanned/totalwhile a full scan ran, so the polling route the README offered gave stale numbers. Full scans now keep their ownS.fullScanned/S.fullTotal(listings), reset byStartFullScan;GetScanStatereads them whenkind == "full". Pinned inTests/pricescan_spec.luaandpricewindow_spec.lua.
Changed -- dev tooling
- Removed the
spec-timessuite from.writ-suites.json. It was the Lua suite re-declared with--quiet --timesonly because the desk'stestcall took no runner flags. Writ addedargs(inboxbb110f037d31, writ CHANGELOG 779), so a timing run is now{"tool":"test","args":"--quiet --times"}-- verified 2026-10-05, it prints one TIME line per spec file and exits 0. .vscode/settings.jsonis now tracked (commitbda440a): Lua 5.1 runtime, WoW API annotations and the diagnostics globals, so the editor's Lua checks match on any clone. Dotfile, so the packager prunes it.LibItemDB-1.0.luaSearch: annotatedlooseastable|nil(thea and b and cform typed it asfalse, acast-local-typewarning). No behaviour change.docs/Curseforge_Description.html: dropped the v0.4.6 and v0.4.7 Recent Updates blocks for one "older notes are in the changelog" line, taking the page from 87,124 to 82,606 characters -- below the largest page CurseForge has been seen to accept (85,514).
[v1.2.0] - 2026-10-04
New -- data for Wrath, Cataclysm and Retail
Requested by SmexyMats (inbox 926a233876bf), which generates its tooltip tables from ours and fell
back to a hand-made list on these three flavours. The operator chose "Ask ItemDB to build it".
- Cores. No Wrath or Cata client is installed, so neither has a walk; Retail has one since
2026-10-04 (below). Wrath 45,155 items, Cata 65,797, Retail 176,130.
- Wrath and Cata carry real stats from the new
tools/build-core-wago.py, which reads wagoItemSparse(StatModifier_bonusStat/bonusAmount,Resistances_0) and REFUSES to write unless it first reproduces the shipped walk-built stats of TBC (classic profile, applied to Wrath: 39,426 exact key matches, 0 unexplained) and Mists (modern profile, applied to Cata: 173,121, 0 unexplained). Every remaining mismatch is a listed reason (on-equip auras, whichbuild-equip-stats.pycovers; 11 named "Communal" items). Wrath 23,944 items with stats, Cata 30,403. Left out because no walk can confirm them: weapon DPS on both (ItemSparse damage reads about half the Mists walk's), all armor on Cata (the Mists walk disagrees with its own build'sResistances_0on 18,598 items), shield armor and school resistances on Wrath (the TBC walk never reports either).Tests/core_wago_check.py(45 checks, red-checked) pins the gate and the shipped blobs;build-core-wowsims.pynow refuses to overwrite a core this builder wrote. WoWSims wotlk/cata were the alternative and were not used: theirdb.jsonis a filtered subset and their stat enums would need a new key map and rating-divisor rework. - Retail is walk-built, with stats (
build-core.py Retail, from the operator's TOG Tools walk of the Retail client, 2026-10-04): 176,130 items -- 901 more thanItemSparse's 175,229, the items the client carries outside that table -- across 19 class files, names from the walk. Stats areGetItemStatson the linkGetItemInfo(id)returns for the bare id, so no bonus or upgrade ids: the base item (TOG ToolsModules/ItemDB/ItemDB.lua:266-275; Spirit Tender's Cuffs 175675: Intellect 4, Mastery 4, Stamina 6, Versatility 3, armor 3). It replaced the structure-only corebuild-core-wowsims.pywrote (ItemSparseholds onlyStatPercentEditorallocations and scaling curves, no flat amounts); that builder now refuses to overwrite it, by the existing walk-built header check. Reagents, Expansion, ItemLocations (121,347 items) and ItemPlaces were rebuilt on the new item set. No Retail scoring model yet. The walk also settles the "~38,000 missing items" question: TOG Tools walks to id 320,000 and keeps only ids the client has data for (GetItemInfoInstant), so 176,130 is every item the Retail client knows; the rest of the client's 213,415Itemrows have no data behind them.
- Wrath and Cata carry real stats from the new
- Pins.
FROZEN_BUILDgains Wrath3.4.3.58936(the last GLOBAL Wrath build; wago's 3.4.5 is the China Titan-Reforged line), Cata4.4.2.60895and Retail12.1.0.69933.PRODUCT_LABELmaps the productwowtoRetail, so the folder does not move when 12.x becomes 13.x. KNOWN COST: when the Anniversary client reaches 3.x the Wrath pin wins over its live build; move it then. - Retail slot tokens.
INVTYPEgains 27 and 29..34 (quiver, profession tool/gear, the four equipable-spell slots), read from the live client'sEnum.InventoryTypeand GlobalStrings. - Reagents for all three: Wrath 766 items, Cata 842, Retail 3,088 across 13 professions.
- Expansion for Wrath, Cata, Mists (new) and Retail. See Changed for the rule.
- ItemLocations (SmexyMats' follow-up, inbox
e9aefe20d418):- Wrath from CMaNGOS wotlk-db (23,256 items; the DB is now cached).
- Cata from SkyFire 5.4.8 (
mistsskyfire.sqlite, the post-Cataclysm world) through the full world-DB path: 31,975 items (gathered 826, drop 9,185, quest 8,476, vendor 9,986, pvp 271, plus crafted and reputation). Vendor stock is 5.4.8's, so a Cataclysm item whose vendor changed in Mists shows the Mists answer. - Retail from TrinityCore's world database, TDB 1210.26091 (client 12.1.0; GPL-2.0), fetched and
converted by the new
tools/fetch-trinitycore.py(desk toolitemdb-fetch), through the full world-DB path, then the AllTheThings passes below: 87,178 items in all. SkyFire stays a gathering-only supplement (GATHER_DB) for 170 old-world items TrinityCore links to no loot. TrinityCore's schema is detected by column throughout: lootItemType, per-difficultyLootID/SkinLootID/TypeFlagsincreature_template_difficulty,QuestSortID, gathering-node gameobject type 50, and per-expansion gathering LockTypes read by name ("Khaz Algar Mining 25"). The client-only mode this replaced was removed the same day. - Modern gathering from AllTheThings (MIT, pinned commit
c84276b52158, cached by the newtools/fetch-att.pythroughitemdb-fetch): TrinityCore has spawns and loot for few Dragonflight-onward nodes, so a further pass reads what ATT records under each gathering spell (Mining 2575, Herb Gathering 2366, the skinning spells, Fishing) and under prospecting and milling (salvagerecipeas well asheader), for items no world DB gave a gathered row. 642 items; per profession, Dragonflight 119, The War Within 84, Midnight 117, older 324 (a few items count under two). Never removes a row; prints any spell header under a gathering profession it does not read.Tests/att_gather_check.py(23 checks, red-checked). A rule from item category alone was measured first and rejected: only herbs matched the real source 98% of the time (metal and stone 76%, leather 72%, gems 78%). STILL UNCOVERED, Dragonflight / The War Within / Midnight reagents with no gathered or crafted row: gems 12 / 6 / 8, pigments 4 / 2 / 3, cooking meats 14 / 12 / 11 (mob drops TrinityCore lacks), cloth 6 / 15 / 8, metal and stone 4 / 2 / 2, plus 184 others. - Drops from AllTheThings (Retail): an item with no drop, gathered, vendor or quest row gets a
drop row from ATT's own records -- world drops (
03 - World Drops), zone drops inside a map header (02 - Outdoor Zones, named from the client's UiMap) and instance loot (01 - Dungeons Raids, through the same instance-name aliases). 10,443 items (Dragonflight 794, The War Within 328, Midnight 537, older 8,784); ATT names the creature for 2,746, the rest are ATT world or zone drops with no creature named. Vendor stock and an item's own contents are never read as drops, and an item the Encounter Journal already places by a boss is skipped (journal_boss_items, which ignores Cata's items-nilmerge call whose first table is the instance map).fetch-att.pynow stays on the recorded commit unless--update, and caches ATT'sMaps.luafor the map constants.Tests/att_drops_check.py(14 checks, red-checked). This gave 24 of the 37 uncovered Dragonflight-onward cooking meats and 15 of the 29 cloths a source. The later passes in the next bullet took the rest down to 12. - Every other origin AllTheThings and the client record (Retail; for SmexyMats' request
d95142e3, all additive, each record shape pinned inTests/att_drops_check.pyoratt_gather_check.pyand red-checked):- salvage as a gathered row: disenchanting -> Enchanting, crushing -> Jewelcrafting, scrapping -> Engineering;
- crafted: SpellEffect 294 ("craft loot") and 296 ("craft salvage item", names from TrinityCore's SpellEffects.cpp) besides 24 and 288 -- Chopped Mycobloom, Fresh Fillet and the Transmutagens are these -- plus ATT's crafted-items lists (2,893 items; the Wilderthread / Duskthread / Dawnthread / Weaverthread spools have no client recipe row at all, only ATT's);
- vendor stock, including profession supply vendors filed under a profession header in a zone (rule checked against one real file);
- quest REWARDS and reward headers -> the zone, else "Quest" ("Crafting Orders" for patron rewards). Quest-objective items, which ATT tags "(QI!)" in three shapes, never get a quest row: that removed 4,632 items' rows;
- treasures, objects and container contents (Airy Soul -> "Docile Airy Soul"), and instance loot ATT keeps outside the Encounter Journal;
- Midnight housing lumber as object drops per map (ATT names no profession for it).
- item constants: ATT writes some items by name,
i(ARTISANS_ACUITY);fetch-att.pynow also caches ATT'sConstants/Items.luaandatt_source_rows(..., items=)resolves them through it. The pass also reads21 - Holidays, and a holiday file's row with no map names the holiday ("Darkmoon Faire" for Artisan's Acuity, a Darkmoon profession-quest reward). - Fixed: a Companion Pet, Mount or Mount Equipment item (Miscellaneous subclass 2 / 5 / 6) is
no longer read as a container, so items nested under one in ATT no longer "drop from
Reins of the Quantum Courser" (
ATT_NOT_CONTAINER; 3 items' rows). - Blizzard's own collectable sources (
client_source_pass, before ATT's drop pass, so an item they place keeps ATT out): the client tablesCollectableSourceInfo+CollectableSourceVendorSparse/QuestSparse/EncounterSparserecord a vendor, quest or Encounter Journal boss for every collectable appearance (96,900 / 15,172 / 47,320 rows at 12.1.0.69933). Vendor rows for 12,629 items, quest rows for 7,189, boss-drop rows for 2,121; Retail locates 96,447 items, up from 88,842. A vendor or quest in an instance's map reads that instance's name -- which for an open-world map is its Journal world-boss tier, so a Valdrakken vendor reads "Dragon Isles" -- else "Vendor" / "Quest". Each item comes from its APPEARANCE (ItemModifiedAppearance.ItemID), never from the vendor table'sVendorItemID: on the Mining Pick's trade-supplier rows that column names Lasherweave Raiment, which Wowhead lists as not available to players.Tests/att_drops_check.pypins the join. - The same vendor rows feed Retail's "Where to get it" (
build-item-places.pyclient_vendor_spawns/add_client_vendors, Retail only): 15,960 items gained a vendor, and 1,281 vendors TrinityCore never spawns are placed at the client's own position (Provisioner Thom, Valdrakken's Blacksmithing Supplies, at 2112: 36.6,50.6). A creature TrinityCore spawns keeps its spawns; one with no name there is skipped.Tests/item_places_check.pypins the merge. - Quest givers in "Where to get it" (MINOR 40): a new place token
q<creature>:<questID>fromCollectableSourceQuestSparse;GetItemPlacesreturns it askind = "quest"withquestID(no chance), after objects, at most 4 per item (MAX_QUESTS). 7,453 Retail items gained a quest giver, 583 givers placed at the client's position alone. Checked: quest 58166 "Unknown Assailants" (Tirna Vaal) rewards 175675-175677, which carryq160439:58166, the giver on Ardenweald's map; Wowhead did not name the starter, so the giver is not verified.BuildWhereRowsshows it as a "Quest" row, applies the vendor faction filter to it, and no longer repeats the generic "Quest" location line when a giver row covers it. An older library skips the token. Specs:libitemdb_spec.lua,where_spec.lua,item_places_check.py.lib.PLACE_KIND(the token letters) is now public, read-only, so a consumer decoding tokens itself need not keep a copy that misses the next letter -- SmexyMats kept one (Peer Review, thread 8acd9f23). - The compile spec is one file per TOC (
Tests/data_compile_<flavour>_spec.lua, body inTests/data_compile.lua). Measured with the newspec-timessuite (run.lua --times, NOT A GATE): the singledata_compile_spec.luatook 69 s CPU and hit the harness's 60 s per-file limit part-way through Mists, so the TOCs after it never compiled -- a pass that depended on the machine's speed. Now the slowest, Mists (12 large Names files), takes 18-26 s.provenance_spec.luaread its-- sourceand-- client buildstamps by walking every line of every Data file; it now reads only the comment header they live in (22 s -> 11 s). - Every Retail item, measured (
report_unplaced, printed by every Retail build): of 175,229 shipped items, 135,314 (77%) now have a source -- ItemLocations or an Encounter Journal boss drop (19,049,Sources.lua, which an earlier count wrongly left out). Of the 39,915 without one, AllTheThings files 10,971 as never implemented and does not list 18,854 at all; 10,090 it lists and no pass reads yet (class-trial / boost / starting gear 1,844 is left out on purpose; the rest is listed by folder in the build output). - Folder sources (
att_folder_pass,ATT_FOLDER_SOURCES): an item no record-shape pass placed takes its source from the ATT folder it is filed in.08 - PvPwas never read at all -- 12,396 items, whole season files (Legion Season 3 Cruel.lua:1121, Cruel Gladiator's Ring -> "PvP") -- plus Trading Post 951, instance files (the file's own name) 928, holidays 784, Delves 470, Group Finder 439, In-Game Shop 258, Black Market 57. A file named for something that is not a place (anAchievements.luainside these folders) is skipped, so achievement rewards do not read "Achievements" as where they drop. Zones, events, expansion features and character files go by their PATH (att_path_row: the folder is the place, the file name below it the kind -- Quests, Vendors, Gear Conversion, Rares, Dungeons Raids; achievements, boosts and files naming no kind are left alone): events 2,335, zones 963, expansion features 548, Heritage 210. Onlyi(<id>)counts, never a{ "i", id, n }cost; quest objectives and never-implemented items are skipped. - Quest objectives (ATT's "(QI!)") with no other source now read their quest's zone
(
add_objective_rows), 4,064 items -- e.g. V-300 Serial Plate -> Tiragarde Sound. Earlier today they were given no row because "rewarded by a quest" is wrong for them; for an item nothing else places, "got during a quest in that zone" is the true answer, and an objective that also drops or is sold keeps only those rows. - Items missing from the database itself: the client's
Itemtable has 213,415 rows andItemSparse(names, details -- what Retail ships) 175,229, so 38,186 client items are not shipped.Tests/item_coverage_report.py(run_check) breaks them down: AllTheThings marks 17,262 never implemented and does not list 19,890; nine Wowhead samples were all 404s or placeholders ("[PH] Plate Chestguard of the Rising Dawn"). 1,034 it lists as real items (quest 377, armor 372, miscellaneous 149) -- not shipped yet, because the client carries no name or details for them. The Dragonflight-onward reagents with no row at all went 152 -> 11, each with a reason: Fang Adornments is never-implemented; two Shadowflame crests were a fragment exchange removed in 10.2.0; three Amirdrassil prototypes are quest objectives; Smudged Lens, Soul Sigil I and II and Spark of Tides are listed by ATT with no source and made by no client spell; Temporal Vestigial has only a description. The client's QuestPackageItem (quest reward packages) names none of the 11, and CurrencyContainer holds currencies, not items.itemdb-schemagained a row filter for that,Table:Column=v1|v2, inside its table field so its command and approval stay unchanged.
- Prospecting and milling are gathered rows now, like disenchanting ("Enchanting"):
prospecting_loot_template-> Jewelcrafting andmilling_loot_template-> Inscription, read wherever the world DB has the table. TBC 32 / 0, Wrath 51 / 15, Cata 58 / 15, Mists 71 / 17, Retail 166 / 57 (with ATT); Vanilla has neither table.fetch-trinitycore.pyconverts both now, and re-converts when its cache lacks a listed table: the skip used to compare only the release tag, so a cache built before a table was added counted as current. - Vanilla ItemLocations was rebuilt for the first time since v1.1.4 and gains that release's fixes, which had only reached TBC and Mists: reagents such as Arcane Powder, Ankh and Symbol of Kings read their PvP Barracks and ordinary vendors instead of "Alterac Valley", and the four battleground healing draughts read Vendor. An orphan row for item id 1 (in no Vanilla item file) dropped out.
- Sources (instance drops):
- Wrath from AtlasLoot (5,522 items).
- Cata: AtlasLoot up to Wrath (5,511 items) plus 20 instances from the client's Encounter
Journal through the new
tools/build-sources-journal.py: 16 new Cataclysm instances, and the 4 instances Cata revamped (Deadmines, Shadowfang Keep, Zul'Gurub, Zul'Aman) folded into the AtlasLoot entry instead of listed twice. ORDER MATTERS:build-sources.py Catarewrites both files with AtlasLoot only, sobuild-sources-journal.py Catamust run after it. - Retail, all from the Journal: 213 instances (142 dungeons, 70 raids), 1,138 encounters, 19,049 items. 14 bosses the Journal lists twice in one instance are merged into one; same-named instances get a suffix (Blackrock Depths, Magisters' Terrace).
- DropRates for Wrath (7,956 items) and Mists (7,561). Mists reads SkyFire, whose loot rules
differ from CMaNGOS: a reference row rolls its own chance and processes only its group, and rows
without lootmode bit 1 are hard-mode only (skipped). The builder picks those rules from the
table's columns, not the version name. Wrath also merges
DifficultyEntry1..3heroic loot, which the oldHeroicEntry-only read missed. - ItemPlaces ("where to get it") for Wrath (17,264 items, CMaNGOS) and Cata (21,078, SkyFire), with 8 locales of place names each, and Retail (23,802 items) from TrinityCore's world database with 8 locales of place names. SkyFire was ruled out for Retail first: its world is pre-Legion and Retail has rebuilt much of it (Undercity, Teldrassil, Silithus).
- RepLoot for Wrath (964).
- TOCs.
ItemDB_Wrath.tocandItemDB_Cata.tocnow load their data (they were deliberately empty placeholders). NewItemDB_Mainline.toc(Interface 120100) loads the whole addon -- Integrations, Tooltip, Price, Where and the bootstrap, with the same dependencies, OptionalDeps and SavedVariables as every other flavour -- and no Scoring file, since Retail has no stats. It shipped data-only at first; the operator asked for the full addon the same day ("we also need to create a retail .toc for you, so you work in retail"). Every module already feature-detects the modern client (data-driven tooltips viaC_TooltipInfo,C_AuctionHouse,C_MerchantFrame;HandleModifiedItemClickexists in the live UI source). Loaded on a Retail client with TOGProfessionMaster, no errors, after the Names.lua fix (operator, 2026-10-04: "togpm is running with no errors, and before it had a bunch"); the tooltip lines, price window and "Where to get it" window have not each been exercised there yet.Tests/toc_spec.luanow treats all seven manifests as full.wow-version-replication.ps1replicates to_retail_too.ItemDB_Mists.tocloads its newExpansion.lua.
New -- scoring and side tables for Mists, Wrath and Cata (parity with TBC)
The operator asked for parity so addons like Dibs work on every flavour ("lets get to parity").
- Scoring. New
Scoring/Mists.lua,Scoring/Wrath.lua,Scoring/Cata.lua, each loaded by its own TOC only (Tests/toc_spec.luanow expects them). Rating conversions at each level cap from WoWSims'base_stats_auto_gen.go(Wrath 80, Cata 85, Mists 90). Spell power is counted once (the data stores "+N spell power" as an equal damage and healing pair). Cata and Mists skip the stats patch 4.0.1 removed (defense, armor penetration, block value, ammo, feral AP); only Wrath registers the ammo scorer. Death Knight (6) and Monk (10) score through their weights. Specs:Tests/{wrath,cata,mists}_scoring_spec.lua(48 tests); 100% coverage on all three. - BiSWeights for Mists (45 specs, 11 classes incl. Monk), Wrath and Cata (41 specs, 10 classes incl. Death Knight), from the Wrath/Cata/Mists branches of HawsJon's Pawn scale. The builder now refuses any Pawn stat that is neither mapped nor explicitly ignored. Not priced: weapon Speed (Marksmanship's top Mists weight), resistances, school spell damage, Hp5, feral AP.
- Side tables. Mists: Factions, Mounts, Sets, ReqLevels, VendorPrices, SellPrices, Classes, EquipStats, UseEffects, BiS, ScoreModel, BiSWeights, DropRates. Wrath and Cata: Effects, Talents, TalentEffects, EquipStats, Sets, Factions, Classes, Races, ReqLevels, VendorPrices, SellPrices, UseEffects, ScoreModel, BiSWeights, Mounts, Hidden; Wrath also SpellSchools.
- AuraBuffs for Wrath (44 buffs), Cata (35) and Mists (31), each from that expansion's OWN raid
buffs: a curated spell-id list from WoWSims wotlk / cata / mop, every amount read from that
build's SpellEffect, and the build refuses when a curated id's name does not match. The first
cut emitted the Classic list (Wrath's Warchief's Blessing read as +500% damage, Trueshot Aura as
a flat 300 AP) and was not shipped. Cata's resistance buffs ship at 195 / 97 from the game's own
spell tooltips. A buff that fills TWO raid-buff categories (Mists Arcane Brilliance: spell power
AND crit) now competes in both, through the new
GetAuraSlotsand theslotsfield onGetAuraBuff/GetAuraBuffsranks (MINOR 39, additive): Arcane Brilliance with Leader of the Pack is 5% crit, not 10%.Tests/aura_buffs_check.pypins the values per version. - Gems, ConsumableGroups and BiS sets for Wrath and Cata; ConsumableGroups for Mists. BiS now covers all 11 Mists classes, and an earlier expansion's gear is no longer listed as BiS for a later one. ScoreModel gains Death Knight and Monk constants.
- Not shipped, on purpose: Mists Talents (tiers and specialisations, not trees).
- New desk tools (all repo-scoped):
itemdb-worlddb(tools/world-db-query.py, one read-only SELECT against a cached world DB, SQLitemode=ro),itemdb-tarball(tools/tarball-read.py, reads inside a cached WoWSims tarball without extracting),itemdb-fetch(tools/fetch-trinitycore.py) anditemdb-build-pinned(a builder at an explicit--wago-build).
Fixed -- found while building the new flavours
- Mists spell effects were wrong, some badly (shipped). Two defects of mine in
itemdb_common.spell_effect_base_points:- Every builder adds
+1to a SpellEffect's base (Classic,EffectDieSides1). Cata 4.4.2 and Mists 5.5.4 storeEffectDieSides0, so every value read one high (Flask of Endless Rage 181 on Cata against 180 on Wrath). - MoP stores many amounts as
Coefficienttimes the client's spell-scaling game table with a base of 0. Flask of Spring Blossoms shipped asITEM_MOD_AGILITY_SHORT=1; it is 1000. The multiplier comes fromgametables/spellscaling.txt(wago CASC file 1391660) at the version's cap, clamped to the spell'sMinScalingLevel..MaxScalingLevel(Spring Blossoms caps at 85: 1.9646 x 509 = 1000). Spells scaled by ITEM level (SpellMisc.Attributes_11 & 0x4, e.g. Relic of Xuen) keep their stored base.Tests/spell_scaling_check.pypins four flasks, Battle Shout and Relic of Xuen (red-checked twice). Mists Effects lost 41 items whose only "effect" was that bogus=1. Vanilla, TBC, Wrath and Forever resolve nothing and do not change.
- Every builder adds
- Wrath item procs never got their CMaNGOS rates.
build-proc-rates.pywroteproc_rates_wotlk.json;build-use-effects.pyreadproc_rates_wrath.json. Both names now come from one helper inbuild-proc-rates.py. Wrath scored procs 111 -> 313. - WoWSims proc rates were found only for Forever.
build-use-effects.pyandbuild-sets.pyderived the repo from the version name ("tbc", "mists"; real:tbc-new,mop); they now readWOWSIMS_REPO. No shipped count moved. - build-talents.py dropped Death Knights (a Vanilla class filter), read Cata's dead talent tabs
(negative
OrderIndex: "(4.0 OLD)") and wrote header-only files for a version with no trees. All three fixed; it now refuses rather than writing an empty file. - build-races.py read only the
AllowableRace_0/_1halves, so Wrath and Cata (one signed 32-bit column) read every item as unrestricted. - Retail loaded no item names at all.
Data/Retail/enUS/Names.luaput 175,229 names in one function, past Lua 5.1's 262,143 constants per function, and the client refused it ("constant table overflow", the operator's in-game report).itemdb_common.write_namesnow splits any table over 60,000 names into blocks, each in its own local function (each gets its own constant table; the[id]="name"lines are unchanged, so every reader still parses them). Files at or under that size are written exactly as before. NewTests/data_compile_spec.luacompiles every file each TOC loads under Lua 5.1, the client's own limit; it was red on exactly that file. - Retail counted no Dragonflight-onward recipe as crafting.
build-item-sources.pyknew only SpellEffect 24 (create item); modern recipes use effect 288, whose misc value is aCraftingDataid (2,450 rows at 12.1.0; its items come fromCraftedItemIDand oneCraftingDataItemQualityrow per quality tier). Effect 301 is enchanting (CraftingData Type 3, no item) and is not read. Retail ItemLocations 34,641 -> 38,048 items. - Weapon haste procs were never scored, on every flavour (found by Peer Review).
build-use-effects.pyread anItemSparsecolumn namedDelay, which no build has; the column isItemDelay. It exited 0 with no swing speed for any weapon. It now readsItemDelayand refuses to write when weapons carry damage but no speed. Scored procs: Vanilla 187 -> 190, TBC 235 -> 237, Wrath 313 -> 315; Cata, Mists and Forever rebuilt the same way. - Socket scoring was copied into four Scoring files and is now one shared scorer
(
lib.StatScorers.sockets), which also fixes gem prices going stale after new gem data loaded. The meta gem bonus (META_SOCKET_EFFECT) is now priced on TBC, Wrath, Cata and Mists. - Weapon speed is scored on Mists: Marksmanship values ranged speed, and Retribution and
Assassination value melee speed, now recorded as
MELEE_WEAPON_SPEEDbybuild-equip-stats.py(ranged keepsWEAPON_SPEED). A hunter's speed weight never counts a melee weapon and a melee spec's never counts a bow.Search { stat = "WEAPON_SPEED" }matches both keys, asstatalways matched a fragment (README). - build-hidden.py missed
QATest.../QAEnchant...names (its^QA\bpattern needed a word break after "QA"); about 100 test items per version were served. Vanilla hides 57 more. - "Where to get it" placed Dalaran's vendors in Crystalsong Forest on Wrath, Cata and Mists
(Dalaran is a dungeon-style map at 3.4.3); they now show on the Dalaran map.
build-item-places.pyalso reads the conditions table's negation flag now; no shipped "conditional" tag changed, because every affected drop already carried one. - Cata Sources:
build-sources.py Catanow runs the Encounter Journal merge itself after it writes, so rebuilding the AtlasLoot half can no longer wipe the Journal half (Tests/sources_order_check.py). Nine Cata bosses listed twice in a folded instance are merged, and Zul'Gurub and Zul'Aman are labelled dungeons. KNOWN COSTS: the Cata Journal loot of Venoxis and Mandokir sits under their Vanilla NPC ids (14507 / 11382); the Vanilla AtlasLoot bosses still list beside the Cata ones in the four folded instances; Retail's Jadefire Masters keeps one row for its two faction versions, since a boss row has no faction field.
Changed -- one profession resolver, which understands Retail's expansion child lines
Retail splits each profession into per-expansion skill lines ("Midnight Alchemy" 2906 is a child of
Alchemy 171) and hangs recipes off the child, and its CategoryID 11 also holds minigame lines
("Abominable Stitching", "Supply Shipments"). The first Retail run reported 29 "professions".
itemdb_common.profession_lines(build) now walks ParentSkillLineID to the root and, on a build
that gives any profession a SpellBookSpellID, keeps only roots that have one (Classic Era sets it
to 0 everywhere, so there the filter is off). build-reagents.py and build-item-sources.py
(craft_professions) both read it, so "used by" and "made by" cannot disagree; they were two
copies that had already diverged.
build-reagents.py also reads the ModifiedCrafting* tables on 9.x+ builds: modern recipes take
quality materials and optional reagents through crafting SLOTS, not SpellReagents. Before this,
Bismuth (210930) listed only Mining; it now lists Blacksmithing, Engineering, Jewelcrafting and
Cooking, all from the client's own slot data (Cooking recipes 445482 and 1245993-1245995 take the
"Common Ore" category; not confirmed in game).
build-expansions.py runs the first-ships-it chain through Vanilla, TBC, Wrath, Cata and Mists;
Retail continues with its own ItemSparse.ExpansionID (5 Warlords .. 11 Midnight; the numbering is
the live Encounter Journal's tier table, Blizzard_EncounterJournal.lua:106-122) and lists items
where that is unset too under -1. GetExpansion returns nil for -1 rather than a number that is
not an expansion; the tooltip's Expansion line now names Wrath through Shadowlands in SmexyMats'
colours (SmexyMats/Utilities/Utilities.lua:29-38). The default bucket is now the largest one,
which keeps Vanilla's and TBC's files unchanged in shape.
Instance names in Sources come from the client's Map table (via the block's InstanceID and the
same INSTANCE_NAME_ALIAS item-sources uses) when the key is not in SOURCE_INSTANCE_NAMES, a
map no other block shares, and the name does not collide with another instance.
Fixed
- Malformed instance names on TBC and Mists. Every AtlasLoot key outside the hand-kept Vanilla table was humanized from its CamelCase key, which shipped "Hallsof Lightning", "Trialofthe Champion", "Ahn Kahet", "Gruuls Lair", "Zul Aman" and "Magisters Terrace". They now carry the client's names ("Halls of Lightning", "Ahn'kahet: The Old Kingdom", "Gruul's Lair"). Consumer- visible: several TBC instances now read with the client's prefixes ("Coilfang: The Slave Pens", "Hellfire Citadel: Ramparts", "The Sunwell"). Vanilla is unchanged.
- Mists listed Pandaren cooking specialisations as professions. "Way of the Grill" and its five
siblings shipped as six separate professions in
Data/Mists/_core/Reagents.lua; they are now folded into Cooking (same 969 items, 11 professions instead of 17). - TBC Expansion.lua was stale against the current Vanilla and TBC cores (17,568 / 12,516); regenerated with unchanged logic: 17,490 Classic / 12,542 TBC.
- Expansion.lua stamped a build it never read. Every flavour's file carried the live client's
client build, though only Retail reads a DB2 (the rest derive from the shipped item sets). Once the Era client moved to 1.15.9.70003 that put a second build into Vanilla's core group andprovenance_specwent red. Non-Retail files now sayclient build: none readand carry asource tools/build-expansions.py sha256:line (the rule lives in the builder, as with ScoreModel.lua); Retail keeps its build stamp too. Vanilla'sReagents.lua, regenerated only to prove the shared profession resolver left it unchanged, went back to HEAD for the same stamp. Vanilla's count now reads 17,604 (was 17,682), matching the class cores today; which change moved it was not traced. - Three builders called a validating helper on a branch (
build-expansions.py'sshipped if ... else core_ids(v),build-item-sources.py's client-only path,build-sources.py's comprehension), whichitemdb_common_checkrefuses. Each call is now unconditional. build-rep-loot.pywrote an empty file when it had nothing (Cata); it now writes nothing and removes a stale one.
Tooling
tools/wago-schema.py+ theitemdb-schemadesk tool (approved 2026-10-04): print a wago DB2 table's columns, row count and first row at a version's pinned build. It replaces throwaway shell probes; the operator's directive the same day is that tool gaps are fixed, not shelled around.- The
itemdb-builddesk tool now lists the new versions in its help, through Writ'stools updateaction (shipped on request, inbox8aa52bc3e697), with a 600-second limit. - New
Tests/constant_headroom_check.py: fails when any shipped Data file's busiest function passes 80% of Lua 5.1's 262,143-constant limit -- the warning beforedata_compile_spec, which only fails once the game already refuses the file. Measured today: the largest unsplit files are Mists' Names.lua at 62% (they split on their next rebuild, being over 60,000 names), then Retail's Expansion.lua at 57%. Peer Review suggested one shared chunked writer for every builder; not done, because no other file is near the limit and the builders cannot all be re-run here to prove an unchanged output -- the check names the writer to split when one gets close. - Harness pin
b1d9bd5->407bd26: an error escaping the runner is now a named FAIL with a traceback instead of a bare(error object is not a string), which is whatdata_compile_spec.luahit. Adoption asks only for the pin; the suite is green on it. itemdb-schematakes its table list separated by commas OR spaces; a space-separated list used to reach the fetch as one table name and crash on the space in the URL.- CLAUDE.md: builders run through
itemdb-build, not the shell; that paragraph used to say the opposite.
[v1.1.4] - 2026-09-29
Fixed -- ordinary vendor goods read "PvP" on TBC and Mists
Refreshing Spring Water (159) showed only "Alterac Valley / PvP" as its source on TBC, with none
of its innkeepers, vendors or quests (SmexyMats peer review 60e304d70cab, found while generating
its tooltip data from ours). The cause was in tools/build-item-sources.py: the battleground
reward-vendor pass REPLACED an item's whole location set with the battleground whenever a
BG_VENDORS NPC sold it, exempting only ammo. TBC's Stormpike Quartermaster (12096) stocks common
food, water, reagents and flasks, so every one of those lost its real sources. The pass now skips
any item a vendor outside a battleground also sells (sold_outside_bg).
- TBC: about 60 items corrected (water, bread, flasks, reagents, arrows), e.g. 159 now reads Vendor and Quest. Item 1 (a placeholder id, "World Drop") also left the file on this rebuild.
- Mists: about 70 items corrected, the same pattern.
- Vanilla: unaffected -- the classic DB's quartermaster stocks none of them, and 159 already
read Vendor and Quest. Not rebuilt:
.build.infono longer lists the Era product, so the builder needs--wago-build, which the desk'sitemdb-buildtool cannot pass. - A trade-off, stated: the battleground-only healing draughts (17348, 17349, 17351, 17352) on TBC and Mists, and the Warsong tabards (19505, 19506) on Mists, now read Vendor instead of their battleground, because the CMaNGOS / SkyFire DB has a vendor outside a battleground selling them.
Changed -- the "Which click" chooser is LibAceGUIWidgets' capture widget
The /itemdb Tooltip tab's "Which click" control is now LAGW-ClickBinding (LibAceGUIWidgets
MINOR 38, built for this at TOGProfessionMaster's request, contract e0f3923e3548): the player
clicks it, then performs the combination. It has a Default button that restores Alt + Left Click,
read from the tooltip defaults through a new private lib:_GetTooltipDefault(key). It also allows
the middle and extra mouse buttons, which ParseClickBinding already accepted. On an older
LibAceGUIWidgets the dropdown of 14 combinations stays. Both store the same canonical string, so no
saved value changes. win.clickDropdown is renamed win.clickChooser.
Changed -- test harness pinned at b1d9bd5 (was 20453ac)
From 9c6c874 on, the harness's libs.load("LibAceGUIWidgets-1.0") includes the ClickBinding file,
so the spec no longer loads it itself. No other Adoption entry in that range needed a change here;
the suite is 777/777 at the new pin. The new pkgmeta.lua checker (a desk tool from 6eb30e0, asked
for on inbox d6eb270e8b4f) reports no FAIL on .pkgmeta. Its three WARNs were left as they are:
the "*.sh" and "*.bat" ignores match no tracked file today, but they are guards against a
future script shipping; and curseforge-project-id: is not needed in .pkgmeta because every TOC
carries X-Curse-Project-ID: 1558208.
Changed -- release docs
README: the source-locations pipeline paragraph states the battleground-vendor rule, and the
tooltip-settings row says whereClick is set on the LAGW-ClickBinding control (dropdown on an
older LibAceGUIWidgets). The CurseForge page gains the v1.1.4 notes. Checked against both files
for this release's changes: no item count, build number or feature row they quote moved.
Fixed -- a stale comment about VersionCheck-1.0
ItemDB.lua and CLAUDE.md said VersionCheck-1.0 is Classic-only. Its TOC declares Retail too
(120005, 120007, 120100); both comments now say so (SmexyMats peer review 34ee062fd221).
Older releases (v1.1.3 and earlier) are in CHANGELOG_ARCHIVE.md. Nothing was edited or dropped, only moved.
All Relations
- All Relations
- Embedded Library
- Optional Dependency
- Required Dependency
- Tool
- Incompatible
- Include

