SDK 4.3: map context and clearer combat data
SDK 4.3 adds neutral points of interest, hero and summon damage contributions,
and resource recovery. It is an additive SDK 4.x update, using protocol 4.0.
Install @w3booster/sdk@4.3.0, update your lockfile and rebuild. The matching
recorder and platform supply the observations; older producers may omit them.
Neutral points of interest
Request match:read and pois:read in your app definition, regenerate the app
binding and have the updated scope granted. POIs live in optional state.pois,
keyed by match-scoped instance ID. The pois capability reports availability;
pointsOfInterest(state, { kind }) returns an immutable, sorted list.
Reset any app-owned history when match.id changes.
Each entry includes its kind, building typeId when applicable, position and
observedAtGameTime in game seconds. Join Warcraft rawcodes with the catalog
identified by match.gameDataId for names, icons and standard definitions.
Self-play is an initial snapshot
state.poiMode === 'initial' contains building positions and starting inventories
captured once at game start, without visibility filtering. The recorder stops
reading POIs after that capture. Purchases, scouting, replenishment, movement,
destruction and cooldown changes never update this snapshot.
Show it as initial map information. Do not animate its timers, infer current stock, or describe it as the last scouted state. If attachment occurs after the startup window, POIs remain unavailable. A reconnect may retain the snapshot already captured for that match.
Observer and replay updates
state.poiMode === 'live' permits current full collection replacements, including
changed offers and removed buildings. Use each observed timer rather than a
wall-clock countdown: pauses and replay speed affect game time, and replay seeks
may decrease observedAtGameTime. Missing collections mean unavailable; an empty
collection means the recorder observed no supported POIs.
import type { MatchState } from '@w3booster/sdk';
import { pointsOfInterest } from '@w3booster/sdk/selectors';
export function merchantView(state: MatchState) {
if (!state.capabilities.includes('pois') || state.pois === undefined) {
return undefined; // unavailable, not an empty map
}
return {
initialOnly: state.poiMode === 'initial',
shops: pointsOfInterest(state, { kind: 'goblin-merchant' }).map(poi => ({
id: poi.id,
typeId: poi.typeId,
position: poi.position,
observedAtGameTime: poi.observedAtGameTime,
offers: poi.offers?.map(offer => ({
id: offer.id,
typeId: offer.typeId,
stock: offer.stock?.current, // 0 is observed sold out
// Initial self-play timers are frozen startup values.
restock: state.poiMode === 'live' ? offer.restock : undefined,
initialAvailability: offer.initialAvailability,
})),
})),
};
}
Offers, stock and timers
offers is the observed assortment. Missing means unknown; [] means observed
empty. Offer IDs are stable within their POI and are distinct from the offered
item/unit rawcode. stock.current === 0 means observed sold out. Do not turn
missing stock into zero or assume that stock alone proves a player can buy.
initialAvailability, restock and cooldown are separate optional
TimedProgress values. Initial availability is the first sale delay; restock is
replenishment after stock is used. Display only fields actually supplied. A
completed timer may disappear from a later live observation.
Native coverage in this release
| POI | Observed data |
|---|---|
| Goblin Merchant, Marketplace | Position, item offers, stock and initial/restock timers |
| Mercenary Camp, Tavern, Dragon Roost, Goblin Shipyard | Position, unit/hero offers, stock and initial/restock timers |
| Goblin Laboratory | Position and unit offers; service timers are unavailable |
| Fountain | Position and standard health/mana restoration category |
| Gold Mine | Position; remaining gold and occupation are unavailable |
| Way Gate | Position; enabled state and destination are unavailable |
| Creep Camp | Contract only; native grouping and drop tables are unavailable |
Costs, stock maxima, player eligibility, marketplace refresh, fountain rates and service cooldowns have optional contract fields but are not supplied by this recorder. Do not present those fields as supported observations. Direct custom axis-only movement may take time to reach the engine's sampled building position.
The catalog adds optional sellsItems, sellsUnits and stock defaults
(initial, maximum, replenishSeconds, firstAvailableSeconds) where defined by the installed game. These describe
standard object configuration, not current inventory or custom-map overrides.
Older immutable catalog revisions may omit them.
Hero and summon damage
hero.combat.damage adds a total, a breakdown of unit-type contributions and
a complete flag under existing hero access. The original damageDealt remains
Warcraft's raw hero counter. Use damage.total for the combined accounting;
never add the old counter or current summons to it again.
Each contribution has one damage amount and a list of { typeId, isIllusion }
types. Repeated summons of a type are grouped, while illusions remain distinct.
A shared amount belongs to the whole listed group and is counted once. For
example, Water Elemental damage credited to the Archmage stays in one shared
contribution. There is no ability breakdown.
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
})),
};
}
Damage is retained after observed summons disappear. Late attachment, ambiguous
casters, missing reads and unsupported relationships can leave gaps. Doom and
Black Arrow can initially credit a dying enemy; those unrecoverable hits produce
complete: false. Show that total as an incomplete lower bound. Missing damage
means unavailable, and complete: true does not promise exhaustive coverage for
arbitrary custom maps or units that exist entirely between samples.
See damage accounting and coverage.
Economy stays unknown until observed
The recorder retries incomplete resource discovery while keeping valid readings live. Failed reads and stale identities invalidate the affected field and can recover later, including when the recovered value is zero.
The API and local SDK feed expose player.resources only after gold, lumber,
supply and supply cap are all available. workerSupply remains optional. A
worker-count-only packet no longer invents a zero economy. Use an unavailable
state for missing resources and preserve legitimate measured zero. Avoid
playerResourcesOrZero when your interface must distinguish these cases.
Resource access stays under resources:read: self-play exposes the actual local
player's economy, independently of selection, while observer/replay covers
participating players. Same-match reconnects retain observations; a new match
starts with fresh discovery and transport state.