foitin/characters/_spec/CHARACTER_SPEC.md
m3ultra b067bf7f0b Scaffold: deterministic 60Hz combat core, drop-in character format, MODELBEAST pipeline
- Godot 4.7 project; fixed-tick FSM fighter, facing-relative input buffer
  with numpad motion parsing, box collision, training-mode hitbox overlay
- characters/ = self-describing drop-in folders (spec in _spec/); two
  generated placeholder fighters (alpha, beta)
- pipeline/: pack_character.py + autohitbox.py (pose/silhouette hurtboxes)
- STUDY.md: reverse-engineering study of the BKB reference games

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 18:45:44 +10:00

3.3 KiB
Raw Blame History

FOITIN character folder spec (v1)

A character is a self-describing folder under characters/. The engine scans characters/* at boot (folders starting with _ or . are skipped) — dropping a valid folder in IS shipping a character. This is the BKB per-character-folder idea (Data/Venus/{bik,zon,geo,me2}) modernized: PNG/WebP frames instead of Bink video, one JSON per move instead of the binary BKB!ZONE/GEOM/DAT2 sidecars.

characters/<id>/
  manifest.json            identity, stats, move bindings
  portrait.png             select-screen / HUD portrait
  moves/<move>/
    data.json              frame data (timing, boxes, damage, root motion)
    frames/0000.png ...    sprite sequence, alphabetical order

Conventions

  • Timing is in 60Hz ticks. Sprite frames are spread evenly across a move's total ticks, so art frame-rate is decoupled from game logic (render at 15, 20, 30 or 60fps — the engine doesn't care).
  • Boxes are fighter-local, right-facing: origin at the ground pivot (feet centre), +x forward, y up. Rects are [x, y, w, h] (y usually negative). The engine mirrors everything when facing left.
  • Frames must share one canvas size with the ground pivot at bottom-centre (the BKB .geo lesson: bake a consistent pivot or sprites jitter).

manifest.json

{
  "id": "alpha",
  "name": "ALPHA",
  "health": 1000,
  "walk_speed": 3.2,
  "back_speed": 2.6,
  "jump_velocity": -13.5,
  "color": "#3ec6a8",
  "normals": { "P": "jab", "6P": "straight", "K": "kick" },
  "command_moves": [
    { "motion": [2, 3, 6], "button": "P", "move": "rush_palm" }
  ]
}
  • normals: button → move. Optional numpad-direction prefix (6P = forward+P, 2K = crouching K). Plain button is the fallback.
  • command_moves: checked before normals; motion is numpad notation ([2,3,6] = quarter-circle-forward), completed within 14 ticks.

moves//data.json

{
  "name": "jab",
  "total": 22,
  "loop": false,
  "startup": 5,
  "active": [6, 8],
  "damage": 30,
  "hitstun": 14,
  "blockstun": 8,
  "pushback": 7,
  "knockdown": false,
  "cancels": [],
  "root_motion": [0, 0, 1.5, 3, 3, 1.5],
  "hurtboxes": {
    "default": [[-22, -305, 44, 50], [-30, -255, 60, 105], [-26, -150, 52, 150]],
    "frames": { "6-8": [[-22, -305, 44, 50], [-30, -255, 90, 105], [-26, -150, 52, 150]] }
  },
  "hitboxes": { "6-8": [[30, -272, 78, 34]] }
}
  • active: [first, last] tick (inclusive) the move can hit. One hit per move execution (no multi-hit yet).
  • hitboxes / hurtboxes.frames: keys are a tick or tick-range string ("6", "6-8") → list of rects. hurtboxes.default covers unlisted ticks.
  • root_motion: per-tick forward displacement in px, optional, truncates to 0.
  • cancels: reserved for the cancel system (not enforced yet).

Required move names

The FSM binds these animation names — every character must provide them:

idle walk_f walk_b crouch jump block hit knockdown getup

plus whatever attack moves the manifest binds. Recommended full set and budget: see STUDY.md §2 (~30 clips).

Where assets come from

Placeholder: tools/gen_placeholder_char.py. Production: the MODELBEAST pipeline (pipeline/README.md) renders a rigged 3D character to frames, then pipeline/autohitbox.py generates hurtboxes from pose estimation and pipeline/pack_character.py assembles this folder.