Open app
Moonborn — Developers

Character voice variants

Fork a single character into multiple voices — formal, casual, on-stage, in-private — without losing the through-line.

A character has one Soul and one Self. They have many Masks — how they speak at work versus at home, on stage versus in private, with a friend versus a stranger. Voice variants forks model that.

The pattern

  1. Build the canonical character (full four layers).
  2. Fork once per voice register, locking Soul + Self, refining only Mask:
const onStage = await client.personas.fork({
  id: canonical.id,
  refine: {
    mode: 'refine',
    layer: 'mask',
    axis: 'more-performative',
    amount: 0.5,
  },
  note: 'On-stage public voice',
});
 
const inPrivate = await client.personas.fork({
  id: canonical.id,
  refine: {
    mode: 'refine',
    layer: 'mask',
    axis: 'more-vulnerable',
    amount: 0.4,
  },
  note: 'Private voice',
});

When to use which Mask variant

Annotate each variant with the scene it serves:

VariantScene
canonicaldefault, narrator-level
on-stagepress conference, public address
with-rivalconfrontation, withheld emotion
with-mentorguard down, asking questions
in-griefpost-loss, register flattens

Distinctiveness sanity check

Variants should differ in voice but not collapse into different characters. After fork:

const cmp = await client.consistency.compare({
  fromPersonaId: canonical.id,
  toPersonaId: onStage.id,
});
console.log(cmp.value); // ideal: 0.15-0.30

Below 0.15 = the variant didn't shift enough. Above 0.40 = the variant escaped the character (rewrite as a new character).

Don't refine Soul + Self

The temptation is to "darken" a variant by editing Soul. Resist — a character with two Souls isn't a character with two voices, it's two characters. If the second voice needs a different Soul, fork from the lineage root with a new Soul brief and accept it's a sibling.

Related