# 반응 시퀀서 & 트리거 (Reactions) 상황 → 반응을 정의하는 레이어. **트리거 매퍼**(`reactions.json`) + **반응 클립**(`clips/*.json`)으로 구성. > ✅ **Phase 2 런타임 구현됨**: `../07_Viewer/reactions.html`(더블클릭 재생) — idle=배경춤(풀캔버스 리그) + 트리거(idle/error/success/focus). baked 바디 + 표정 머리(목 정합·회전) 합성은 `../../_tools/reactions_layout_render.py`가 만든 `_layout.json`을 사용(브라우저는 file:// alpha 판독 불가라 사전계산 필수). 오프라인 검증: `_reaction_preview.png`. ## 트리거 매퍼 (`reactions.json`) 상황키 → 반응 클립 이름. 앱은 상황키만 던지면 된다. ```json { "error": "gesture_no", "success": "gesture_heart", "idle": "dance_idle" } ``` ## 반응 클립 스키마 (`clips/.json`) 하나의 반응 = 레이어드 타임라인. ```jsonc { "name": "gesture_no", "duration": 2.4, // 초 "return": "idle", // 종료 후 복귀 클립(보통 배경 idle) "layers": { "body": [ // Body 트랙: 시간에 따라 rig 클립 or baked 포즈 { "t":0.0, "mode":"rig", "clip":"idle" }, { "t":0.15,"mode":"baked", "image":"noeul_body_cozy_armscross", "fade":0.2 } ], "face": [ // 표정 프레임 트랙 { "t":0.0, "expr":"neutral" }, { "t":0.3, "expr":"negative" } ], "mouth": [ // 말하기(유사 립싱크): expr↔talk 순환 { "t":0.5, "say":"안돼요", "dur":1.2, "pattern":"talk" } ], "transform": { // 리그 위 잔모션(본별 delta) — Animation.md 스키마와 동일 "head": { "rot":[ {"t":0.5,"v":0},{"t":0.8,"v":9},{"t":1.1,"v":-9},{"t":1.4,"v":9},{"t":1.7,"v":0} ] }, "chest":{ "ty":[ {"t":0.0,"v":0},{"t":0.2,"v":-4},{"t":0.5,"v":0} ] } }, "caption": [ { "t":0.5, "text":"안돼요", "dur":1.6 } ], // 옵션 말풍선 "sfx": [ { "t":0.5, "id":"nope" } ] // 옵션 효과음 } } ``` ### 필드 규칙 - `body[]`: 시간순. `mode:"rig"`+`clip` 또는 `mode:"baked"`+`image`(파일명, 확장자 생략 가능). `fade`=크로스페이드 초. - `face[]`: `expr` = 표정 프레임 키(20종 중). 시간에 스냅. - `mouth[]`: `say` 대사, `dur` 길이, `pattern:"talk"`(talk/talk_wide/현재 감정 프레임 순환). 립싱크는 근사. - `transform`: 본별 키프레임(리그 delta). Body가 baked여도 head/chest 등 트랜스폼은 적용(단 baked는 통짜라 파츠 분리 트랜스폼은 제한적 → 주로 전체/머리에 적용). - `caption`/`sfx`: 옵션. 앱 설정(말풍선/TTS)에 따라 사용. ## 노을 톤 (로파이) — 튜닝 방침 > 노을은 **차분·나른·집중** 캐릭터. 다른 캐릭터보다 **모션 진폭을 작게(고개 ±6 이하), 속도를 느리게, 대사를 부드럽게**("음, 안 돼요~" / "잘됐어요~"). 시그니처는 **비트에 고개 까딱**(로파이 감상). ## 상황 → 반응 카탈로그 | 상황키 | 클립 | Body | Face | Mouth | 잔모션 | |---|---|---|---|---|---| | `idle` | `dance_idle` | rig | smile/neutral | — | 잔잔한 그루브 루프 | | `error` | `gesture_no` | baked `cozy_armscross` | neutral→negative | "음, 안 돼요~" | 작은 고개 젓기(±6, 느리게) | | `success` | `gesture_heart` | baked `cozy_heart` | smile→love | "잘됐어요~" | 부드러운 바운스(±5) | | **`focus`** ★시그니처 | `gesture_focus` | baked `cozy_listen` | neutral→sleepy | 허밍 "음~" | **비트에 고개 까딱**(루프, 4s) + "♪" | | *(확장)* `greet` | `gesture_wave` | rig wave | smile | "안녕하세요~" | 부드러운 손 흔들기 | | *(확장)* `sleepy` | `gesture_sleepy` | baked `cozy_idle_upper` | sleepy | (하품) | 느린 끄덕 | > 사용 자산(모두 `Noeul/` 소스 md에 스펙됨): baked 포즈 `noeul_body_cozy_{armscross,heart,listen,idle_upper}` · 표정 `noeul_head_wave_{neutral,negative,smile,love,sleepy,thinking}` · hairmask. 생성 후 `../03_Assets/Library/`로 분류 복사 → 시퀀서 연결. ## 트리거 API(개념) ``` Mascot.React("success") // reactions.json으로 클립 결정 → 시퀀서 재생 → 종료 후 return 클립 Mascot.SetIdle("dance_idle")// 배경 기본 루프 Mascot.Say("...", expr) // 임시 대사(mouth+face)만 ``` 상세 앱 연동: `../08_Roadmap/App_Integration.md`.