SDK 4: Warcraft catalogs and migration
SDK 4 connects live match objects to the Warcraft configuration and artwork for
that exact patch. Import the optional @w3booster/sdk/game-data entry point.
Catalogs and images are loaded from the static service; they are not bundled into
your app or the SDK npm package.
SDK 4 uses protocol 4. Update the recorder, API, compositor and application
bundles together. SDK 3 bundles cannot consume the changed contract. Install
@w3booster/sdk@4, update your lockfile, rebuild your app and run its tests.
Your generated application binding needs regeneration if its definition changed.
Instance, type and catalog identities
| Value | Meaning | Join |
|---|---|---|
match.id + unit.id |
One observed instance in one match | UI keys and live updates |
unit.typeId |
Case-sensitive unit or hero rawcode | data.units.get(unit.typeId) |
ability.typeId |
Actual ability rawcode, including variants | data.abilities.get(ability.typeId) |
upgrade.typeId + upgrade.level |
Upgrade rawcode and one-based level | data.upgrades.get(typeId)?.levels[level - 1] |
hero.inventory[index] |
Item rawcode in that inventory slot; empty string means empty | data.items.get(typeId) |
match.gameDataId |
Exact immutable generated dataset | loadGameData(id) |
match.gameVersion |
Observed Warcraft build version | Display and diagnostics |
Treat IDs as strings. Preserve rawcode case; do not strip ability variants or append a research level to an upgrade rawcode. Level zero is unlearned, not an alternative encoding for an ultimate ability.
Load once for the current revision
Load match.gameDataId when the revision changes, rather than on every state tick.
Use an AbortController for your component/runtime lifetime. When a request
finishes, verify that its revision still matches the current match before rendering
it. The loader caches successful immutable results; failed and aborted loads can
be retried. It validates the manifest, catalog checksum and game identity.
An absent revision or failed load does not make live state invalid. Retain live
health, mana, queues and other observations, and omit unavailable static decoration.
Never load current.json or a nearby patch as a fallback. Custom maps may override
standard melee configuration; observed values remain authoritative.
import type { MatchState, Unit, ActiveUpgrade } from '@w3booster/sdk';
import { loadGameData, abilityCooldownsForState } from '@w3booster/sdk/game-data';
export async function catalogPanel(state: MatchState, unit: Unit, upgrade: ActiveUpgrade) {
if (!state.match.gameDataId) return undefined;
const data = await loadGameData(state.match.gameDataId);
const graphics = state.match.isReforged ? 'reforged' : 'classic';
return {
cost: data.units.get(unit.typeId)?.cost,
supply: data.units.get(unit.typeId)?.supply,
icon: data.assets.unitIcon(unit.typeId, { graphics }),
research: data.upgrades.get(upgrade.typeId)?.levels[upgrade.level - 1],
researchIcon: data.assets.upgradeIcon(upgrade.typeId, { graphics, level: upgrade.level }),
cooldowns: abilityCooldownsForState(state, data),
// Current observed values stay authoritative.
hitpoints: unit.hitpoints
};
}
// Sort a copy; SDK arrays and objects are immutable. Unknown cost stays last.
export function unitsByCost(units: readonly Unit[], data: import('@w3booster/sdk/game-data').GameData): Unit[] {
const compareCost = (left: number | undefined, right: number | undefined) =>
left === undefined ? (right === undefined ? 0 : 1)
: right === undefined ? -1 : right - left;
return [...units].sort((a, b) => {
const left = data.units.get(a.typeId)?.cost;
const right = data.units.get(b.typeId)?.cost;
return compareCost(left?.gold, right?.gold)
|| compareCost(left?.lumber, right?.lumber)
|| (a.typeId < b.typeId ? -1 : a.typeId > b.typeId ? 1 : 0);
});
}
Costs, supply and default stats
A unit definition exposes cost.gold, cost.lumber, supply.used,
supply.provided, baseStats, buildTimeSeconds, isBuilding, and relationships
in abilities, heroAbilities, upgrades, trains and builds.
Fields such as cost or a base-stat value can be absent. Unknown is different from
zero: a valid zero-supply unit must not be treated as missing.
Choose a sorting policy explicitly. The checked example above sorts by gold,
then lumber, with unknown costs last and typeId as a deterministic tie-breaker.
There is no universal gold-to-lumber conversion. For group totals, multiply known
per-unit costs by group size; do not silently turn missing costs into free units.
await data.unitGameplay(typeId) separately loads the broader retained source
configuration. It returns source-section dictionaries of strings. It is optional,
not a second live-stat source or a promise of every World Editor field. The
retained field policy
describes the extraction and size budgets.
Artwork, upgrade levels and cooldowns
Choose graphics: 'classic' | 'reforged' from match.isReforged. Use
data.assets.unitIcon, abilityIcon, itemIcon or upgradeIcon with the exact
rawcode. Upgrade icon options take a one-based level; levels arrays use
level - 1. The research and inactive roles select source-configured art.
Single source frames may be shared across levels; ordered source frames retain
their level-specific selection. Do not construct filenames or force a level suffix.
Unknown objects and objects without configured art return undefined. Hide the
image when there is no URL; do not emit <img src="undefined"> or guess a Cancel
icon. Country flags and Battle.net badges remain in @w3booster/sdk/assets.
Upgrade definitions supply category, maxLevel and per-level name, gold/lumber
cost and research time. Use those values instead of a manual weapon/armor table.
Ability definitions supply per-level cooldown, mana cost, cast time and range.
abilityCooldown(ability, match.gameTime, data) and
abilityCooldownsForState(state, data) derive standard-game cooldown estimates
from the actual ability level and observed activation. Use game time so pauses and
replay speed remain correct. Buffs or custom-map modifications can differ from
the base configuration; unknown cooldowns remain unavailable.
Breaking API changes
| SDK 3 | SDK 4 |
|---|---|
HeroAbility.name |
HeroAbility.typeId |
Upgrade name, aliases or level suffixes |
typeId plus separate level |
/standard-game/icons, icon filename tables |
/game-data and data.assets |
/standard-game/cooldowns |
Cooldown helpers in /game-data, requiring the exact catalog |
normalizeUpgradeRawcode |
Preserve the actual typeId |
isWeaponOrArmorUpgrade, weaponOrArmorUpgradeRawcodes |
Catalog upgrade category and maxLevel |
/standard-game still supplies game conventions such as race/mode information,
hero experience, game-time formatting and team ordering. It no longer supplies
manual object configuration. Native applied HUD scale is forwarded as the
0.5–1.0 multiplier in gameContext.hudScale; apps must not reinterpret it as the
retired raw 0–128 value.
See the API reference for every exported type and method. Test catalog load failures, unknown art, ability variants and multi-level upgrades when migrating an existing app.
SDK 4.1: item cooldowns and building upgrades
SDK 4.1 adds two optional live fields without changing protocol 4.0. Hero.inventoryCooldowns matches inventory slots by index and contains direct game timer observations or null. Building.upgrade contains the destination typeId plus the existing TimedProgress fields. The current building keeps its typeId until completion; cancellation or completion removes upgrade. These additions are available through both cloud and local transports. Existing SDK 4.0 consumers can continue running.
Use the destination ID with the current catalog to get its name and artwork. Progress is a completed fraction from zero to one; remaining and total seconds are game-time snapshots and can be null when unreadable. Do not advance them with wall-clock time. Inventory cooldowns follow hero visibility; building upgrade progress requires building and production access.
SDK 4.2 observations
APM, mana recovery, hero combat totals, local-player resources and stats provenance are additive in SDK 4.2. The guide also summarizes every change since the SDK 3 announcement.