The pack
abilities
function abilities():Pack<{abilities:typeof rules}>
Install renderer-independent abilities policies.
Helpers
checkAbilityState
function checkAbilityState(state: unknown, definitions: readonly AbilityDefinition[]): string | null
Why a saved state cannot be restored into abilities of these definitions, or null when it can: the time and every
resource finite and 0 or more; the actor with an id, a team and its two flags; a slot for each ability and none
other, its charges a whole number up to the ability's and its recharge within the cooldown; and the cast null or
one of the abilities, with its targets, elapsed time and a cursor within its effects. restore throws with this.
createAbilities
function createAbilities(definitions: readonly AbilityDefinition[], actor: AbilityActor)
Create abilities for actor from definitions. Throws on duplicate or empty ids, a cooldown of 0, charges or
maxTargets outside 1 to 1000, a refund outside 0 to 1, or a negative or non-finite cost, delay, windup, range or
resource.
Classes
Abilities
class Abilities
Costs reserve on start; interruption refunds an explicit fraction. Effect delivery is an adapter boundary.
readonly definitions: AbilityDefinition[]— Validated copies of the ability definitions, with each ability's effects sorted by delay.state: AbilityState— The live state. Read it freely; change it only throughstart,tick,interruptandrestore(or the game-ownedactorfields). Usesnapshotfor a copy.readonly events: AbilityEvent[]— Events queued since the lastdrain, oldest first.constructor(definitions: readonly AbilityDefinition[], actor: AbilityActor)preview(id: string, targets: readonly AbilityTarget[]): { /** True when no rule rejects the cast. */ ok: boolean; /** One message for each rule that rejects the cast; empty when \ok` is true. */ reasons: string[]; }— Whether abilityidcould start now againsttargets, without changing anything. Returnsokand areasons` list naming every rejection, such as 'recharging' or 'insufficient mana'. Throws 'Unknown ability' for an unknown id.start(id: string, targets: readonly AbilityTarget[])— Start a cast ifpreviewallows it: spends the costs and one charge, starts the recharge timer if every charge was ready, and queues 'started'. Effects already due (zero windup and delay) are delivered at once. Returns the preview result; whenokis false nothing changed. Throws for an unknown id.tick(dt: number)— Advance time bydtseconds: recharge charges and deliver each effect as it falls due, completing the cast, and queue the events. Long steps are split at every due moment so none is skipped. Throws unlessdtis finite and 0 or more.interrupt(): boolean— Cancel the cast in progress: undelivered effects are dropped and theinterruptRefundfraction of each cost is returned, but the spent charge is not. Queues 'interrupted'. Returns false when no cast was running.drain()— Remove and return every queued event, oldest first.snapshot()— An owned deep copy of the state, for saving and a laterrestore.restore(state: AbilityState)— Replace the state with a copy ofstate. Throwsabilities.restore: …, changing nothing, on a statecheckAbilityStaterefuses for these definitions.
Types
AbilityActor
interface AbilityActor
The caster: its identity, team, whether it can act, and the resources it can spend.
id: string— Actor id; the only valid target of a 'self' ability.team: string— Team name, compared with each target's team.alive: boolean— Whether the caster is alive; a dead caster cannot start a cast.disabled: boolean— Whether the caster is currently unable to act, for example stunned; a disabled caster cannot start a cast. The composing game sets it throughstate.actor.resources: Record<string, number>— Spendable amounts by resource name, in the game's own units (mana, energy); each finite and 0 or more. Costs are deducted here and refunds added back.
AbilityDefinition
interface AbilityDefinition
A castable ability: its costs, timing, charges, targeting and the effects it delivers.
id: string— Unique non-empty ability id; names the ability inpreview,start, events andAbilityState.slots.cost: Record<string, number>— Resources spent when a cast starts, by resource name; each amount is a finite number of 0 or more, in the actor's own resource units.cooldown: number— Seconds a spent charge takes to recharge; greater than 0 (at least 0.000001).charges: number— Most charges the ability holds, a whole number from 1 to 1000; it starts with all of them. Each cast uses one.windup: number— Seconds from the start of a cast until its effects begin; 0 or more. Each effect'safterdelay counts from the end of the windup.range: number— Furthest a target may be from the caster, in the same units asAbilityTarget.distance; 0 or more.target: 'self' | 'ally' | 'enemy' | 'any'— Who may be targeted: 'self' only the caster, 'ally' anyone on the caster's team (the caster included), 'enemy' anyone on another team, 'any' anyone.maxTargets: number— Most targets one cast may name, a whole number from 1 to 1000; a cast needs at least one.effects: { /** Seconds after the windup ends at which the effect is delivered; 0 or more. */ after: number; /** The combat-rules effect to apply to each target; each 'effect' event carries a copy. */ effect: Effect; }[]— Effects a cast delivers to every target, eachafterseconds after the windup ends. Sorted byafterwhen the abilities are created and delivered in that order.interruptRefund?: number— Fraction of eachcostreturned if the cast is interrupted, from 0 to 1; default 0.
AbilityEvent
interface AbilityEvent
Something the abilities did: a cast started, an effect was delivered, a cast completed or was interrupted, or a
charge recharged. Queued until drain.
kind: 'started' | 'effect' | 'complete' | 'interrupted' | 'recharged'— What happened: 'started' a cast began, 'effect' an effect reached a target, 'complete' the cast finished, 'interrupted' it was cancelled, 'recharged' a charge came back.ability: string— Id of the ability concerned.target: string— Id of the target an 'effect' event reached; the empty string for every other kind.effect?: Effect— Copy of the delivered effect; set only on 'effect' events.time: number— Value ofAbilityState.timewhen this happened: seconds since the abilities were created.
AbilityState
interface AbilityState
Everything that changes: the clock, the actor, each ability's charges and the cast in progress. snapshot and
restore carry it.
time: number— Seconds simulated so far; advances only intick.actor: AbilityActor— The caster, including its current resources.slots: Record<string, { /** Charges ready to use now, from 0 up to the definition's \charges`. / charges: number; /* Seconds until the next charge returns; 0 when every charge is ready. */ recharge: number; }>` — Charge state for each ability, by ability id.cast: null | { /** Id of the ability being cast. */ id: string; /** Ids of the targets chosen when the cast started. */ targets: string[]; /** Seconds since the cast started. */ elapsed: number; /** * Index into the ability's sorted \effects` of the next effect to deliver; equals the number of effects once * all have been delivered. */ cursor: number; }` — The cast in progress, or null when idle. Only one cast runs at a time.
AbilityTarget
interface AbilityTarget
A candidate target as the caller observes it; the rules take its team, life and distance from here and never look it up.
id: string— Target id, unique within one target list. For a 'self' ability it must equal the actor's id.team: string— Team name, compared with the actor's team for 'ally' and 'enemy' abilities.alive: boolean— Whether the target is alive; a dead target is rejected.distance: number— Distance from the caster in game units, compared with the ability'srange; finite, 0 or more.


