# VN story sprites

Standing character art for **visual-novel dialogue** — the figure that sits
beside the text box while a maid talks to an NPC. These are **not** gameplay
sprites (no 256×256 frame contract, no atlas, no `anim.rs` policy) and not the
512×640 bust portraits in `../portraits/`.

Everything here is **drop-in**: one PNG per (character, pose, expression), no
sheet to rebuild and no index to update. Adding a file is adding a file.

    assets/sprites/vn/{character}_{pose}_{expression}.png

- `{character}` — the sprite character key, same vocabulary as
  `game/client/src/gfx/mod.rs::character_for_unit`: `maid` (the operator
  protagonist) and `heiress`.
- `{pose}` — the body. `stand` is the **base** pose (always present);
  `arms` is the alternate.
- `{expression}` — the face. `neutral` is always present and is the fallback
  if a requested expression is missing.

## Shipped today

| character | pose    | expressions |
| --------- | ------- | ----------- |
| `maid`    | `stand` | `neutral` `happy` `angry` `surprised` `sad` `embarrassed` `determined` `sly` |
| `maid`    | `arms`  | `neutral` `happy` `angry` `sly` |
| `heiress` | `stand` | `neutral` `happy` `angry` `surprised` `sad` `embarrassed` `determined` `sly` |
| `heiress` | `arms`  | `neutral` `happy` `angry` `sly` |

24 files. `stand` = relaxed standing, hands clasped (maid) / sabre held
point-down (heiress). `arms` = arms folded (maid) / hand on hip with the sabre
planted (heiress).

## Registration contract — this is the whole point

**Within one character, every PNG shares one canvas, one origin and one
scale.** Swapping the expression or the pose moves nothing:

| character | canvas    | figure bbox in canvas | figure height |
| --------- | --------- | --------------------- | ------------- |
| `maid`    | 402×1035  | `(3, 3) → (399, 1032)` | 1029 px |
| `heiress` | 525×1035  | `(3, 3) → (522, 1032)` | 1029 px |

- Draw **bottom-anchored**: put the bottom edge of the PNG on the floor line
  and centre it horizontally on wherever the speaker stands. The 3 px
  transparent margin is deliberate — it stops edge-clamped filtering from
  smearing the silhouette when the sprite is scaled.
- Both characters are normalised to the same 1029 px figure height, so they
  read at the same size in the same box. Scale by `box_height / 1035`.
- Expressions of one pose are **byte-identical outside the face**: the alpha
  channel is shared verbatim and the RGB delta outside the composited face
  region is exactly 0. Swapping expression mid-conversation cannot make the
  body jump, which is the failure mode this set was built to avoid.

## Format

RGBA PNG, straight (non-premultiplied) alpha, fully transparent background,
~0.4–0.6 % of pixels at partial alpha (the 1 px anti-aliased outline ring —
everything else is 0 or 255). Verified over magenta, not just over grey:
holes are invisible on a neutral backdrop and glaring on magenta.

    magick maid_stand_neutral.png -background magenta -alpha remove -alpha off /tmp/check.png

## Style

1997 Nasu/Takeuchi doujin cel-ink — scratchy dip-pen linework, hard-edged flat
cel shadows under one upper-left key light, muted desaturated palette. The
shipped bust portraits (`../portraits/portrait_{maid,heiress}_select.png`) are
the reference for face and palette; the shadow masses are lighter here than in
the gameplay sprites on purpose, because the player has to read the expression
at a glance while reading text.

## Regenerating

Not produced by `pipeline/run.mjs`. Registered in `pipeline/manifest.json` as
asset **`vn_story`** with `deferred: true, external: true` so no pipeline stage
can rebuild or destroy it. The build scripts live in `pipeline/out/vn/`
(gitignored); the manifest entry's `notes` carries the exact re-run order.
