Docs Design

Contents

Abilities, Effects and beatable platformer foes

The October audit's Wave 4 item "Abilities and Effects on every mover; beatable platformer enemies" (docs/design/audit-2026-10.md). Before it, every body had one attack (kit.melee's blow or kit.ranged's volley), nothing could slow, stun, shield or poison anything, and a platformer foe was a hazard to jump over: it could never be beaten.

Effects (Effect, ids effect.*, in effects/)

A status a body is under for a while: slowed, poisoned, shielded, hasted, stunned, invulnerable.

  • Fields: durationMs (0 lasts until taken off), stacking (refresh starts its time over, extend adds to it, stack adds a stack up to maxStacks, keep does nothing), and what it changes: speedMul, damageTakenMul, damageDealtMul (each once per stack), canMove, canAct, invulnerable, and every actions run each everyMs once per stack (regeneration, poison), with self the body and source whoever put it on.
  • Put on by apply_effect, an ability, blast and fire (their effect), and a shooter's effect (every shot that hurts). Taken off by remove_effect or its time.
  • One place for everyone (GwEffects): kits never keep a slow or a stun of their own. They ask speed_mul, can_move, can_act, damage_taken_mul, damage_dealt_mul and invulnerable:
    • kit.move scales a walk and holds a root;
    • kit.melee multiplies harm both ways, turns every hit away from an invulnerable body, and stops a body that can't act from striking or dodging;
    • kit.ranged stops it shooting;
    • kit.platformer scales a body's running and climbing, holds a root, lets an invulnerable player pass spikes and foes, and makes a foe that can't act harmless to the touch.
  • State on the entity (comp.effects.on, absolute ticks), so saves and replays carry it exactly. An entity nothing was put on has none, and one whose last effect ends loses it: games without effects keep every state hash. kit.core's first system takes off what has ended and runs the every actions due, before any other system steps.
  • Events effect.started, effect.renewed, effect.ended; latches effect:<id>, renewed:<id>, ended:<id>. Pure functions has_effect(e, id), effect_stacks, effect_secs, can_act, can_move, so a HUD shows "SLIMED" and juice answers effect.started.

Abilities (Ability, ids ability.*, in abilities/)

Something a body does on demand: a dash, a fireball, a shield, a slam, a swipe.

  • Carried in kit.core's abilities component (any mover). Used by the player's action, by any other body by itself (auto, while when holds), or by use_ability from a state machine or rule.
  • A use: the body is alive and can act, the ability is off its cooldownMs, when holds and the body has its cost (a stat). Then the cost is paid, the cooldown starts, ability.used goes out and do runs, with self the body and target what it was used at (a click's target for the player). A refused press or use_ability says why in ability.refused; ability.ready comes when a cooldown ends.
  • What it does is any actions, and three new ones built for it: kit.move's dash, kit.melee's blast (an area hit that may put an effect on what it hurts), kit.ranged's fire (a volley from any body, paced by the ability's cooldown), and kit.platformer's platformer_strike.
  • Cooldowns are absolute ticks in comp.abilities.ready, written on the first use.

Beatable platformer foes

A platformer_touch with beatable is beaten by:

  • a stomp: the player comes down on it from above (falling, its feet the tick before at the thing's top or over it). The player bounces off at stompBounce of its jump;
  • a strike: platformer_strike, a box ahead of a body (an ability's swipe).

It takes its touch's hits to beat (1 unless it says; a strike counts its damage). Hits are counted on the touch, never in a stat: every entity carries each declared stat's default, so a health stat would have made a plain foe as tough as the player (the first test of it found that). platformer_hits_left(e) reads what is left, for a boss's bar. A thing just hit stays harmless, and can't be hit again, for 400 ms, so the landing that stomped it doesn't hurt the player on the way back up. Beaten, it is gone from the level (platformer.beaten, latch beaten:<id>); short of that, platformer.struck. Starting the level over brings it back. A side touch still hurts.

The levels' witnesses (the core's reachability search) never stomp, so beatable foes only add ways through: every route still holds.

Checks (core/src/game/abilities.ts)

An ability's cost names a declared stat (error); its action is in the game's InputMap (error); something carries every Ability and something uses it (warnings); something puts every Effect on (warning); maxStacks only with stacking stack (warning). Expressions in an Ability read self and target; in an Effect, self and source.

In the samples and the starter

  • Burrow Run: its crawlers are beatable; a stomp bursts sparks, stretches the hero and plays a squash and a boing. Its look tour stomps the meadow's crawler. The platformer starter's crawler is beatable too, so a new platformer game's foes can be stomped from the start.
  • Saltwell: a spitter's glob slimes the keeper (effect.slimed: seven tenths of the speed for 1.5 s, refreshed by each hit), with green droplets and SLIMED on the HUD.

Not yet

  • An ability bar in the HUD (cooldowns drawn), and touch buttons for ability actions.
  • A boss built from these and a Timeline in a sample.
  • Effects that change a body's look (a tint while slowed) beyond juice on effect.started.

From docs/design/abilities-effects.md in the Gamewright repository.