pixi-vn-ink-characters

Exact captured content · path skills/characters/SKILL.md · sha256 b9183c3b4e5a1245… · revision 18a671aa83db02fe201d417cb7e6815b0b503c6d

Download the exact captured file

---
name: pixi-vn-ink-characters
description: "Use when attributing ink dialogue to a Character, printing a character's name inline with [characterId], writing character emotions with the id@emotion syntax, or renaming a character from ink with # rename. Part of the pixi-vn-ink skill set — install alongside pixi-vn-ink."
---

# Characters in ink

Official docs: [pixi-vn.com/ink/character](https://pixi-vn.com/ink/character). Assumes
[Pixi'VN characters](https://pixi-vn.com/start/character) are already defined via `CharacterBaseModel`
+ `RegisteredCharacters.add(...)` — this skill only covers referencing them from `.ink`.

## When to use this skill

Use whenever an `.ink` line needs to be spoken by a registered character, needs that character's
display name printed inline, needs an emotion-specific character variant, or needs to change a
character's name from inside the story (a naming scene, a nickname reveal, ...).

## Attributing a line to a character

```ink
mc: Hello, I'm Liam.
```

`<character_id>: <text>` — Pixi'VN splits on the **first `": "` (colon, then a space)** in the line and
checks whether the token before it is a known character id. A known id is either one explicitly passed
to `importInkText(text, { characters: [...] })` / `vitePluginInk({ characters: [...] })`, or (the
common case) already present in `RegisteredCharacters` at conversion time. If the token doesn't match a
known character, the whole line is left as plain narrator text — including the colon — so a typo'd
character id silently becomes garbled narration instead of an error. Because the split requires the
literal `": "`, `mc:Hello` (no space) is **not** recognized as dialogue attribution.

```ts title="values/characters.ts"
import { CharacterBaseModel, RegisteredCharacters } from "@drincs/pixi-vn";

export const mc = new CharacterBaseModel("mc", { name: "Liam" });
RegisteredCharacters.add(mc);
```

If characters are registered *after* the ink importing/build step runs (a common ordering issue with
the Vite plugin, since `vitePluginInk`'s JSON export happens independently of when your `content/`
files execute), pass their ids explicitly via `vitePluginInk({ characters: [...] })` or
`importInkText(text, { characters: [...] })` so the split still recognizes them.

## Printing a character's name inline: `[characterId]`

```ink
Hello, [mc].
```

This is **not** native ink's `{variable}` interpolation — it's Pixi'VN's
[text-replacement](../text-replacement/SKILL.md) mechanism (`[key]` in square brackets), applied here to
substitute a character id with that character's current `name`. It needs one text-replace handler
registered, using the built-in `"characterId"` validation mode — every official template ships this by
default:

```ts title="content/ink/text-replaces.ts"
import { TextReplaces } from "@drincs/pixi-vn-ink";
import { RegisteredCharacters } from "@drincs/pixi-vn/characters";

TextReplaces.add((key) => RegisteredCharacters.get(key)?.name, {
  name: "character name",
  validation: "characterId",
  type: "after-translation",
  i18nInterpolation: true,
  description: "Replaces a character ID with the character's name in the game.",
});
```

`validation: "characterId"` means this handler only fires for keys that are currently a registered
character id (checked against `RegisteredCharacters`) — no regex needed. Because the character's
`name` can change at runtime (see rename below), `[mc]` always reflects the *current* name, not the one
at story-write time.

## Character emotions: `id@emotion`

```ink
mc@happy: Hi, I'm Liam. I'm very happy today.
```

`<character_id>@<emotion>: <text>` — the whole `mc@happy` token is checked against
`RegisteredCharacters` exactly like a plain id above; it resolves because Pixi'VN characters can be
registered under a composite `{ id, emotion }` key (internally addressed the same way, `id@emotion`):

```ts title="values/characters.ts"
import { CharacterBaseModel, RegisteredCharacters } from "@drincs/pixi-vn";

export const mc = new CharacterBaseModel("mc", { name: "Liam" });
export const mcHappy = new CharacterBaseModel({ id: "mc", emotion: "happy" }, { name: "Liam happy" });

RegisteredCharacters.add([mc, mcHappy]);
```

An emotion variant is a full second `CharacterBaseModel` registration — typically with its own `name`
override (here identical) plus whatever sprite/portrait data your project's rendering layer keys off of
(see [pixi-vn-ink-canvas](../canvas/SKILL.md) for showing an emotion-specific sprite alongside the line).
Referencing `mc@sad` without registering that variant behaves like any other unknown id: the line falls
back to plain narrator text.

## Renaming a character from ink: `# rename`

```ink
=== start ===
mc: Hello, I'm [mc].
# request input string
mc: My name is:
# rename mc {_input_value_}
mc: My name is [mc]
-> DONE
```

`# rename <characterId> <newName>` is **not** a built-in hashtag command — every official template
registers it itself, because "which field(s) does renaming touch" is project-specific:

```ts title="content/ink/hashtag-commands.ts"
import { characterIdsEnum } from "@/pixi-vn.keys.gen"; // generated by vitePluginPixivn's typeFilePath
import { HashtagCommands } from "@drincs/pixi-vn-ink";
import { RegisteredCharacters } from "@drincs/pixi-vn/characters";
import zod from "zod";

HashtagCommands.add(
  async (script) => {
    const character = RegisteredCharacters.get(script[1]);
    if (character) character.name = script[2];
    return true;
  },
  {
    name: "character rename",
    description: "Renames a character in the game.\n\n```ink\n# rename <characterId> <newName>\n```",
    validation: zod.tuple([zod.literal("rename"), zod.enum(characterIdsEnum), zod.string()]),
  },
);
```

See [pixi-vn-ink-hashtag-commands](../hashtag-commands/SKILL.md) for the `add` vs `addMapper` choice and
why this is registered as a plain `add` handler (it mutates game state directly rather than returning a
`PixiVNJsonOperation`).

## Related

- **pixi-vn-ink-text-replacement** — the general `[key]` mechanism `[characterId]` is built on.
- **pixi-vn-ink-hashtag-commands** — how `# rename` (and any other custom command) is registered.
- **pixi-vn-ink** — knots-as-labels, storage mapping, installation.
- [pixi-vn.com/start/character](https://pixi-vn.com/start/character) — defining `CharacterBaseModel`s
  in JS/TS in the first place.