Skip to content

The fight pack ​

Every fighting move is engine data: a punch, a kick, an elbow, a knee, a sweep, a throw, as a take on the standard skeleton with the numbers a fighting game's rules run on. The movement system does locomotion; the fight pack does the fighting (Isaac, 23 Oct 2026), tagged by style, so a game picks what its fighters have. A game reads the pack; it types no move of its own.

The file ​

A motion set (soma-motion, text or the compact binary pack_motion.mjs writes) whose takes of the part strikes are the moves' captured motions, with a fight block beside the takes:

json
{
  "format": "soma-motion",
  "version": 1,
  "name": "fight-v1",
  "kind": "fighting",
  "takes": [
    {
      "name": "boxing_hook_lead",
      "module": "strikes",
      "fps": 30,
      "frames": 62,
      "bones": {},
      "root": []
    }
  ],
  "fight": {
    "strikes": [
      {
        "id": "hook",
        "name": "Hook",
        "take": { "name": "boxing_hook_lead", "step": 0.3 },
        "startup": 8,
        "active": 3,
        "recovery": 18,
        "damage": 9,
        "hitstun": 22,
        "blockstun": 14,
        "level": "high",
        "limb": "lh",
        "radius": 0.12,
        "push": 0.55,
        "pushBlock": 0.35,
        "hitstop": 11,
        "strength": 0.7,
        "styles": ["boxing"],
        "power": "powerhook"
      }
    ]
  }
}

Each strike, in frames at 60 a second: startup (the frames before it can hit), active (the frames it hits), recovery; damage, hitstun and blockstun (the frames the one hit, or the one blocking, is held), push and pushBlock (metres the one hit slides back), hitstop (the impact freeze), strength (0 to 1: the size of the reaction, the shake, the sound); level (high, mid, low, overhead) and limb (lh, rh, lf, rf, lk, rk, le, re: the hand, foot, knee or elbow, each side), radius (the striking part's reach, metres); take (the take's name, the frames of it used, its contact frame when the file says it, how much of its own step, rise and turn it keeps); aim, aimWeight and reachMax (where on the other body it is aimed and how firmly); launch and sweep (into the rag-doll), travel, armor, special, crouch, air, and the rest of a game's words for a move; styles (empty: everyone's); power (the id of its power version, the power button held). A throw carries a grab (the two takes played together, where it takes hold, the slam, the one taken lying still, the attacker up again, the reach to take hold).

The other parts of the set are what a fighting game plays between strikes: stances (the idle, the guard, the blocks, the hit reactions, the steps), getups, celebrations, throws. A take says which joints it animates (animated) where it does not animate them all; the rest carry a still track for the packer.

The first pack ships with the engine at FIGHT_MOTION_URL (gameable/animation) (115 takes, 43 strikes, 2.9 MB; from Fight Lab's clips and moves of 28 Oct 2026: the studio's shared clips, the Mixamo boxing and kicking set converted onto the skeleton, the studio's two get-ups and three captured kicks).

The character bridge loads it once, lazily, when the first character.fight asks. A sandbox script game needs no page wiring or fight.set. A page can pass fight: { set: customUrl, random, bounds, cameraRandom } to createCharacterBridge to use its own pack and settings, or fight: false to disable fighting. The docs site also serves the pack from docs/public/sets/fight-v1.motion.bin.

Reading it ​

ts
import {
  FIGHT_MOTION_URL,
  fightMoves,
  parseFightPack,
  parseMotionBytes,
  parseMotionSet,
  powerOf,
  strikeOf,
} from 'gameable/animation';

const bytes = new Uint8Array(await (await fetch(FIGHT_MOTION_URL)).arrayBuffer());
const { source } = parseMotionBytes(bytes, parseMotionSet); // or parseFightPack(json) on the text
const pack = parseFightPack(source);
const jab = strikeOf(pack, 'jab'); // { startup: 5, active: 2, recovery: 9, total: 16, damage: 4, ... }
const mine = fightMoves(pack, ['boxing', 'wrestling']); // everyone's strikes and these styles'
const held = powerOf(pack, 'jab'); // 'powerjab'
console.log(pack.refused); // a strike that breaks a rule is left out and said, never the whole file

The numbers are the game's rules: it stretches them by a fighter's own speed and power as it likes, and plays the take on its own road until the engine's strike door lands (the fight kit's landing c: the take re-timed to the frames, the limb bent to the target, the body kept solid).

See also ​