# Persona Lab

Origin: https://persona-lab.jessald.fairystack.com

A browser-only experiment in agent-selected expressions, inspired by
https://github.com/elevenyellow/handcrafted-persona-engine.

## Runtime boundary

This app does not run Persona Engine, Live2D, an LLM, ASR, TTS, or RVC.
It bundles four original procedural Canvas characters: Pip (robot), Ember (fox),
Wisp (ghost), and Clover (sprout). The character picker changes only the renderer,
preserving the active expression, script, and running playback. Selection persists
in the browser and exports as the additive `character` field (robot/fox/ghost/sprout).
Older saved experiments without that field use Pip. Invalid saved IDs show a notice. The 17 tag/target mappings
were verified against upstream Live2D.md and EmotionAnimationService.cs on
2026-09-21. No upstream code, model, or SDK assets are bundled.

The UI lets an operator select expressions, insert their tags into a script,
preview a sequence, and download the experiment as JSON. This is an explicitly
interactive workbench. All user input stays in browser localStorage under
`persona-lab.experiment.v1`. There is no server-side experiment database,
agent mutation API, background worker, or connection to FairyStack messages.

## Text protocol

Prefix a message with `[EMOTION:😊]`. Supported emoji values:

😊 🤩 😎 😏 💪 😳 😲 🤔 👀 😤 😢 😅 🙄 💕 😂 🔥 ✨

Text before the first tag is neutral. A tag applies until the next tag.
Empty tagged cues are allowed for expression-only previews. Brackets are
reserved for tags: unknown, empty, malformed, or incomplete tags fail visibly.
Scripts are limited to 8,000 characters and 30 cues. Each cue lasts 1–4 seconds.
Playback is simulated timing, not upstream audio/phoneme timing. The neutral
state is implicit; `[EMOTION:😐]` is intentionally rejected because upstream
does not include it in its active map.

The playback controller owns one timer and an overall deadline of the summed
cue durations plus 2 seconds (maximum 122 seconds). Duplicate Play actions
are ignored. Stop, editing the script, manual expression changes, leaving the
tab, and closing the page cancel playback. A missed deadline terminates as
timed_out. Reloading an interrupted run shows cancelled and never auto-replays.
State includes step, startedAt, finishedAt, deadlineAt, and an error/reason.
Successful completion resets the face to neutral. Reduced-motion preference
disables avatar animation; expression changes still work.

## Export

Export JSON creates a local download with schema
`com.fairystack.persona-lab.experiment`, version 1, renderer, source,
secondsPerCue, cues, and lastPlayback. Cues carry mood, text, seconds, emoji,
expression, and motionGroup. This is Persona Lab data, not an importable
Persona Engine configuration.

## Read-only endpoints

- `GET /api/health`: status, version, revision.
- `GET /api/version`: same shape, deployed revision supplied by FairyStack.
- `GET /agent-guide.md`: this contract.

No credentials are required. Other HTTP methods return 405. Static serving is
allowlisted; source, manifests, and local experiment data are not served.

## Verification

`npm test` checks protocol validation and playback termination with a fake clock.
`node test/browser.mjs <origin>` checks real browser interactions, theme layouts,
reload recovery, stable selections/drafts during playback, and static/API routes.
The browser script uses a fresh browser context and only synthetic input.
