foitin/characters/_spec/CHARACTER_SPEC.md
type-two c1eca9e1a0 hp_bands wardrobe mode: states as mini health bars + segmented HUD
Each wardrobe state owns an equal hp band; emptying a band breaks the
fighter into the next look (white flash). HUD draws one colored segment
per band. --demo=bands + --shot-ticks for multi-shot verification.
Vesper (geared/base/torn) switched to hp_bands as the pilot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 15:11:36 +10:00

132 lines
5.0 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.
### hp_bands mode — punch through the layers
```json
"wardrobe": { "states": ["geared", "base", "torn"], "hp_bands": true }
```
With `hp_bands`, the ordered states split max health into **equal hp bands**
and the active state follows the band hp is in. Each state is a mini health
bar: the HUD draws one colored segment per band, and emptying a segment
breaks the fighter through into the next look (white sprite flash on the
break). Forward-only — a passed band is never re-entered. `tears` and
`tear_below_hp` are ignored in this mode. States don't need full variant
coverage: missing `frames@<state>/` dirs fall back to base per-move, so a
style tier that only covers idle/walk/hit/jab/straight/kick still reads.
The states can be *any* ordered looks, not just wardrobe — e.g. render
styles (`["base", "sketch", "wireframe"]`) made with the restyle pipeline.
## 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.