fix(kaori): stop voice on leave + FS360 head/shoulder work + stylish variant - #7
Merged
Merged
Conversation
…variant Voice: - Silence Kaori the moment the stage loses focus. expo-router keeps the screen mounted, so the useKithVoice unmount cleanup never fired and her voice kept talking after you navigated away/back. Added a stop() to the hook (interrupt playback + tell the backend to stop) and fire it on the focused→blurred edge. FS360 (existing "clean" version — kept, only enhanced): - Head cranks over the leading shoulder through the air (new headRoll + stronger headLead), so the spin isn't flat. - Shoulders lead the rotation to spot the landing and the hips catch up (stronger, decaying coil through the air). - She ends looking DOWNHILL, not straight at the camera (settle headLead; the idle stance also gazes downhill now). Stance/board sync: - The board no longer lingers after she's stood up — faster blend-out and a higher visibility cutoff so the stance return and the board fade finish together. New "stylish" FS360 (frontside-360-stylish) — FIRST PASS, needs on-device tuning of amounts + the board-tilt axis/sign: - Lifts the back leg as she spins (board angles tail-up), then ~3/4 through pushes the back leg down and lifts the front leg (board angles the other way), landing tail-first then nose. - Adds asymmetric leg lift + a board-tilt channel to the pose/board system. - The demo auto-loop now ALTERNATES the clean and stylish FS360 so both show. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stylish FS360 drove the board tilt on its own timeline, independent of the leg lifts, so the deck drifted away from the feet. Instead, derive the board's world transform from the actual raw foot bones each frame in KaoriStage (after vrm.update): aim the board's long axis down the line between the two feet and keep the deck facing up, so the bindings stay under the soles and the board angle FOLLOWS the legs — lift the back leg, the tail rises. The stylish pose now only moves the legs (board tilt removed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… stays Head: Kaori now looks DOWNHILL toward the board nose (front foot) at the start and end of the trick instead of square-across / off to the side — DOWNHILL_LOOK 0.32 -> 0.6 plus a small DOWNHILL_CHIN, wired into both the on-board idle stance and the FS360 settle so start and end match. Smoothness: the FS360 head channel was hard if-branches with constant values that SNAPPED (0.6->0 at trick start, 0->0.65 at air, 0.65->0.5 at the spot boundary). Replaced with one continuous curve driven off the smooth spin progress — no snaps from beginning to end. Final fade now holds the downhill riding stance instead of snapping to a neutral REST pose. Board: hold the stance/board up while ANY action is still running, not just while the session is live. A trick that outlasts the spoken reply now FINISHES with the board under her instead of spinning on after the board faded (Kaori doing a boardless 360 while the reply tail played). Arms: ship the coil-locked arm swing (pump/wrap/elbow-flex that loads on the wind-up and whips with the shoulders) so they stop dangling at her sides. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three fixes + one new trick, from a multi-agent investigation: 1. Begin/end GLITCH: the demo drove torso/head/arms at full strength even at zero stance (only legs eased in), so the head snapped ~34° stepping on/off the board. Now the WHOLE demo pose eases in/out by the stance weight — the idle↔demo handoff is a smooth blend both ways. 2. Board VANISHES before the last move: two independent causes. (a) Board presence was welded to the exact `session` flag, which Kith flips false mid-reply on a ~500ms audio-drain grace during between-sentence pauses — dropping the board in the sub-second gap before the last 360. Added a holdT grace-hold (BOARD_HOLD_SECONDS 1.2) so the board stays pinned across any gap and only retracts a beat after she's truly done; startAction re-anchors it so a late cue keeps it continuously visible. (b) The stylish leg-lift could tilt the foot-to-foot vector near-vertical, collapsing the feet-lock basis → boardLocked=false → the fallback flung the deck to the world origin. Now the basis rebuilds off world-forward and the fallback reuses the last-good transform, so the board can't fly off. 3. BACKSIDE 360 (new): frontside + backside now share one spin360PoseAt(dir, spotStart) core (frontside stays byte-identical: dir 1, spot 0.72). Backside = dir -1, totalSpin -2π (CW from above), spots later (0.55, blind first half). A new pose `dir` channel flips the body wind, head look and arm wrap in the appliers while coil stays frontside-signed so the arm load→whip TIMING is preserved (the naive coil-flip plays the arms backwards — caught in verify). "backside 360 / bs 360 / back 3" selects it; plain "360" stays frontside; the auto-loop never drags a backside session to frontside. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…side)
The trick was only set when the user's text had BOTH a demo verb
(show/demo/do) AND named the trick; if the demo instead fired from Kaori's
reply ("watch this") with requestedTrick===null, the trick stayed at its
default frontside-360 — so a backside request rendered a frontside spin
(which is why backside "looked exactly the same as frontside").
Split trick IDENTIFICATION (detectTrickId, no verb gate — also runs on the
reply) from demo INTENT, broaden the intent verbs, and ALWAYS resolve the
trick: user's ask → what the reply names → current. Never fall through to a
stale/default frontside. Dev log prints the chosen trick to confirm on-device.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Head: was LEADING the spin (headLead = dir*0.68) so the gaze swept blindly all the way around and stared backwards. Replaced with a spotting model — the body sweeps a full dir*2π via rootYaw and the neck (its child) counter-rotates to hold her gaze on the landing (DOWNHILL_LOOK − dir*2π*spin), capped at the neck limit (~75°); she holds forward as long as anatomically possible, whips FAST through the apex (a quick glance, not a slow blind sweep), and re-fixates forward — measured from the end of the revolution — landing looking downhill. Continuous end to end; seamless at spin 0/1. Knobs: NECK_CAP, HEAD_HOLD_FRAC, HEAD_WHIP_END. Arms: past the pop the coil decayed to ~0, so windup/whip died and the arms hung dead at the sides for the whole rotation. Added a continuous air driver (spinSwing = sin(π·spin): zero at both ends, peak mid-spin) that draws the arms in, wraps the hands around the torso, folds the elbows, and reaches the lead arm forward through the air, then opens for the landing. Coil-driven frontside wind-up load/throw is byte-identical; the pose.dir wrap mirror is preserved; still eased from rest by w. uz clamped so the arms never rotate past T overhead. Designed + adversarially verified via workflow (world-yaw traced per spin point both directions; continuity, neck-limit, frontside regression checked). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Flips rotate around a HORIZONTAL axis pivoting at the center of mass — a new axis vs the 360s (Y-yaw at the feet). Added a separate `pitch` pose channel (parallel to `spin`) so spins and flips never fight: a flip keeps spin=0 (no yaw, no coil Y-wind, no arm Y-wrap) and drives pitch; a spin keeps pitch=0. - riderFundamentals: RiderPose gains `pitch` (0 in REST_POSE / spin360PoseAt). - trickAnimations: TrickTimeline gains optional signed `totalFlip`; flipPoseAt (deep vertical load → pop → tight knees-to-chest tuck → absorb) with a pitch-based head SPOT that reads the two apart (wildcat blind/late re-spot, tamedog spots early); wildcat totalFlip=-2π (backflip over the tail), tamedog=+2π (frontflip over the nose). driveDemo outputs rootPitch (gated by stanceEase, zeroed on retract). - KaoriStage: whole-body transform is now a yaw*pitch quaternion pivoted at the CoM/hip (position = arcCoM − q·comLocal) so she flips around her hip on the jump arc, not her feet. Pure 360 (rootPitch=0) collapses byte-identical to the old rotation.y=rootYaw / position.y=rootY. Board-through-inversion fix: lockBoardToFeet now derives board-up from her body quaternion (rootQuat), published each frame, so the deck ROLLS with her and stays soles-down through the somersault instead of lying world-flat under an upside-down rider. - [botId].tsx: "wildcat/backflip" → wildcat, "tamedog/frontflip" → tamedog (checked before the 360 branch); flip words cue a full run. Designed + adversarially verified via workflow (CoM-pivot math, quaternion order, board-inversion, backward-compat all checked). On-device knobs: COM_LOCAL_Y (pivot height), the totalFlip signs, and _flipPitchAxis. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e straps in New SnowWorld component crossfades a full alpine scene IN as Kaori steps onto her board and OUT as she steps off — driven by the SAME stance weight that fades the board in, so "strapping in" transforms the whole stage in lockstep. - Procedural + vertex-colored, PNG-free, no drei (honors the expo-gl rules): a gradient sky (inverted vertex-colored icosahedron, unlit/BackSide), a lit snow ground disc, a ring of 14 low-poly mountains (slate→white caps), 700 THREE.Points falling snow animated in useFrame, FogExp2 for depth, and cool alpine key/fill lights. All opacities/fog/background/lights lerp off one 0→1 weight (demo.current.stance) — zero React re-renders, alloc-free per frame, and nearly free in studio mode (snow animation gated on weight > 0.02). - scene.fog would fog everything, so per the material rules Kaori's unlit MeshBasicMaterial, the brand ring, and the TrickBoard deck/base now set fog:false — only the intentional scene geometry (ground, mountains) recedes. - The static <color> background seeds frame 0; SnowWorld owns it per frame, lerping studio #0b0e17 → snow #bcd3ea. Designed + adversarially verified via workflow (expo-gl feasibility, fog-on- Kaori, single background owner, perf all checked). On-device knobs: SNOW_COUNT, fog density, mountain count, the studio↔snow color endpoints. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Overview
Fixes for the Kaori 3D companion: the voice no longer keeps talking after you leave the stage, plus FS360 animation work — head/shoulder detail on the existing trick, a synced stance/board finish, and a new "stylish" FS360 variant.
Voice — stop on leave ✅ (high confidence)
The voice kept talking after navigating away/back because expo-router keeps the stage screen mounted, so
useKithVoice's unmount cleanup never ran. Addedstop()to the hook (interrupt playback + tell the backend to stop generating) and fire it on thefocused → blurrededge (also stops the mic if listening). Fires exactly once per blur.FS360 — existing "clean" version (kept, only enhanced)
headRollchannel + strongerheadLeadthrough the air, so the spin isn't flat.Stance / board finish sync
The board no longer lingers as a faint ghost after she's stood up — faster blend-out + a higher board visibility cutoff so the stance return and the board fade finish together.
New "stylish" FS360 (
frontside-360-stylish) 🚧 first passriderFundamentals+TrickBoard).Technical notes
RiderPosefields:headRoll,backLegLift,frontLegLift,boardTilt(all default 0; the existing mini-demos inherit the defaults).TrickBoardnow appliesrotation.z = boardTilt.resetRiderBonesclears neck roll/pitch so a trick can't leave the head tilted in idle.Testing
tsc --noEmit0 errors ·biome check .0 errors.How to see it
Ask Kaori to show a 360 (e.g. "show me a frontside 360"). She does the clean version first, then the auto-loop alternates clean ↔ stylish while she talks.