feat(mascot): add Toshi as selectable preset with Rive lip sync#2836
Draft
M3gA-Mind wants to merge 7 commits into
Draft
feat(mascot): add Toshi as selectable preset with Rive lip sync#2836M3gA-Mind wants to merge 7 commits into
M3gA-Mind wants to merge 7 commits into
Conversation
## 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.
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
visemewas never passed to either mascot component (onlyfacewas destructured fromuseHumanMascot)Key changes
Bug fix: viseme data was never reaching the mascots
HumanPage.tsxonly destructuredfacefromuseHumanMascot, dropping theviseme: VisemeShapereturn entirely. Every mascot rendered withviseme=RESTregardless of TTS output. Fixed by:shapeToVisemeId(shape: VisemeShape): VisemeIdtovisemes.ts(nearest-neighbour snap for Rive's discrete integer states)visemethroughHumanPageto both mascot componentsToshiMascot (new component)
MasterSMonly — per designer guide, it runs body + mouth concurrently;LipSyncSMis an alternative, not additivemouthStateVM property (0–5: neutral / A / E / I / O / U); M→0, F→2 for phonemes without a dedicated Toshi shapeapp/public/mascots/toshi_mascot.rivRiveMascot (OpenHuman, fixed)
visemecorrected fromuseViewModelInstanceString→useViewModelInstanceNumber(was a silent no-op)['Main State Machine', 'LipSyncSM']app/public/mascots/tiny_mascot.rivStore
PresetMascotType = 'openhuman' | 'toshi',setPresetMascotTypeaction,selectPresetMascotTypeselectorpresetMascotTypeadded to redux-persist whitelisti18n
en.tsand all 14 locale chunk-5 filesTest plan
speakingface (defaults to state 1/A)pnpm typecheckpasses cleanpnpm i18n:checkpasses clean