regen-new-character
Add a new speaking character (companion persona) to ReGen Civics end to end, including both voice layers. Use when asked to "add a character", "new persona", "new companion", "give X a voice", "signat
SKILL.md.claude/skills/regen-new-character/SKILL.md
GitHubregen-new-character
Every speaking character on the site gets three voice layers, in this order of preference at runtime: signature voices (paid, designed per character), the five free Kokoro browser voices (the default), and device speechSynthesis (robot voices, last resort only). Adding a character means adding data in a few known places. No new component code: the companion shell, voice engine, and picker all key off the registries below.
Full architecture: ADR-44 in .ai/docs/DECISIONS.md and
docs/planning/VOICE_TTS_RESEARCH_2026-07-17.md. Cost model and env flags at the bottom.
1. Persona data (client-safe)
shared/companions.ts:
- Form companion (fills a form by talking): add the id to
CompanionPersonaId, an entry inCOMPANION_PERSONAS, and aCOMPANION_FORMSconfig pointing at it. - Chat persona (free conversation, like the Ship's Cook): a standalone
ChatPersonaexport instead.
Fields that matter beyond copy:
genderdrives voice ordering and the default voice. "neutral" takes any.greetingtriples as the opening line, the VoicePicker "Hear it" sample, and the audition-clip line. Write it once, correctly. The First Mate lesson: the audition script once said "Step outside, find a window" while the site said "Step outside or find a window"; clips had to be regenerated. Audition lines must quote the site copy exactly.- Voice rules from
STEERING.mdsection 1 apply to every line (no em-dashes, no AI-isms, Rye's voice).
2. Server-side persona prompt
server/lib/ship-personas.ts (form companions) or the persona's own server
module (chat personas, like server/lib/ship-cook.ts). System prompts are
server-only so client code can never coach them. Treat user text as data
(.ai/docs/security/AI-AUTOMATION-RISKS.md).
3. Portrait
client/public/images/ship/persona-<id>.webp, painterly golden-hour style
matching the cast, 640x640, target 42-102KB (the existing set's range). The
regen-character-art skill holds the style reference.
4. Free voices: nothing to add
All five Kokoro voices (Bella, Emma, Puck, George, Fable) are offered to every
character automatically from client/src/components/companion/kokoroVoices.ts.
The gender-matched one sorts first and is the default
(defaultVoiceFor). Do not add per-character Kokoro entries.
5. Signature voices: exactly two, one female + one male
server/lib/ttsVoices.ts. Rye's standing direction (2026-07-17): each
character offers one designed voice of each gender, whatever the character's
own gender.
Per entry:
key:<personaId>/<name>. Names are single nature or nautical words in the world's register (existing: Brook, Tide, Moss, Haven, Cedar, Willow, Indigo, Alder, Ember, Banner, Fern, Rowan, Clove, Barley). Never reuse.design: a full VoiceDesign paragraph (age, texture, pace, one image from the character's world). This generates the audition clip and later the clone reference, so it IS the voice. 2-3 sentences.preset+instruct: the day-one fallback. Preset is one of Qwen3-TTS's nine timbres (English-friendly: Serena and Vivian for women, Ryan, Aiden, Uncle_Fu for men); instruct is the delivery direction sent with every line.- Picker metadata:
label,tone(a few words),gender.
6. Audition clips
voice-demos/gen_signature.py (gitignored folder). Add the persona's greeting
to LINES (exact site copy) and the two voices to VOICES, then run it from
the sandbox in batches of 2 (each clip 15-25s; bash calls cap at 45s):
pip install --break-system-packages gradio_client
SIG_OUT=.../voice-demos/signature python3 gen_signature.py <persona>_<name> ...
It calls the Qwen/Qwen3-TTS HF Space /generate_voice_design endpoint and
skips existing files. ZeroGPU quota: anonymous is usually zero; ask Rye for an
HF token (Read scope) and pass token= via the HF_TOKEN env var the script
reads. Never write the token to a file. Present the clips to Rye; he approves
by ear and decides whether either becomes the character's default.
7. Optional upgrade: clone the approved voice
Preset+instruct is consistent but shares timbre across characters. To make an
approved voice fully unique: upload its clip once to DeepInfra
(POST /v1/voices/add, multipart, returns voice_id), then map
"<personaId>/<name>": "<voice_id>" into the TTS_VOICE_MAP Railway var.
server/lib/tts.ts prefers the mapped id automatically.
8. Tests
client/src/components/companion/kokoroVoices.test.ts iterates
COMPANION_PERSONAS automatically for form companions; chat personas must be
added to its personaIds lists by hand (the Ship's Cook pattern). The suite
enforces exactly two signature voices per persona, one of each gender, unique
namespaced keys, and non-trivial design/instruct text. Run:
set NODE_ENV=test&& npx vitest run client/src/components/companion/
(NODE_ENV=test matters on Rye's machine; see regen-windows-env.)
9. Ship checklist
pnpm gate(truncation + typecheck), voice tests green.- Commit
type(scope):style, push tomain, pollpnpm railway:deploysto SUCCESS. - Live-verify the picker data:
curl 'https://regencivics.earth/api/trpc/companion.voices?batch=1&input=%7B%220%22%3A%7B%22json%22%3A%7B%22persona%22%3A%22<personaId>%22%7D%7D%7D' - If touching the speak path, one sub-cent smoke test of
companion.speakproves the paid path end to end. - SHIPPED_LOG entry; DOMAIN-LANGUAGE entry if the character introduces a new load-bearing term.
Env flags and cost (facts as of 2026-07-18)
TTS_API_KEY: DeepInfra key. Account is prepaid; zero balance makescompanion.speakfail gracefully (client falls back to the free voice).TTS_VOICES_LIVE=1: master switch; unset hides signature voices everywhere and makes the key unbillable.TTS_VOICE_MAP: JSON of cloned voice ids (step 7).- Cost: DeepInfra Qwen3-TTS is $20 per 1M characters. A spoken reply is
300 chars ($0.006); greetings and previews are cached server-side (server/lib/tts.ts) so repeated lines cost once. Rate limitcompanion_tts+ 600-char cap bound abuse. - Free Kokoro voices cost nothing at any scale and stay the default unless Rye explicitly promotes a signature voice.