- gear GLBs generated on MODELBEAST (flux product shot -> bg_remove -> trellis_mac), staged in foitin_assets/gear/ - pipeline: --attach bone-parents rigid gear (armature-scale compensated, '|'-separated spec); render_geared.sh holds calibrated transforms - vesper: 3-state degradation geared -> base -> torn; specials knock gear off first, then shred clothes; kachujin stays 2-state (per-character data) - spec doc: wardrobe section in CHARACTER_SPEC.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.2 KiB
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, wardrobe states
portrait.png select-screen / HUD portrait
moves/<move>/
data.json frame data (timing, boxes, damage, root motion)
frames/0000.png ... sprite sequence, alphabetical order (base state)
frames@torn/ ... optional wardrobe-state variants (same frame count;
frames@geared/ ... missing variants fall back to base per-move)
Conventions
- Timing is in 60Hz ticks. Sprite frames are spread evenly across a move's
totalticks, 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
.geolesson: 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;motionis numpad notation ([2,3,6]= quarter-circle-forward), completed within 14 ticks.
Wardrobe (battle damage)
"wardrobe": { "states": ["geared", "base", "torn"], "tear_below_hp": 0.35 }
Ordered degradation states. Getting hit by a move with "tears": true
advances one state (gear knocked off → clothes shredded); dropping below
tear_below_hp × max health forces the final state. States map to
frames@<name>/ variant dirs per move ("base" = the default frames/).
Render variants with pipeline/render_moves.py --torn ... (alpha-hole
shredding) or --attach ... (bone-parented gear, see
pipeline/render_geared.sh for calibrated specs). Per-character: a fighter
with ["base","torn"] simply has no gear stage.
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.defaultcovers 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.