* Align Oo avatar eye spacing with evoo-logo overlap ratio Derive small-eye placement from evoo-logo.svg center spacing (~0.94 of r_big + r_small) for the shared rig, EvooMascot brand lock, and face crops. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Format brand-lock spacing test for Biome Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * Align mobile Expo packages for install --check in CI Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * ci: retrigger after flaky chart-links E2E Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
142 lines
7.6 KiB
Markdown
142 lines
7.6 KiB
Markdown
# evOo Character — Poses & Runtime Mapping
|
|
|
|
Motion timing, SFX levels, and expressor hierarchy: **[MOTION-EVOO-CHARACTER.md](./MOTION-EVOO-CHARACTER.md)** (Brand Soul / Motion God).
|
|
|
|
## Brand lock (non-negotiable)
|
|
|
|
> **Oo trademark = asymmetric Kulleraugen + size difference**
|
|
|
|
All Oo's — `EvooMascot`, `BotAvatar`, Wordmark, every pose — must stay instantly recognizable through:
|
|
|
|
1. **Consistent size asymmetry** between the two eyes (big O vs small o). Never symmetrize. Never equal radii.
|
|
2. **Kulleraugen** — big, round, glossy googly/cute sclera with dark pupils and catchlights.
|
|
3. **Logo eye spacing** — sclera centers follow `evoo-logo.svg` (`OO_EYE_CENTER_SPACING_RATIO` ≈ 0.94: close together, slight overlap).
|
|
|
|
Personality (mouth, hands, accessories, poses) builds **on top** of this signature. Never replace with Grok-bot-style emoji faces.
|
|
|
|
| Constant | Value | Role |
|
|
|----------|-------|------|
|
|
| `EVOO_EYE_BIG_R` | 30 | Capital O sclera |
|
|
| `EVOO_EYE_SMALL_R` | 18.54 | Lowercase o sclera (~φ⁻¹ of big) |
|
|
| `EVOO_EYE_ASYMMETRY_MIN_RATIO` | 1.45 | CI guard — catches accidental symmetrization |
|
|
| `OO_EYE_CENTER_SPACING_RATIO` | ~0.940 | Center distance ÷ (r_big + r_small) from `evoo-logo.svg` |
|
|
|
|
Bot avatars use the same ratio via `OO_RIG_EYES` in `packages/core/src/oo-avatar.ts`. Enforced in `evoo-character/brand-lock.ts`, `oo-avatar.test.ts`, and `brand-lock.test.ts`. Poses may adjust `eyeOpen` (squint), `gaze`, brows — **not** eye size parity or spacing drift.
|
|
|
|
---
|
|
|
|
The evOo mascot is **not** a generic emoji face. Identity comes from the **Oo geometry** (large O + small o Kulleraugen, capsule-glass hands) shared by `EvooMascot` and `BotAvatar`.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Runtime signal (status / voice phase / explicit pose)
|
|
→ resolveEvooPose / mapVoicePhaseToPose (evoo-character/pose-map.ts)
|
|
→ EvooMascot → EvooCharacterSvg (eyes + mouth + hands + accessory)
|
|
BotAvatar = persona shape silhouette + EvooMascot overlay (same soul)
|
|
Wordmark = calm EvooMascot + text, trackPointer="none"
|
|
```
|
|
|
|
Legacy pose names (`typing`, `wave`, `peek`, `profit`) normalize to canonical states via `normalizeEvooPose`.
|
|
|
|
## Pose library
|
|
|
|
| Pose | Eyes / mouth / hands | Accessory | Typical trigger |
|
|
|------|----------------------|-----------|-----------------|
|
|
| **idle** | soft smile, rest hands | — | Default; sidebar Wordmark |
|
|
| **listening** | tiny-o mouth, attentive gaze | headset | Voice phase `listening`; status `waiting_input` |
|
|
| **speaking** | speaking mouth, conductor hands | — | Voice phase `speaking`; streaming text |
|
|
| **thinking** | flat mouth, upward gaze | thought bubble | Voice phase `thinking` |
|
|
| **working** | flat mouth, typing hands | — | Status `running` / `queued`; live message bubble |
|
|
| **reading** | tiny-o, clipboard hold | clipboard | Status `booting`; onboarding („liest sich ein“); curator persona idle |
|
|
| **analyzing** | narrowed eyes, magnifier | magnifier | CTO / quant persona idle |
|
|
| **skeptical** | raised brow, side gaze | — | Manual / future tool-review moments |
|
|
| **curious** | wide eyes, peek hands | — | Builder persona idle; legacy `peek` |
|
|
| **delighted** | grin, sparkle pupil | spark | Delight / achievement beats |
|
|
| **concerned** | worried brow | — | Life-inbox persona idle |
|
|
| **waiting** | half-lidded | — | Work-inbox persona idle |
|
|
| **success** | grin + sparkle, nod hands | — | Click delight; legacy `profit` |
|
|
| **greeting** | grin, wave hand | — | Empty thread hero; legacy `wave` |
|
|
| **asleep** | closed lids | zzz | Mascot idle timeout (pointer tracking modes) |
|
|
| **error** | worried mouth | spark | Status `error` / `failed` |
|
|
| **collaborating** | soft smile, dual hands | tool | Voice awareness (subagent / handoff) |
|
|
|
|
## Runtime maps
|
|
|
|
### `mapVoicePhaseToPose(phase, collaborating?)`
|
|
|
|
Used by `VoiceStage` → `BotAvatar.pose` via `personaMascotPose`.
|
|
|
|
| Input | Pose |
|
|
|-------|------|
|
|
| `listening` | `listening` |
|
|
| `speaking` | `speaking` |
|
|
| `thinking` | `thinking` (or `collaborating` when awareness event active) |
|
|
|
|
### `mapBotStatusToPose(status, streaming?)`
|
|
|
|
Used by `BotAvatar` when no explicit `pose` prop.
|
|
|
|
| Status | Pose |
|
|
|--------|------|
|
|
| `running`, `queued`, `leased` | `working` |
|
|
| `waiting_input` | `listening` |
|
|
| `error`, `failed` | `error` |
|
|
| `booting` | `reading` |
|
|
| `idle` / unknown | `idle` |
|
|
| streaming flag | `working` |
|
|
|
|
### `resolveEvooPose({ pose, status, streaming, voicePhase, collaborating })`
|
|
|
|
Priority: **voicePhase → explicit pose → streaming → status → idle**.
|
|
|
|
### Shell / chrome call sites
|
|
|
|
| Surface | Pose source |
|
|
|---------|-------------|
|
|
| **Oo Stage (1:1 text hero)** | `personaTextPose(persona, resolveTextStagePhase(...), collaborating)` |
|
|
| **Oo Group Stage (multi-Oo)** | Active speaker → full hero; ensemble row for the rest (working pulse only — same chrome as 1:1) |
|
|
| Empty thread (legacy / non-encounter) | `greeting` (interactive, hover tracking) |
|
|
| Encounter transcript trail | Caption labels only — identity lives on Oo Stage |
|
|
| Live streaming message header | `working` (hidden in encounter mode) |
|
|
| Sidebar Wordmark | `idle` (click cycles greeting → success) |
|
|
| Bot picker / command palette | `mapBotStatusToPose(status)` |
|
|
| Voice Stage hero | `personaMascotPose(persona, phase, collaborating)` |
|
|
|
|
### Gesamterfahrung
|
|
|
|
Stage + transcript-as-trace + sound language form one coherent encounter. Poses on the stage reflect runtime status (`working`, `reading`, `listening`, …); the transcript is the audit trail, not the hero.
|
|
|
|
### No Wunderwuzzi
|
|
|
|
Every Oo is a **specialist**. Persona copy and poses assume a defined lane; unmatched bots use the `specialist` archetype (not a generic companion). Crew collaboration happens in **Gruppenunterhaltungen**, not one omnibus agent. **The ensemble should feel stronger than any single Oo** — emergent capability from differences (`docs/PRODUCT.md`). Group chats need **fun factor** — friction and banter when useful, never sterile committee tone (`docs/GROUP-CONVERSATIONS.md`).
|
|
|
|
### Personalization (persona selection)
|
|
|
|
Lifestyle-agnostic: the same archetype system serves any user. Onboarding infers **help & vibe** (geek-peer vs quiet-doer) and **suggested lanes** — spawn/select Oos to match, never a single default personality. Voice copy for `specialist` models both modes: depth when wanted, execution without lecturing. Details: `docs/PERSONA-SELECTION.md`.
|
|
|
|
**Accessibility of ambition:** skill **floor** = grandma-simple entry; skill **ceiling** = expert-grade per specialty (not mediocre-at-everything Wunderwuzzi). Shell and onboarding stay plain; specialists carry domain depth.
|
|
|
|
### Persona idle layer
|
|
|
|
`mapPersonaArchetypeToIdlePose` sets the fallback when voice is not in an active phase — e.g. engineer → `working`, curator → `reading`.
|
|
|
|
## Design constraints
|
|
|
|
- **Brand lock** — `EVOO_OO_TRADEMARK`; asymmetric Kulleraugen always; see `brand-lock.ts`.
|
|
- **No Grok-style emoji ripoff** — ownable Oo eyes, glass capsule hands, soft motion (`evoo-breathe-awake`, `evoo-wave-harmonic`).
|
|
- **Wordmark** stays pointer-calm (`trackPointer="none"`) for chrome.
|
|
- **Design tokens** from the shared ui-web palette; persona tint only on Voice bloom, not eye geometry.
|
|
|
|
## Files
|
|
|
|
| Path | Role |
|
|
|------|------|
|
|
| `evoo-character/brand-lock.ts` | Trademark constants + asymmetry guard |
|
|
| `evoo-character/types.ts` | `EvooPose`, accessories, mouth styles |
|
|
| `evoo-character/poses.ts` | `POSE_DEFINITIONS` per pose |
|
|
| `evoo-character/EvooCharacterSvg.tsx` | SVG layers (eyes, mouth, hands, accessories) |
|
|
| `evoo-character/pose-map.ts` | Runtime mappers + `resolveEvooPose` |
|
|
| `evoo-brand.tsx` | `EvooMascot`, `Wordmark`, blink/saccade life |
|
|
| `bot-avatar.tsx` | Shape + mascot composite |
|