Skip to content

feat(mascot): add Toshi as selectable preset with Rive lip sync#2836

Draft
M3gA-Mind wants to merge 7 commits into
tinyhumansai:mainfrom
M3gA-Mind:feat/toshi-mascot
Draft

feat(mascot): add Toshi as selectable preset with Rive lip sync#2836
M3gA-Mind wants to merge 7 commits into
tinyhumansai:mainfrom
M3gA-Mind:feat/toshi-mascot

Conversation

@M3gA-Mind
Copy link
Copy Markdown
Contributor

Summary

  • Toshi preset — adds Toshi as a second built-in mascot alongside OpenHuman in Settings → Mascot → Character via a two-tile selector
  • Rive lip sync — both mascots now drive mouth animation from TTS viseme data; fixes a silent bug where viseme was never passed to either mascot component (only face was destructured from useHumanMascot)
  • Voice section disabled when Toshi is selected, with an explanatory notice

Key changes

Bug fix: viseme data was never reaching the mascots

HumanPage.tsx only destructured face from useHumanMascot, dropping the viseme: VisemeShape return entirely. Every mascot rendered with viseme=REST regardless of TTS output. Fixed by:

  • Adding shapeToVisemeId(shape: VisemeShape): VisemeId to visemes.ts (nearest-neighbour snap for Rive's discrete integer states)
  • Wiring viseme through HumanPage to both mascot components

ToshiMascot (new component)

  • Single state machine MasterSM only — per designer guide, it runs body + mouth concurrently; LipSyncSM is an alternative, not additive
  • Controls mouthState VM property (0–5: neutral / A / E / I / O / U); M→0, F→2 for phonemes without a dedicated Toshi shape
  • Asset: app/public/mascots/toshi_mascot.riv

RiveMascot (OpenHuman, fixed)

  • viseme corrected from useViewModelInstanceStringuseViewModelInstanceNumber (was a silent no-op)
  • Both SMs run: ['Main State Machine', 'LipSyncSM']
  • Asset moved to app/public/mascots/tiny_mascot.riv

Store

  • PresetMascotType = 'openhuman' | 'toshi', setPresetMascotType action, selectPresetMascotType selector
  • presetMascotType added to redux-persist whitelist

i18n

  • 4 new keys added to en.ts and all 14 locale chunk-5 files

Test plan

  • Settings → Mascot → Character: two tiles render (OpenHuman, Toshi); selecting each switches the mascot on the Human page
  • Toshi selected: voice section grays out with notice; OpenHuman selected: voice section active
  • During AI speech: mascot mouth cycles through viseme shapes (A/E/I/O/U) in sync with TTS audio
  • Procedural fallback (no TTS): mouth still opens on speaking face (defaults to state 1/A)
  • Custom GIF / backend mascot overrides still work and clear the preset tile selection
  • pnpm typecheck passes clean
  • pnpm i18n:check passes clean

## What
- Add Toshi as a second built-in mascot preset alongside OpenHuman in
  Settings → Mascot → Character (two-tile selector).
- Switch both mascots from GIF/SVG to `.riv` Rive animations with
  full lip sync driven by TTS viseme data.
- Disable voice selection UI when Toshi is selected (Toshi uses a
  dedicated voice managed outside the app).

## How

### ToshiMascot (new)
- `app/src/features/human/Mascot/ToshiMascot.tsx` — new Rive component
  using `MasterSM` only (designer guide: MasterSM drives body + mouth
  concurrently; LipSyncSM is an alternative, not additive).
- Controls `mouthState` VM property (0–5: neutral/A/E/I/O/U).
  M→0 (closed), F→2 (E approx) for phonemes without a dedicated shape.
- `.riv` asset at `app/public/mascots/toshi_mascot.riv`.

### RiveMascot (OpenHuman)
- Switched `viseme` from `useViewModelInstanceString` → correct
  `useViewModelInstanceNumber` (was a no-op; viseme is a number in the VM).
- Added `VISEME_TO_NUM` mapping (0–7) and `rive.play/stop('talking9')`
  fallback alongside ViewModel binding.
- Both state machines run simultaneously: `['Main State Machine', 'LipSyncSM']`.
- `.riv` asset moved to `app/public/mascots/tiny_mascot.riv`.

### Viseme wiring
- `visemes.ts`: add `shapeToVisemeId(shape)` — nearest-neighbour snap
  from lerped `VisemeShape` → discrete `VisemeId` for Rive components.
- `HumanPage.tsx`: destructure `viseme` from `useHumanMascot`, convert
  via `shapeToVisemeId`, pass to both mascot components. Previously only
  `face` was destructured, leaving every mascot stuck at REST.

### Store / settings
- `mascotSlice.ts`: `PresetMascotType = 'openhuman' | 'toshi'`,
  `setPresetMascotType` action (clears custom overrides), `selectPresetMascotType`.
- `store/index.ts`: add `presetMascotType` to persist whitelist.
- `MascotPanel.tsx`: preset tile picker + voice section disabled with
  notice when Toshi is active.

### i18n
- Added keys: `settings.mascot.character.presetsLabel`,
  `settings.mascot.character.presetOpenhuman`,
  `settings.mascot.character.presetToshi`,
  `settings.mascot.voice.toshiLockedNotice` — in `en.ts` and all 14
  locale chunk-5 files.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c9e959f0-e535-4d05-aa10-125098a92005

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands and usage tips.

senamakel added 6 commits May 28, 2026 04:00
New Settings → Account → Wallet addresses page that calls the existing
openhuman.wallet_status RPC and lists every derived account. The EVM
card is highlighted and tagged "Signs Base txs" so users can confirm
which address will sign upcoming Uniswap swaps on Base.
New openhuman.wallet_buy_toshi_on_base RPC. Takes ethAmountWei, hits
QuoterV2 (0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a) for the expected
TOSHI output, applies 1% slippage, builds SwapRouter02.exactInputSingle
calldata, signs with the user's locally-derived EVM key, and broadcasts
to https://mainnet.base.org. Routes the 1% WETH/TOSHI pool (the deeper
of the two). msg.value is auto-wrapped to WETH by SwapRouter02.

Also implements the previously-stubbed PreparedKind::Swap arm in
execute_evm_quote so the standard execute_prepared flow can broadcast
swaps end-to-end. Approval still parks through the existing ApprovalGate
when enabled.

ABI selectors verified against canonical Uniswap V3 selectors
(0x04e45aaf for exactInputSingle, 0xc6a5026a for quoteExactInputSingle).
Surfaces the buy_toshi_on_base swap as a first-class tool the
orchestrator can call directly (alongside wallet_status and
wallet_prepare_transfer). Marked external_effect=true so the harness
routes every invocation through ApprovalGate before signing — real
ETH leaves the user's wallet on confirm.
…hortcut

The crypto_agent's tool allowlist is narrow by design (read → simulate →
confirm → execute), so the new one-shot swap tool wasn't reachable from
the orchestrator's delegate_do_crypto route. Adds it to the allowlist
and documents it under a new "Curated shortcuts" prompt section that
spells out the eligibility conditions (exact pair, fixed slippage) and
keeps the fallback to wallet_prepare_swap for anything else.
Forces every cloud TTS request through synthesize_reply to use the
voice id mMf8pnvS4tTEecRvNcpn regardless of what the caller passes.
Marked TEMP — strip once Settings → Voice writes a persistent default.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants