Defense
Frame-exact high and low guard, timed parry and dodge, guard points, guard break and chip damage for fighting games, driven by explicit commands.
Guard, parry and dodge (combat.defense) #
What it is for #
combat.defense decides what happens when an attack reaches a defender: blocked, parried, dodged, hit or guard_break, and how much damage gets through. It owns the guard points, the parry and dodge windows and the guard-break recovery of each defender. It owns no health, no contact detection, no animation and no knockback: the game detects the contact, sends it, and applies the returned damage exactly once. It is a driver you step yourself, not a hidden tick. It is the guard logic that combat.duel shares, and it works with combat.moveset (moveset guide) and combat.hit_reactions (reactions guide).
Turn it on #
gripforge_gamekit_install { project_id: "gkp_…", id: "combat.defense" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "combat.defense",
config: { "guardMax": 100, "chipRatio": 0.1, "parry": { "active": 3 } } }
The kit requires game.engine (it is added with it). Call step once per fixed simulation frame (normally 60 Hz); durations are whole frames.
First example #
<!-- test: kits/game_engine/host/tests/combat_defense.rs::explicit_driver_isolates_defenders_and_replays --> Each defender is an instance, default or one created with create. Hold the standing guard for a frame, then submit a front mid attack: the default instance blocks it, another defender without a guard takes it.
{ "service": "combat.defense", "method": "create", "args": ["other", {}] }
{ "service": "combat.defense", "method": "request", "args": ["default", { "op": "step", "guard": "standing" }] }
{ "service": "combat.defense", "method": "request", "args": ["default",
{ "op": "contact", "sequence": 1, "level": "mid", "front": true, "damage": 10, "guardDamage": 10 }] }
The answer holds contact: { sequence, outcome: "blocked", damage }. The same contact sent to other answers hit. A repeated or out-of-order contact sequence is refused without changing anything.
Settings and defaults #
All optional and fixed when the instance is created; unknown keys are refused.
| Setting | Default | Meaning |
|---|---|---|
guardMax | 100 | Guard points (1 to 1,000,000); also restored at the end of a guard break. |
guardRegen | 1 | Guard points restored per eligible frame. |
regenDelayFrames | 60 | Frames without regeneration after a block (0 to 600). |
guardBreakFrames | 45 | Frames without defense once guard points run out (1 to 600). |
chipRatio | 0.1 | Fraction (0 to 1) of the incoming damage that passes a successful block. |
airDefense | false | Allow guard, parry and dodge while airborne. |
parry | { startup: 0, active: 3, recovery: 18 } | Frames of the parry action. |
dodge | { startup: 2, active: 8, recovery: 16 } | Frames of the dodge action. |
Services #
Service combat.defense (at most 128 named instances):
create(id, config),remove(id),ids().request(id, command): the commands arestep { guard?, action?, grounded?, enabled?, frozen? }(guardisnone,standingorcrouching;actionis a press edgeparryordodge),contact { sequence, level, front, damage, guardDamage, blockStunFrames?, parryable?, dodgeable? }(levelismid,low,overhead,unblockableorthrow),interrupt,reset,status,config,save,load { save },preview { command }.
Rules in short: an active dodge avoids any dodgeable strike; an active parry stops parryable strikes from the front; a standing guard blocks mid and overhead, a crouching one mid and low; throws and unblockable strikes bypass guard and parry. A block spends guard points; at zero the outcome is guard_break with full damage. Script verbs: a request verb and an exists condition named after the kit id with dots turned into underscores (<kit>_request, <kit>_exists).
Events #
- Bus events, named
<kit id>.<suffix>:.createdand.removedwhen an instance appears or goes away. .changed: a decided command applied (instance,op,result); the result listsstarted,active,ended,cancelled,contactandguard_restoredevents of that request only..refused: a tap cancelled a command.
Hooks and modding #
| Hook | Effect |
|---|---|
combat.defense.create | Before an instance is created; may change its config or cancel. |
combat.defense.request | Before a decided command (not step, status, config, save, preview, load); may change the command or cancel. |
create(id, { preset: "name" }) reads a document of the presets collection of the kit (its id with dots turned into underscores, then _presets), which mods can extend.
Engines #
The same Rust core runs in the web player, the native player, and as a C library and WebAssembly module for the engine wrappers. The wrappers detect contacts and apply damage; the core owns timing and rules.
When it does not do what you expect #
- Contact refused: the
sequencemust grow strictly for that defender, over all attackers. - A parry does nothing against a throw: throws are handled by
combat.grabs. - Time does not pass: nothing steps the instance; send
stepevery frame (frozen: truepauses clocks for hit stop).
Limits #
No health, no hit stop, no animation, no world coordinates: front comes from the game. Parry and dodge reactions on the attacker belong to the game.
Versions #
Kit 1.1.0. Save format gripforge.motion/1.