@voqalize/avatar 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/LICENSE +21 -661
  2. package/LICENSE-CC-BY-4.0 +396 -0
  3. package/README.md +191 -662
  4. package/assets/README.md +48 -0
  5. package/assets/tanya.glb +0 -0
  6. package/assets/tara.glb +0 -0
  7. package/assets/tushar.glb +0 -0
  8. package/client/Avatar.tsx +57 -0
  9. package/client/AvatarClient.ts +818 -0
  10. package/client/arjun.ts +26 -0
  11. package/client/createAvatar.ts +177 -0
  12. package/client/createCanvasAvatar.ts +72 -0
  13. package/client/index.ts +44 -0
  14. package/client/internal.ts +108 -0
  15. package/client/interviewer-female.ts +4 -0
  16. package/client/interviewer-male.ts +4 -0
  17. package/client/ishita.ts +26 -0
  18. package/client/kabir.ts +26 -0
  19. package/client/meera.ts +26 -0
  20. package/client/naina.ts +26 -0
  21. package/client/playout.ts +95 -0
  22. package/client/professional-female-a.ts +4 -0
  23. package/client/professional-female-b.ts +4 -0
  24. package/client/professional-male-a.ts +4 -0
  25. package/client/professional-male-b.ts +4 -0
  26. package/client/react.ts +13 -0
  27. package/client/supports.ts +28 -0
  28. package/client/three/assets.ts +24 -0
  29. package/client/three/budgets.ts +27 -0
  30. package/client/three/holds.ts +64 -0
  31. package/client/three/internal.ts +54 -0
  32. package/client/three/motion-limits.json +116 -0
  33. package/client/three/sequences.ts +281 -0
  34. package/client/three/tanya.ts +69 -0
  35. package/client/three/tara-rig.ts +1562 -0
  36. package/client/three/tara.ts +76 -0
  37. package/client/three/tushar.ts +65 -0
  38. package/client/types.ts +235 -0
  39. package/client/useAvatar.ts +74 -0
  40. package/client/vikram.ts +26 -0
  41. package/dist/Avatar.d.ts +44 -0
  42. package/dist/Avatar.d.ts.map +1 -0
  43. package/{client/dist → dist}/Avatar.js +2 -2
  44. package/dist/Avatar.js.map +1 -0
  45. package/dist/AvatarClient.d.ts +274 -0
  46. package/dist/AvatarClient.d.ts.map +1 -0
  47. package/dist/AvatarClient.js +712 -0
  48. package/dist/AvatarClient.js.map +1 -0
  49. package/dist/arjun.d.ts +7 -0
  50. package/dist/arjun.d.ts.map +1 -0
  51. package/dist/arjun.js +20 -0
  52. package/dist/arjun.js.map +1 -0
  53. package/dist/createAvatar.d.ts +135 -0
  54. package/dist/createAvatar.d.ts.map +1 -0
  55. package/dist/createAvatar.js +75 -0
  56. package/dist/createAvatar.js.map +1 -0
  57. package/dist/createCanvasAvatar.d.ts +22 -0
  58. package/dist/createCanvasAvatar.d.ts.map +1 -0
  59. package/dist/createCanvasAvatar.js +47 -0
  60. package/dist/createCanvasAvatar.js.map +1 -0
  61. package/dist/index.d.ts +34 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +33 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/internal.d.ts +34 -0
  66. package/dist/internal.d.ts.map +1 -0
  67. package/dist/internal.js +41 -0
  68. package/dist/internal.js.map +1 -0
  69. package/dist/interviewer-female.d.ts +4 -0
  70. package/dist/interviewer-female.d.ts.map +1 -0
  71. package/dist/interviewer-female.js +3 -0
  72. package/dist/interviewer-female.js.map +1 -0
  73. package/dist/interviewer-male.d.ts +4 -0
  74. package/dist/interviewer-male.d.ts.map +1 -0
  75. package/dist/interviewer-male.js +3 -0
  76. package/dist/interviewer-male.js.map +1 -0
  77. package/dist/ishita.d.ts +7 -0
  78. package/dist/ishita.d.ts.map +1 -0
  79. package/dist/ishita.js +20 -0
  80. package/dist/ishita.js.map +1 -0
  81. package/dist/kabir.d.ts +7 -0
  82. package/dist/kabir.d.ts.map +1 -0
  83. package/dist/kabir.js +20 -0
  84. package/dist/kabir.js.map +1 -0
  85. package/dist/meera.d.ts +7 -0
  86. package/dist/meera.d.ts.map +1 -0
  87. package/dist/meera.js +20 -0
  88. package/dist/meera.js.map +1 -0
  89. package/dist/naina.d.ts +7 -0
  90. package/dist/naina.d.ts.map +1 -0
  91. package/dist/naina.js +20 -0
  92. package/dist/naina.js.map +1 -0
  93. package/dist/playout.d.ts +42 -0
  94. package/dist/playout.d.ts.map +1 -0
  95. package/dist/playout.js +85 -0
  96. package/dist/playout.js.map +1 -0
  97. package/dist/professional-female-a.d.ts +4 -0
  98. package/dist/professional-female-a.d.ts.map +1 -0
  99. package/dist/professional-female-a.js +3 -0
  100. package/dist/professional-female-a.js.map +1 -0
  101. package/dist/professional-female-b.d.ts +4 -0
  102. package/dist/professional-female-b.d.ts.map +1 -0
  103. package/dist/professional-female-b.js +3 -0
  104. package/dist/professional-female-b.js.map +1 -0
  105. package/dist/professional-male-a.d.ts +4 -0
  106. package/dist/professional-male-a.d.ts.map +1 -0
  107. package/dist/professional-male-a.js +3 -0
  108. package/dist/professional-male-a.js.map +1 -0
  109. package/dist/professional-male-b.d.ts +4 -0
  110. package/dist/professional-male-b.d.ts.map +1 -0
  111. package/dist/professional-male-b.js +3 -0
  112. package/dist/professional-male-b.js.map +1 -0
  113. package/dist/react.d.ts +13 -0
  114. package/dist/react.d.ts.map +1 -0
  115. package/dist/react.js +13 -0
  116. package/dist/react.js.map +1 -0
  117. package/dist/supports.d.ts +20 -0
  118. package/dist/supports.d.ts.map +1 -0
  119. package/dist/supports.js +26 -0
  120. package/dist/supports.js.map +1 -0
  121. package/dist/three/assets.d.ts +24 -0
  122. package/dist/three/assets.d.ts.map +1 -0
  123. package/dist/three/assets.js +22 -0
  124. package/dist/three/assets.js.map +1 -0
  125. package/dist/three/budgets.d.ts +17 -0
  126. package/dist/three/budgets.d.ts.map +1 -0
  127. package/dist/three/budgets.js +21 -0
  128. package/dist/three/budgets.js.map +1 -0
  129. package/dist/three/holds.d.ts +33 -0
  130. package/dist/three/holds.d.ts.map +1 -0
  131. package/dist/three/holds.js +56 -0
  132. package/dist/three/holds.js.map +1 -0
  133. package/dist/three/internal.d.ts +52 -0
  134. package/dist/three/internal.d.ts.map +1 -0
  135. package/dist/three/internal.js +49 -0
  136. package/dist/three/internal.js.map +1 -0
  137. package/dist/three/motion-limits.json +116 -0
  138. package/dist/three/sequences.d.ts +71 -0
  139. package/dist/three/sequences.d.ts.map +1 -0
  140. package/dist/three/sequences.js +262 -0
  141. package/dist/three/sequences.js.map +1 -0
  142. package/dist/three/tanya.d.ts +35 -0
  143. package/dist/three/tanya.d.ts.map +1 -0
  144. package/dist/three/tanya.js +51 -0
  145. package/dist/three/tanya.js.map +1 -0
  146. package/dist/three/tara-rig.d.ts +403 -0
  147. package/dist/three/tara-rig.d.ts.map +1 -0
  148. package/dist/three/tara-rig.js +1500 -0
  149. package/dist/three/tara-rig.js.map +1 -0
  150. package/dist/three/tara.d.ts +35 -0
  151. package/dist/three/tara.d.ts.map +1 -0
  152. package/dist/three/tara.js +58 -0
  153. package/dist/three/tara.js.map +1 -0
  154. package/dist/three/tushar.d.ts +31 -0
  155. package/dist/three/tushar.d.ts.map +1 -0
  156. package/dist/three/tushar.js +47 -0
  157. package/dist/three/tushar.js.map +1 -0
  158. package/dist/types.d.ts +149 -0
  159. package/dist/types.d.ts.map +1 -0
  160. package/dist/types.js +140 -0
  161. package/dist/types.js.map +1 -0
  162. package/dist/useAvatar.d.ts +39 -0
  163. package/dist/useAvatar.d.ts.map +1 -0
  164. package/dist/useAvatar.js +35 -0
  165. package/dist/useAvatar.js.map +1 -0
  166. package/dist/vikram.d.ts +7 -0
  167. package/dist/vikram.d.ts.map +1 -0
  168. package/dist/vikram.js +20 -0
  169. package/dist/vikram.js.map +1 -0
  170. package/package.json +110 -25
  171. package/src/avatar.d.ts +218 -120
  172. package/src/avatar.js +950 -213
  173. package/src/behavior.d.ts +42 -0
  174. package/src/behavior.js +114 -0
  175. package/src/camera.js +29 -0
  176. package/src/canvas/author/parts/eye.mjs +722 -0
  177. package/src/canvas/author/parts/hand.mjs +1156 -0
  178. package/src/canvas/author/parts/mouth.mjs +741 -0
  179. package/src/canvas/author/parts/nose.mjs +100 -0
  180. package/src/canvas/author/parts/skin-detail.mjs +67 -0
  181. package/src/canvas/author/path.mjs +283 -0
  182. package/src/canvas/author/rig.mjs +405 -0
  183. package/src/canvas/avatars/round/face.d.mts +3 -0
  184. package/src/canvas/avatars/round/face.mjs +1307 -0
  185. package/src/canvas/create-rig.d.ts +15 -0
  186. package/src/canvas/create-rig.js +100 -0
  187. package/src/canvas/data/img/professional-female-a-hair-back.webp +0 -0
  188. package/src/canvas/data/img/professional-female-a-hair-front.webp +0 -0
  189. package/src/canvas/data/img/professional-female-a-top-body.webp +0 -0
  190. package/src/canvas/data/img/professional-female-b-hair-back.webp +0 -0
  191. package/src/canvas/data/img/professional-female-b-hair-front.webp +0 -0
  192. package/src/canvas/data/img/professional-female-b-top-body.webp +0 -0
  193. package/src/canvas/data/img/professional-male-a-hair-back.webp +0 -0
  194. package/src/canvas/data/img/professional-male-a-hair-front.webp +0 -0
  195. package/src/canvas/data/img/professional-male-a-top-body.webp +0 -0
  196. package/src/canvas/data/img/professional-male-b-hair-back.webp +0 -0
  197. package/src/canvas/data/img/professional-male-b-hair-front.webp +0 -0
  198. package/src/canvas/data/img/professional-male-b-top-body.webp +0 -0
  199. package/src/canvas/data/img/round-m3-hair-back.webp +0 -0
  200. package/src/canvas/data/img/round-m3-hair-front.webp +0 -0
  201. package/src/canvas/data/img/round-m3-top-body.webp +0 -0
  202. package/src/canvas/data/img/round-w1-hair-back.webp +0 -0
  203. package/src/canvas/data/img/round-w1-hair-front.webp +0 -0
  204. package/src/canvas/data/img/round-w1-top-body.webp +0 -0
  205. package/src/canvas/data/interviewer-female.rig.json +1 -0
  206. package/src/canvas/data/interviewer-male.rig.json +1 -0
  207. package/src/canvas/data/professional-female-a.rig.json +1 -0
  208. package/src/canvas/data/professional-female-b.rig.json +1 -0
  209. package/src/canvas/data/professional-male-a.rig.json +1 -0
  210. package/src/canvas/data/professional-male-b.rig.json +1 -0
  211. package/src/canvas/src/live.js +508 -0
  212. package/src/canvas/src/render2d.js +218 -0
  213. package/src/canvas/src/rig.js +297 -0
  214. package/src/canvas/src/vocab.js +96 -0
  215. package/src/clips.js +18 -7
  216. package/src/conformance.js +119 -0
  217. package/src/emotions.js +8 -5
  218. package/src/face-core.js +27 -1
  219. package/src/face-myna.d.ts +7 -0
  220. package/src/face-myna.js +192 -131
  221. package/src/face-peep-control-plane.js +167 -0
  222. package/src/face-peep.d.ts +7 -0
  223. package/src/face-peep.js +274 -116
  224. package/src/face-wren.d.ts +7 -0
  225. package/src/face-wren.js +17 -19
  226. package/src/faces.d.ts +12 -0
  227. package/src/faces.js +53 -0
  228. package/src/gaze.js +434 -58
  229. package/src/hand.js +120 -91
  230. package/src/head.js +134 -0
  231. package/src/idle.js +227 -119
  232. package/src/interjections.js +200 -31
  233. package/src/params.js +8 -4
  234. package/src/perform.js +7 -9
  235. package/src/prosody.js +647 -0
  236. package/src/rig.d.ts +24 -0
  237. package/src/rig.js +32 -0
  238. package/src/speech-timing.js +23 -0
  239. package/src/visemes.js +110 -24
  240. package/client/dist/Avatar.d.ts +0 -27
  241. package/client/dist/Avatar.d.ts.map +0 -1
  242. package/client/dist/Avatar.js.map +0 -1
  243. package/client/dist/AvatarClient.d.ts +0 -139
  244. package/client/dist/AvatarClient.d.ts.map +0 -1
  245. package/client/dist/AvatarClient.js +0 -223
  246. package/client/dist/AvatarClient.js.map +0 -1
  247. package/client/dist/index.d.ts +0 -22
  248. package/client/dist/index.d.ts.map +0 -1
  249. package/client/dist/index.js +0 -22
  250. package/client/dist/index.js.map +0 -1
  251. package/client/dist/types.d.ts +0 -86
  252. package/client/dist/types.d.ts.map +0 -1
  253. package/client/dist/types.js +0 -31
  254. package/client/dist/types.js.map +0 -1
  255. package/client/dist/useAvatar.d.ts +0 -42
  256. package/client/dist/useAvatar.d.ts.map +0 -1
  257. package/client/dist/useAvatar.js +0 -51
  258. package/client/dist/useAvatar.js.map +0 -1
  259. package/client/src/Avatar.tsx +0 -33
  260. package/client/src/AvatarClient.ts +0 -270
  261. package/client/src/index.ts +0 -22
  262. package/client/src/types.ts +0 -104
  263. package/client/src/useAvatar.ts +0 -85
  264. package/docs/contract-avatar.md +0 -371
  265. package/docs/contract-protocol.md +0 -461
