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.

Source: module-kits:kits/combat_defense/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs combat-defenseMCP tool: gripforge_docs { "topic": "combat-defense" }

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.

SettingDefaultMeaning
guardMax100Guard points (1 to 1,000,000); also restored at the end of a guard break.
guardRegen1Guard points restored per eligible frame.
regenDelayFrames60Frames without regeneration after a block (0 to 600).
guardBreakFrames45Frames without defense once guard points run out (1 to 600).
chipRatio0.1Fraction (0 to 1) of the incoming damage that passes a successful block.
airDefensefalseAllow 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 are step { guard?, action?, grounded?, enabled?, frozen? } (guard is none, standing or crouching; action is a press edge parry or dodge), contact { sequence, level, front, damage, guardDamage, blockStunFrames?, parryable?, dodgeable? } (level is mid, low, overhead, unblockable or throw), 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>: .created and .removed when an instance appears or goes away.
  • .changed: a decided command applied (instance, op, result); the result lists started, active, ended, cancelled, contact and guard_restored events of that request only.
  • .refused: a tap cancelled a command.

Hooks and modding #

HookEffect
combat.defense.createBefore an instance is created; may change its config or cancel.
combat.defense.requestBefore 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 sequence must 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 step every frame (frozen: true pauses 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.