W3 / DevelopersSDK 4.6.0Source & examples ↗

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.