@@ -0,0 +1,35 @@
1
+ /**
2
+ * tanya — the third 3-D character, and like `tushar.ts` a copy of `tara.ts`.
3
+ *
4
+ * Her GLB is tara's scripts run with her landmarks and atlas layout in place of
5
+ * hers (`characters/tanya/`, whose README has the provenance and the commands),
6
+ * so the rig, the mixer, the sequences and her tuning are all hers, unmodified;
7
+ * the one seam is `TaraRigOptions.url`.
8
+ *
9
+ * She is the first character whose atlas is not tara's window. Her hair is the
10
+ * silhouette down past the jaw, out to |u| 0.761 where tara's window stops at
11
+ * 0.64, so `characters/tanya/face_texture.py` widens it to 1408x1568 at the same
12
+ * 800 pixels per face height. That is a fact about the asset and costs this file
13
+ * nothing — the rig reads the window from the GLB.
14
+ */
15
+ import type { PipecatClient } from "@pipecat-ai/client-js";
16
+ export interface AvatarOptions {
17
+ readonly mount: HTMLElement;
18
+ readonly client: PipecatClient;
19
+ /** Viseme amplitude, 0..2. `1` is as authored. */
20
+ readonly mouthGain?: number;
21
+ /** Gesture-clip amplitude, 0..2. */
22
+ readonly gestureGain?: number;
23
+ /** Idle/liveness amplitude, 0..2. */
24
+ readonly motionGain?: number;
25
+ /** Fires when the GLB is in the scene — for a capture tool, not a consumer. */
26
+ readonly onReady?: () => void;
27
+ }
28
+ export interface AvatarInstance {
29
+ destroy(): void;
30
+ }
31
+ /** The optional driving-UI declaration; one list for all three characters
32
+ * (`sequences.ts`), because they are one rig. */
33
+ export declare const supports: import("../createAvatar.js").AvatarSupport;
34
+ export declare function createAvatar(options: AvatarOptions): AvatarInstance;
35
+ //# sourceMappingURL=tanya.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tanya.d.ts","sourceRoot":"","sources":["../../client/three/tanya.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAQ3D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qCAAqC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAAG,OAAO,IAAI,IAAI,CAAC;CAAE;AAEpD;kDACkD;AAClD,eAAO,MAAM,QAAQ,4CAAmB,CAAC;AAEzC,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CA0BnE"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * tanya — the third 3-D character, and like `tushar.ts` a copy of `tara.ts`.
3
+ *
4
+ * Her GLB is tara's scripts run with her landmarks and atlas layout in place of
5
+ * hers (`characters/tanya/`, whose README has the provenance and the commands),
6
+ * so the rig, the mixer, the sequences and her tuning are all hers, unmodified;
7
+ * the one seam is `TaraRigOptions.url`.
8
+ *
9
+ * She is the first character whose atlas is not tara's window. Her hair is the
10
+ * silhouette down past the jaw, out to |u| 0.761 where tara's window stops at
11
+ * 0.64, so `characters/tanya/face_texture.py` widens it to 1408x1568 at the same
12
+ * 800 pixels per face height. That is a fact about the asset and costs this file
13
+ * nothing — the rig reads the window from the GLB.
14
+ */
15
+ import { AvatarClient, createSvgAvatar } from "../internal.js";
16
+ import { ASSETS } from "./assets.js";
17
+ import { BLENDER_ACTIONS, BLENDER_SEQUENCES, BLENDER_SUPPORTS } from "./sequences.js";
18
+ import { headHold } from "./holds.js";
19
+ import { createTaraRig, TARA_TUNING } from "./tara-rig.js";
20
+ /** The optional driving-UI declaration; one list for all three characters
21
+ * (`sequences.ts`), because they are one rig. */
22
+ export const supports = BLENDER_SUPPORTS;
23
+ export function createAvatar(options) {
24
+ const { mount, client, onReady, ...gains } = options;
25
+ if (!mount)
26
+ throw new TypeError("createAvatar: `mount` is required");
27
+ if (!client)
28
+ throw new TypeError("createAvatar: `client` is required");
29
+ const rigOptions = { onReady, url: ASSETS.tanya };
30
+ // TARA-SPECIFIC, kept on purpose: `TARA_TUNING` (her mouth and motion gains)
31
+ // is the first thing to question if she reads wrong in a call.
32
+ const widget = createSvgAvatar({
33
+ mount, rig: createTaraRig, rigOptions, hand: false,
34
+ sequences: BLENDER_SEQUENCES, actions: BLENDER_ACTIONS, ...TARA_TUNING,
35
+ headHold: headHold("tanya"), ...gains,
36
+ });
37
+ const driver = new AvatarClient(widget);
38
+ const detach = driver.attach(client);
39
+ let destroyed = false;
40
+ return {
41
+ destroy() {
42
+ if (destroyed)
43
+ return;
44
+ destroyed = true;
45
+ detach();
46
+ driver.destroy();
47
+ widget.destroy();
48
+ },
49
+ };
50
+ }
51
+ //# sourceMappingURL=tanya.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tanya.js","sourceRoot":"","sources":["../../client/three/tanya.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAkB3D;kDACkD;AAClD,MAAM,CAAC,MAAM,QAAQ,GAAG,gBAAgB,CAAC;AAEzC,MAAM,UAAU,YAAY,CAAC,OAAsB;IACjD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IACrD,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,SAAS,CAAC,mCAAmC,CAAC,CAAC;IACrE,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,SAAS,CAAC,oCAAoC,CAAC,CAAC;IAEvE,MAAM,UAAU,GAAmB,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;IAClE,6EAA6E;IAC7E,+DAA+D;IAC/D,MAAM,MAAM,GAAG,eAAe,CAAC;QAC7B,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK;QAClD,SAAS,EAAE,iBAAiB,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,WAAW;QACtE,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,GAAG,KAAK;KACtC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAErC,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,OAAO;QACL,OAAO;YACL,IAAI,SAAS;gBAAE,OAAO;YACtB,SAAS,GAAG,IAAI,CAAC;YACjB,MAAM,EAAE,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,403 @@
1
+ /**
2
+ * tara's renderer: the `AvatarRig` contract (`apply(frame)` / `destroy()`)
3
+ * over the Blender-authored GLB.
4
+ *
5
+ * The whole file is one idea — **the pose channel is the interface, and every
6
+ * mapping here is a translation of one channel into the one control that
7
+ * renders it.** `scripts/morphs.py` authored the shape keys under the library's
8
+ * own channel names precisely so this file never has to interpret a viseme, a
9
+ * state or an emotion; it receives a fully mixed pose and moves geometry.
10
+ *
11
+ * Three kinds of control, in the order they appear below:
12
+ *
13
+ * morph targets the face itself — lips, jaw, lids, brows, and the mouth
14
+ * interior that has to choreograph with them
15
+ * head group `headYaw` / `headPitch` / `headRoll`, as a rotation of the
16
+ * parts that ride the skull about the jaw-angle pivot
17
+ * eye globes `pupilX` / `pupilY`, as a rotation of the eyeball, because
18
+ * the iris is painted onto a sphere and cannot slide
19
+ *
20
+ * and a fourth, for the body: `shoulderL/R` are morph targets on the torso
21
+ * shell like any face channel, and `breath`, `torsoLean` and `torsoTurn` are
22
+ * each one transform of a group — a swell, a scale, a sway (see `BODY`).
23
+ *
24
+ * An asset may add a fifth: expression maps, which change the face's *light*
25
+ * where a smile or a raised brow would, because moving the geometry cannot
26
+ * (see `expressive`).
27
+ */
28
+ import type { AvatarRig } from "../internal.js";
29
+ /**
30
+ * Head motion, in degrees at the channel's own clamp of ±1.4.
31
+ *
32
+ * These are the program's *working* envelope, mapped so that a channel pinned to
33
+ * its limit lands exactly on it. That is the point of scaling by the clamp
34
+ * rather than by 1: the mixer cannot ask for more than the envelope allows, and
35
+ * the hard ceiling stays unreachable by construction instead of by a second
36
+ * clamp nobody runs.
37
+ *
38
+ * **It opened on 2026-09-12, from yaw ±6°, pitch ±5°, roll ±3°.** That
39
+ * envelope made Busso's neutral-speech numbers unreachable: his mean
40
+ * per-sentence pitch *range* is 9.5°, which is 95 % of everything ±5° could
41
+ * ever produce, so speaking alone would have swung the channel corner to corner
42
+ * and a deliberate nod on top of it would have had nowhere left to go. What had
43
+ * held it there was the asset, not anatomy — the neck's follow was linear and
44
+ * drew a second jawline past a few degrees — and once that follow became exact
45
+ * (`NECK_QUAD` below) pitch could open to 24 and roll to 8.
46
+ *
47
+ * Yaw is still the smallest because it is the one axis this rig is genuinely
48
+ * constrained on: the albedo is a front-orthographic projection of a 0.34-deep
49
+ * shell, and a large turn is where that reads as a cardboard cutout rather than
50
+ * a head. **It opened from 9° to 15° on 2026-09-18.** The cutout was measured
51
+ * rather than assumed — a ladder rendered at 9/12/15/18/21/25/30 and read at
52
+ * crop on all three characters is clean to 21° on tara and to 18° on tanya and
53
+ * tushar. 9° was therefore set at half of where the artefact actually begins,
54
+ * and the stiffness the owner reported on tanya's turns was that margin, not
55
+ * her asset. 15° keeps 3° of headroom on the tightest of the three.
56
+ *
57
+ * This is not for speech: Busso wants ±1.15° of yaw in neutral conversation and
58
+ * always did. It is for a head that turns to *look* at something, which is what
59
+ * mocap drives and what pegged the channel — a real 25° turn still saturates at
60
+ * 15°, so this widens the envelope without making it generous.
61
+ *
62
+ * And it is not the angle a pose may be *held* at, which is a stricter question
63
+ * with its own measurement per character (`motion-limits.json`, applied through
64
+ * `holds.ts`): a turn that returns is forgiven what a sustained one is not. The
65
+ * two numbers differ by about 3x on yaw and neither is a correction of the
66
+ * other.
67
+ *
68
+ * **Editing these needs no rebuild, but it is not free.** The neck's fields
69
+ * carry no angle, so `tara.glb` cannot go stale against them. What a number
70
+ * here does move:
71
+ *
72
+ * - Every clip is authored in channel units, so a degree here re-sizes every
73
+ * clip driving that axis. `test/nods.test.ts` bands *pitch* only — `down`,
74
+ * `up`, `upFirst` — and computes `yawPP` without ever asserting it. A yaw
75
+ * change moves nothing there; a pitch change moves four tests.
76
+ * - `head_parallax.py` and `validate_morphs.py` quote their gates at this
77
+ * envelope. `validate_morphs` reads `morphs.head_envelope()`, but
78
+ * `head_parallax.POSES` hardcoded `yaw 9` until 2026-09-18 and would have
79
+ * gone on grading 9° while the rig shipped 15° — a gate defending a number
80
+ * nothing used. It derives both angles from here now.
81
+ * - The yaw twist's two fields are an expansion in the angle, so their error
82
+ * grows as θ²/6. Against the exact rotation at the maximum ramp
83
+ * (`NECK_TWIST` = 0.5) that is 0.31 % at 9°, 0.85 % at 15°, 1.23 % at 18°
84
+ * (`morphs.neck_twist`). The note here used to read as a wall at 9°; it is
85
+ * not one — 15° costs under a percent of a displacement that is itself a
86
+ * fraction of the neck's radius.
87
+ *
88
+ * TARA-SPECIFIC: each number is her reach before an artefact shows — yaw by
89
+ * the cutout, pitch by the neck fold that starts to crease at 24° chin-up.
90
+ * Both were measured on the shipping surface, one axis at a time. This is still
91
+ * one shared pair of
92
+ * constants for all three characters, which holds only because 15° is inside
93
+ * every one of them; the first character that wants more than its neighbours
94
+ * forces the envelope onto `TaraRigOptions` as a per-character fact. A second
95
+ * avatar measures its own with the audit.
96
+ */
97
+ export declare const HEAD_CLAMP = 1.4;
98
+ export declare const HEAD_DEG: {
99
+ yaw: number;
100
+ pitch: number;
101
+ roll: number;
102
+ };
103
+ /** Degrees per pose unit: the globe turns `pupil * GAZE_TRAVEL / GLOBE_RADIUS`
104
+ * radians, and the head reaches `HEAD_DEG` at the clamp.
105
+ *
106
+ * Exported through `internal.ts` for the same reason as the head envelope: an
107
+ * instrument that asks for "eyes on the camera through a head turn" is running
108
+ * this conversion backwards, and a second copy of it would be a second thing to
109
+ * update when the eye tile or the globe changes. */
110
+ export declare const EYE_DEG: {
111
+ x: number;
112
+ y: number;
113
+ };
114
+ /**
115
+ * The mixer's per-rig calibration for tara, passed by `tara.ts` and the motion
116
+ * audit so both measure the same face. A pose unit is an angle here and a
117
+ * pixel count on an SVG face, so the speech layer's amplitudes are tuned per
118
+ * rig rather than in the library: `prosodyHeadGain` sizes speech-rhythm head
119
+ * motion against this face's own motion envelope.
120
+ *
121
+ * `oculomotor` is the eye-head system sized for this face (`gaze.js`). Her eye
122
+ * turns 10° a pupil unit and her head 6.4° of yaw a head unit, and the shared
123
+ * look table — drawn for a line face, whose pupils cross most of an eye — put
124
+ * every look in the eyes: a thinking look away was the iris parked in the
125
+ * corner of the socket for two thirds of the state, which is side-eye, not
126
+ * thought. Here the head carries about 60 % of a look and the eyes land a third
127
+ * of the way off centre, where a real eye-head shift leaves them (Freedman &
128
+ * Sparks; Pejsa & Andrist). The comment on each target is its world angle,
129
+ * x right and y down.
130
+ *
131
+ * vor Real gain in the light is close to 1. A little under leaves the
132
+ * head some say, so a nod carries the eyes a touch with it rather
133
+ * than pinning them to the lens. Vertically it is well under
134
+ * (2026-09-15): her pitch is a shell tipping on a photograph and
135
+ * reads as a fraction of what it is, so the eyes' full answer to
136
+ * it read as the eyes moving on their own — at 0.8, THINKING's
137
+ * up-look rolled the iris to the lid with white beneath it, and
138
+ * the reply that followed dropped it into a downcast look with the
139
+ * lid riding down, while the head was still coming back. At 0.35
140
+ * the eyes mostly go where the head takes them.
141
+ * range How far the reflex may carry the eye in the socket. Unlimited,
142
+ * an up-look's onset put the whole 8° in the eye before the neck
143
+ * moved — the iris pinned under the lid, white beneath it, which
144
+ * on this photograph reads as an eye-roll. Up 0.45 is 2.8° and
145
+ * down 0.5 is 3.1°; the head covers the rest, as it does once the
146
+ * eye nears its effective range. Both were looser (0.55, 0.85)
147
+ * and a gap between turns hit both ends of them every time.
148
+ * lidFollow Down 0.38 keeps the upper lid on the iris: the lid travels
149
+ * 0.068 face units per lid unit and the iris 0.026 per pupil unit.
150
+ * Up a little less, so an upward look opens the eye a hair instead
151
+ * of dragging the lid along with it.
152
+ * avert A conversational look away keeps under half its size in the
153
+ * eyes and gives the neck 0.6 of it — on this face the eyes alone
154
+ * could only make it a glance sideways.
155
+ * head The follow's launch and cruise, in head units. A real head
156
+ * reaches a 7-8° shift in about 0.4 s, its speed scaling with the
157
+ * size of the shift; the shared amble took a thinking look 1.2 s,
158
+ * and the reflex, doing its job, held the eyes in the corner of
159
+ * the socket the whole way.
160
+ *
161
+ * `trunkFollow` is 0.3, under the line faces' 0.45. Live2D's face-tracking
162
+ * sample gives the body a third of the head's yaw (BodyAngleX 10 against
163
+ * AngleX 30; docs/research-head-rotation.md § 5 item 6), and that is the
164
+ * benchmark to sit at, not above. Here it matters for a reason a line face
165
+ * does not have: the neck's outline holds under a twist by construction
166
+ * (`morphs.neck_twist`), so the trunk's sway is what is left moving it — a
167
+ * quarter to a third of it at the yaw peak in the recorded call, read as the
168
+ * neck sliding. TARA-SPECIFIC in its evidence only: a second Blender avatar
169
+ * starts from 0.3 and checks its own outline at crop.
170
+ */
171
+ export declare const TARA_TUNING: {
172
+ readonly prosodyHeadGain: 1;
173
+ readonly prosodyFaceGain: 1;
174
+ readonly saccadeGain: 2.4;
175
+ readonly aversionGain: 1.8;
176
+ readonly trunkFollow: 0.3;
177
+ readonly brows: {
178
+ readonly range: readonly [-0.28, 0.24];
179
+ readonly floor: 0.11;
180
+ readonly forms: readonly [{
181
+ readonly p: 0.38;
182
+ readonly inner: 0;
183
+ readonly angle: 0;
184
+ }, {
185
+ readonly p: 0.34;
186
+ readonly inner: 0.26;
187
+ readonly angle: 0;
188
+ }, {
189
+ readonly p: 0.28;
190
+ readonly inner: 0;
191
+ readonly angle: 0.3;
192
+ }];
193
+ };
194
+ readonly states: {
195
+ readonly WORKING: {
196
+ readonly pose: {
197
+ readonly headPitch: 0.04;
198
+ readonly lidL: -0.08;
199
+ readonly lidR: -0.08;
200
+ readonly shoulderL: 0.06;
201
+ readonly shoulderR: 0.06;
202
+ };
203
+ };
204
+ readonly CANT_HEAR: {
205
+ readonly pose: {
206
+ readonly torsoLean: 0.22;
207
+ readonly headPitch: 0.1;
208
+ readonly browRaiseL: -0.14;
209
+ readonly browRaiseR: -0.14;
210
+ readonly browInnerL: 0.22;
211
+ readonly browInnerR: 0.18;
212
+ readonly mouthPress: 0.4;
213
+ readonly mouthCornerL: -0.1;
214
+ readonly mouthCornerR: -0.1;
215
+ };
216
+ };
217
+ readonly SEARCHING_SCREEN: {
218
+ readonly pose: {
219
+ readonly mouthPress: 0.4;
220
+ readonly mouthCornerL: -0.08;
221
+ readonly mouthCornerR: -0.08;
222
+ readonly browRaiseL: -0.1;
223
+ readonly browRaiseR: -0.06;
224
+ };
225
+ };
226
+ };
227
+ readonly oculomotor: {
228
+ readonly angles: {
229
+ readonly eye: {
230
+ x: number;
231
+ y: number;
232
+ };
233
+ readonly head: {
234
+ x: number;
235
+ y: number;
236
+ };
237
+ };
238
+ readonly vor: {
239
+ readonly x: 0.8;
240
+ readonly y: 0.35;
241
+ };
242
+ readonly range: {
243
+ readonly x: 0.8;
244
+ readonly up: 0.45;
245
+ readonly down: 0.5;
246
+ };
247
+ readonly lidFollow: {
248
+ readonly down: 0.38;
249
+ readonly up: 0.3;
250
+ };
251
+ readonly avert: {
252
+ readonly eye: 0.45;
253
+ readonly head: 0.6;
254
+ };
255
+ readonly head: {
256
+ readonly accel: 16;
257
+ readonly speed: 3.5;
258
+ };
259
+ readonly targets: {
260
+ readonly OWN_SCREEN: {
261
+ readonly px: 0.14;
262
+ readonly py: 0.24;
263
+ readonly hx: 0.1;
264
+ readonly hy: 0.06;
265
+ };
266
+ readonly USER_EAR: {
267
+ readonly px: -0.2;
268
+ readonly py: 0.05;
269
+ readonly hx: 0.4;
270
+ readonly hy: 0.02;
271
+ readonly roll: 0.55;
272
+ };
273
+ readonly SCREEN_CENTER: {
274
+ readonly px: 0;
275
+ readonly py: -0.06;
276
+ readonly hx: 0;
277
+ readonly hy: -0.14;
278
+ };
279
+ readonly SCREEN_LEFT: {
280
+ readonly px: -0.42;
281
+ readonly py: -0.1;
282
+ readonly hx: -0.8;
283
+ readonly hy: -0.05;
284
+ };
285
+ readonly SCREEN_RIGHT: {
286
+ readonly px: 0.42;
287
+ readonly py: -0.1;
288
+ readonly hx: 0.8;
289
+ readonly hy: -0.05;
290
+ };
291
+ readonly SCREEN_TOP: {
292
+ readonly px: 0;
293
+ readonly py: -0.26;
294
+ readonly hx: 0;
295
+ readonly hy: -0.28;
296
+ };
297
+ readonly SCREEN_WORK: {
298
+ readonly px: -0.35;
299
+ readonly py: 0.18;
300
+ readonly hx: -0.55;
301
+ readonly hy: 0.12;
302
+ };
303
+ readonly AWAY_THINKING: {
304
+ readonly px: -0.3;
305
+ readonly py: -0.4;
306
+ readonly hx: -0.75;
307
+ readonly hy: -0.33;
308
+ readonly roll: 0.08;
309
+ };
310
+ readonly AWAY_RIGHT: {
311
+ readonly px: 0.3;
312
+ readonly py: -0.36;
313
+ readonly hx: 0.72;
314
+ readonly hy: -0.3;
315
+ readonly roll: -0.06;
316
+ };
317
+ readonly AWAY_DOWN: {
318
+ readonly px: -0.28;
319
+ readonly py: 0.45;
320
+ readonly hx: -0.62;
321
+ readonly hy: 0.16;
322
+ readonly roll: 0.04;
323
+ };
324
+ readonly AWAY_SIDE: {
325
+ readonly px: 0.32;
326
+ readonly py: 0.08;
327
+ readonly hx: 0.72;
328
+ readonly hy: 0.02;
329
+ readonly roll: -0.03;
330
+ };
331
+ };
332
+ };
333
+ };
334
+ export interface TaraRigOptions {
335
+ /** Called once the GLB is in the scene, for a capture tool that must wait. */
336
+ readonly onReady?: () => void;
337
+ /** The character's GLB, when it is not tara. The second-character seam: every
338
+ * build fact the rig reads (jaw-shadow tile, rim depth, morph names) travels
339
+ * in the GLB's own extras, so a character built by the same scripts needs
340
+ * nothing else. Tara's tuning is still applied, which is the experiment. */
341
+ readonly url?: string;
342
+ /** `false` leaves an asset's expression maps unread, for a capture tool
343
+ * comparing the face with and without them. */
344
+ readonly expression?: boolean;
345
+ }
346
+ /**
347
+ * The hair's roll, which is the one thing in this rig that is not a function of
348
+ * the pose alone.
349
+ *
350
+ * A hank that hangs past the jaw is lying on a shoulder, and a shoulder does not
351
+ * tilt when the head does. Rolled rigidly with the skull it lifts off the collar
352
+ * and the page shows through behind it, so the shell gives up `hold` of the
353
+ * roll at its lowest rows and none at the crown, graded by `morphs.hair_hold`.
354
+ * That is the static half and it is what fixes the gap.
355
+ *
356
+ * The other half is why roll read as a hinge at all. A rigid rotation about a
357
+ * fixed point is a hinge — there is nothing else in it — and what a real head
358
+ * tilt has that this lacked is hair that arrives late and settles. Live2D gives
359
+ * every hank a spring for exactly this (`docs/research-head-rotation.md` § 3.1:
360
+ * mobility ~0.95, delay 0.8-0.9, one clear overshoot), so this is a spring on
361
+ * the hair's own angle chasing the share of the roll it agrees to take.
362
+ *
363
+ * It is on the hair and not on the head's channels on purpose. The mixer's
364
+ * per-channel time constants are shared with the SVG faces and every clip in the
365
+ * library is authored pre-compensated for them, so a spring on `headRoll` would
366
+ * silently re-time every nod ever authored. Secondary motion on a shell that
367
+ * only this renderer has costs nothing outside it.
368
+ *
369
+ * 1.5 Hz is the band the library already keeps gesture under, and a hank of hair
370
+ * on a real head swings near it (a 7 cm pendulum is 1.9 Hz); the damping is a
371
+ * single visible overshoot, settling inside 0.8 s. Faster reads as a flick and
372
+ * slower as wet hair.
373
+ */
374
+ /**
375
+ * `hold` is the share of the head's roll the hanging hair declines to take, and
376
+ * `hz`/`damping` are how it gets there. 1.5 Hz is the ceiling the repo's idle
377
+ * constraint sets on *driven* oscillation; a settle is a one-shot and could
378
+ * defensibly go faster, but there is no reason to spend the exemption: what
379
+ * unhinges the roll is the hair arriving late, not the ring. At 0.65 it trails
380
+ * by 93% of its travel a frame in, overshoots 5% and is inside 5% of the hold in
381
+ * 450 ms — well within a phrase's hold.
382
+ */
383
+ export declare const HAIR_ROLL: {
384
+ hold: number;
385
+ hz: number;
386
+ damping: number;
387
+ };
388
+ /**
389
+ * One step of `HAIR_ROLL`'s spring: semi-implicit, the rate taking the frame's
390
+ * acceleration before the angle takes the rate. Explicit Euler rings at this
391
+ * stiffness and 30 fps; this does not, which is the only reason the order of
392
+ * those two lines is worth a sentence.
393
+ *
394
+ * Exported for `test/nods.test.ts`, because settle time and overshoot are
395
+ * numbers and not something a still frame can show. It is not part of the
396
+ * package's surface — `packages/avatar/client/tara.ts` is.
397
+ */
398
+ export declare const hairRollStep: (angle: number, rate: number, target: number, dt: number) => {
399
+ angle: number;
400
+ rate: number;
401
+ };
402
+ export declare function createTaraRig(mount: HTMLElement, options?: unknown): AvatarRig;
403
+ //# sourceMappingURL=tara-rig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tara-rig.d.ts","sourceRoot":"","sources":["../../client/three/tara-rig.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAe,SAAS,EAAW,MAAM,gBAAgB,CAAC;AAmBtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAKH,eAAO,MAAM,UAAU,MAAM,CAAC;AAC9B,eAAO,MAAM,QAAQ;;;;CAAkC,CAAC;AAyIxD;;;;;;qDAMqD;AACrD,eAAO,MAAM,OAAO;;;CAA2G,CAAC;AAGhI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiJd,CAAC;AA8bX,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B;;;iFAG6E;IAC7E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB;oDACgD;IAChD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAsID;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS;;;;CAAyC,CAAC;AAEhE;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,EAAE,MAAM,MAAM,EAAE,QAAQ,MAAM,EAAE,IAAI,MAAM;;;CAInF,CAAC;AAkCF,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CA4Z9E"}