# Move clocks and input buffer (combat.moveset) ## What it is for `combat.moveset` runs the timing of a fighter's attacks: which move starts, how long it is in startup, active and recovery, which buttons and directions start it (a quarter circle is `[2,3,6]`), how long an early press stays buffered and which cancels open on a hit or a block. One instance per actor. It owns no collision, damage, root motion or animation: the game applies the contacts, spends the resource and plays the clip when a move `started` event arrives. It is a driver you step yourself, with no hidden tick. It works with `combat.defense` ([guide](guide:combat-defense)), whose outcomes feed the cancel confirmations, and shares its scheduler with `combat.duel` ([guide](guide:combat-duel)). ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "combat.moveset" } gripforge_gamekit_configure { project_id: "gkp_…", id: "combat.moveset", config: { "bufferFrames": 8, "moves": [ … ] } } ``` Without `moves` the kit has the three default moves below. Call `step` once per fixed simulation frame (normally 60 Hz); times are whole frames. ## First example Queue `jab`, step five times (the jab is then active), confirm that it hit, queue `heavy`: the jab cancels into the heavy on hit. The test also saves and loads the instance and gets the same result. ```json { "op": "queue", "move": "jab" } { "op": "step" } { "op": "confirm", "sequence": 1, "result": "hit" } { "op": "queue", "move": "heavy" } { "op": "step", "resource": 100 } ``` Each line is the `command` of `request("default", command)`; the single `step` stands for the five steps of the test. After the last step the answer has `state.active.move` equal to `heavy`. ## Settings and defaults Fixed at creation; unknown keys are refused. | Setting | Default | Meaning | |---|---|---| | `moves` | `jab`, `heavy`, `shot` | 1 to 64 move definitions (see below). | | `bufferFrames` | `8` | How long an early press waits (1 to 60). | | `historyFrames` | `30` | Direction history kept for motion commands (1 to 60). | A move needs `id`, `button`, `startup` (0 to 600), `active` (1 to 600) and `recovery` (0 to 600). Optional: `cost` (default 0), `ground` (`any`, `ground` or `air`), `directions` (numpad directions, `[]` for a plain button), `maxGapFrames` (12), `priority` (0 to 1000: highest wins, then the longest motion, then the smallest id) and up to 32 `cancels` `{ to, from, until, on }` with `on` being `always`, `hit` or `block`. The defaults are `jab` (4/3/10, cancels into `heavy` on hit), `heavy` (9/4/18, cost 10) and `shot` (a quarter circle, 10/1/20, cost 20, on the ground). ## Services Service `combat.moveset` (up to 128 named instances): `create(id, config)`, `remove(id)`, `ids()` and `request(id, command)` with the commands: - `step { direction?, facing?, buttons?, grounded?, resource?, enabled?, frozen? }`: `direction` is a numpad value 1 to 9 in world space (5 neutral), `facing` is 1 or -1, `buttons` are press edges (up to 16), `resource` is the balance the game holds. `enabled: false` blocks starts and cancels; `frozen: true` stops the clocks (hit stop). - `queue { move }`: buffers a known move by hand (for AI); grounds, cost and cancels still apply. - `confirm { sequence, result }`: `hit` or `block` for the current attack, after it started. - `interrupt`, `reset`, `status`, `config`, `save`, `load { save }`, `preview { command }`. Answers hold `state`, `phase` and `events`. Script verbs: a request verb and an exists condition named after the kit id with dots turned into underscores (`_request`, `_exists`). ## Events - In the answer of a request: `started { move, sequence, cost }` (spend the cost once), `active`, `ended`, `cancelled`. - Bus events, named `.`: `.created`, `.removed`, `.changed` (a decided command applied) and `.refused` (a tap cancelled it). ## Hooks and modding | Hook | Effect | |---|---| | `combat.moveset.create` | Before an instance is created; may change its `config` or cancel. | | `combat.moveset.request` | Before a decided command; may change the `command` or cancel. | `create(id, { preset })` reads the presets collection of the kit (its id with dots turned into underscores, then `_presets`). ## Engines The same Rust core runs on the web, the native player and as a C library and WebAssembly module for the engine wrappers. The game checks hitboxes during the whole `active` phase and keeps its own table of already hit targets per attack sequence. ## When it does not do what you expect - A command does not start: check `ground`, `cost` against the `resource` you send, `enabled` and the buffer. - A cancel never opens: the window `[from, until)` is in frames of the current move and `on` must match the confirmation. - `confirm` is refused: the `sequence` is stale or the attack has not started yet. ## Limits No collision, damage, animation or resources: the game owns them. The duel kit does not yet read arbitrary movesets from this kit. ## Versions Kit 1.1.0 (modding contract). Save format `gripforge.moveset/1`.