SDK 4.2: more observations for your apps
SDK 4.2 is an additive update for SDK 4.x, using protocol 4.0. Install
@w3booster/sdk@4.2.0, update your lockfile and rebuild to use the new fields.
The matching recorder and platform supply the observations; installing the SDK
alone cannot create data that the producer does not provide.
Changes since the SDK 3 announcement
| Release | What changed |
|---|---|
| 3.1 | isIllusion, selectors that exclude illusions by default, deterministic instance ordering, and Hero.heroOrder for Warcraft's hero order. |
| 4.0 | Build-specific /game-data catalogs and generated artwork; actual ability/upgrade typeId values; level-specific definitions; applied HUD scale. Old manual catalog/icon helpers were removed. Protocol 4 is breaking for SDK 3 apps. |
| 4.0.1–4.0.2 | Typed settings compatibility in cooldown helpers, TypeScript 5.0 coverage, and catalog/icon loading compatibility with Electron 15. |
| 4.1 | Slot-aligned Hero.inventoryCooldowns and destination/progress data in Building.upgrade. |
| 4.2 | Warcraft APM, observed mana recovery, hero combat totals, own-player self-play resources and stats provenance. |
If upgrading from SDK 3, follow the SDK 4 migration guide and regenerate your app binding if its definition changed. Do not mix protocol 3 consumers with protocol 4 producers. See the changelog for the complete release history.
The Warcraft catalog and artwork guide explores the build-specific data in detail, with practical examples for richer app interfaces.
APM and own-player resources
Player.apm is Warcraft's own average actions per minute. 0 is a valid reading;
absence means unavailable. Subscribe to player.apm.changed or observe state.
It requires resources:read.
Player.resources now includes the real local player's economy during self-play.
Gold and lumber are already normalized to player-facing values. Switching the
selected player does not grant another player's resources or APM. Observer and
replay sessions can expose participating players. Scopes and entitlements still
apply; missing observations must remain unknown in your UI.
Mana recovery
Hero.mana.regenerationPerSecond is an optional observed net change per game
second. It can be zero or negative. For a learned ability, load its current
level's manaCost from the catalog and calculate max(0, cost - currentMana).
Only a positive rate supports an estimate of missing mana divided by rate.
Recalculate from snapshots. Do not advance the actual mana pool on a wall clock or let a countdown run through a paused replay. Casting, regeneration effects and other state changes can invalidate the previous estimate. Unknown cost, mana or rate must not be guessed.
Hero combat totals
Hero.combat is available with hero access and contains cumulative observed
damageDealt, selfDamage, damageReceived and healingDealt values.
Self-damage is included in both damage totals: subtract selfDamage to derive
damage dealt to others or received from others.
Healing is attributed by Warcraft and excludes ordinary regeneration. It
includes self-healing; an exact healing-to-others split is not available.
These values are observations for a hero instance. Do not promise full-match
coverage when recording starts late, and key your data by match.id plus hero
instance ID. Do not merge different instances because they share a typeId.
The original damageDealt is Warcraft's raw hero counter. Some summons already
credit that counter and others keep their own, so it is not consistently a
hero-plus-summons total. Do not sum the current unit list onto it indiscriminately.
Hero and summon damage by unit
Since SDK 4.3, supporting recorders add optional
hero.combat.damage, a HeroDamageSummary:
| Field | Meaning |
|---|---|
total |
Observed hero and attributed summon damage, without double-counting native redirects. |
breakdown |
Contributions with damageDealt and a units list of { typeId, isIllusion }. The contributions sum to total. |
complete |
Whether the recorder has detected gaps in history or attribution. When false, the total is a lower bound. |
Repeated summons of one type are grouped; illusions remain distinct from real units of the same type. Each type/illusion pair appears in one contribution. For example, Keeper and treants can have separate amounts. Warcraft merges Water Elemental damage into the Archmage counter, so their contribution lists both types. A shared amount belongs to the listed group, not to each unit type individually. There is no per-ability damage breakdown.
Observed summon damage is retained after summons disappear. Late recording,
ambiguous casters, unavailable final reads and unsupported custom relationships
can leave gaps. Doom and Black Arrow can initially credit their dying enemy;
those hits cannot safely be separated from unrelated enemy damage. These cases
report complete: false. Label them as incomplete observed damage, not an exact
all-summon lifetime total. complete: true is not a promise to capture arbitrary
custom-map damage or units that exist entirely between samples.
Use damage.total for this accounting. Keep damageDealt only when you want the
original native counter; do not add it or current summon counters to total.
Missing damage means the producer does not supply the summary. Do not present
the legacy counter as a summon-inclusive fallback. Use the observations directly
rather than accumulating snapshots; replay rewinds replace the accounting.
import type { Hero } from '@w3booster/sdk';
export function heroDamageView(hero: Hero) {
const damage = hero.combat?.damage;
if (!damage) return undefined; // unavailable, not zero
return {
total: damage.total,
label: damage.complete ? 'Hero and summon damage' : 'Observed damage (incomplete)',
complete: damage.complete,
contributions: damage.breakdown.map(part => ({
units: part.units, // typeId + isIllusion; resolve names with the game catalog
shared: part.units.length > 1,
damageDealt: part.damageDealt, // count once, even when several types share it
})),
};
}
Stats metadata
PlayerStatsCollection.source optionally identifies live or cache results;
observedAt preserves the original Unix-millisecond observation time. Both may
be absent for older producers. Continue to render the collection's status and
select the exact ladder record; provenance does not replace those contracts.
The desktop first exhausts fresh retries, then uses a matching cached record as fallback. Entries older than 48 hours are removed. The cache is bounded and lives only for the desktop session. Original observation times survive cache reuse; fallback data must not overwrite a ready live result in the same match.
Checked example
The example below is type-checked against the SDK version used to build this site.
import type { Hero, Player } from '@w3booster/sdk';
export function observedMetrics(player: Player, hero: Hero, manaCost?: number) {
const mana = hero.mana;
const rate = mana?.regenerationPerSecond;
const missing = mana && manaCost !== undefined && Number.isFinite(manaCost)
? Math.max(0, manaCost - mana.current) : undefined;
return {
apm: player.apm, // zero is valid; undefined remains unknown
resources: player.resources,
missingMana: missing,
estimatedGameSeconds: missing === 0 ? 0
: missing !== undefined && rate !== undefined && rate > 0
? missing / rate : undefined,
nativeHeroDamageToOthers: hero.combat
? hero.combat.damageDealt - hero.combat.selfDamage : undefined,
damageFromOthers: hero.combat
? hero.combat.damageReceived - hero.combat.selfDamage : undefined,
healingIncludingSelf: hero.combat?.healingDealt,
statsSource: player.stats?.source,
statsObservedAt: player.stats?.observedAt,
};
}
See the API reference for the complete types and events.