foitin/characters/_spec/CHARACTER_SPEC.md
m3ultra 347804135d Gear wardrobe state (Phase B): vesper geared with MODELBEAST boxing gloves + headgear
- 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>
2026-07-28 22:23:05 +10:00

115 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
`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
```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.
## Wardrobe (battle damage)
```json
"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/<move>/data.json
```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.